From 83bd6a33857121170fe1e891821b699c738e978d Mon Sep 17 00:00:00 2001 From: Roman Langrehr Date: Thu, 29 Jun 2023 00:15:14 +0200 Subject: [PATCH] Added fees to account transfer dialog --- .../portfolio/model/CrossEntryTest.java | 108 ++++++++++++++++++ .../name/abuchen/portfolio/ui/Messages.java | 2 + .../transactions/AccountTransferDialog.java | 37 +++++- .../transactions/AccountTransferModel.java | 91 ++++++++++++--- .../abuchen/portfolio/ui/messages.properties | 4 + .../portfolio/ui/messages_cs.properties | 4 + .../portfolio/ui/messages_da.properties | 4 + .../portfolio/ui/messages_de.properties | 4 + .../portfolio/ui/messages_es.properties | 4 + .../portfolio/ui/messages_fr.properties | 4 + .../portfolio/ui/messages_it.properties | 4 + .../portfolio/ui/messages_nl.properties | 6 +- .../portfolio/ui/messages_pl.properties | 4 + .../portfolio/ui/messages_pt.properties | 4 + .../portfolio/ui/messages_ru.properties | 4 + .../portfolio/ui/messages_sk.properties | 4 + .../portfolio/ui/messages_zh.properties | 4 + .../ui/views/TransactionsViewer.java | 19 ++- .../snapshot/ClientPerformanceSnapshot.java | 44 ++++++- 19 files changed, 330 insertions(+), 25 deletions(-) diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/model/CrossEntryTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/model/CrossEntryTest.java index d824ccf5d2..1283436ea1 100644 --- a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/model/CrossEntryTest.java +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/model/CrossEntryTest.java @@ -4,6 +4,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.Month; @@ -113,6 +114,70 @@ public void testAccountTransferEntry() assertThat(pA.getDateTime(), is(date)); assertThat(pB.getDateTime(), is(date)); + assertThat(accountA.getCurrentAmount(LocalDateTime.now().plusMinutes(1)), is(-1000L * Values.Amount.factor())); + assertThat(accountB.getCurrentAmount(LocalDateTime.now().plusMinutes(1)), is(1000L * Values.Amount.factor())); + + // check cross entity identification + assertThat(entry.getCrossOwner(pA), is((Object) accountB)); + assertThat(entry.getCrossTransaction(pA), is((Transaction) pB)); + + assertThat(entry.getCrossOwner(pB), is((Object) accountA)); + assertThat(entry.getCrossTransaction(pB), is((Transaction) pA)); + + // check cross editing + pA.setNote("Test"); //$NON-NLS-1$ + entry.updateFrom(pA); + assertThat(pB.getNote(), is(pA.getNote())); + + pB.setDateTime(LocalDateTime.of(2013, Month.MARCH, 16, 0, 0)); + entry.updateFrom(pB); + assertThat(pA.getDateTime(), is(pB.getDateTime())); + + // check deletion + accountA.deleteTransaction(pA, client); + assertThat(accountA.getTransactions().size(), is(0)); + assertThat(accountB.getTransactions().size(), is(0)); + } + + @Test + public void testAccountTransferEntryWithExchange() + { + Account accountA = client.getAccounts().get(0); + Account accountB = client.getAccounts().get(1); + accountB.setCurrencyCode("CHF"); + + AccountTransferEntry entry = new AccountTransferEntry(accountA, accountB); + LocalDateTime date = LocalDateTime.now(); + entry.setDate(date); + entry.getSourceTransaction().setCurrencyCode("EUR"); + entry.getTargetTransaction().setCurrencyCode("CHF"); + entry.getSourceTransaction().setAmount(1000 * Values.Amount.factor()); + Transaction.Unit forex = new Transaction.Unit(Transaction.Unit.Type.GROSS_VALUE, + Money.of("EUR", 1000 * Values.Amount.factor()), Money.of("CHF", 750 * Values.Amount.factor()), + new BigDecimal("1.33333333")); + entry.getSourceTransaction().addUnit(forex); + entry.getTargetTransaction().setAmount(750 * Values.Amount.factor()); + entry.insert(); + + assertThat(accountA.getTransactions().size(), is(1)); + assertThat(accountB.getTransactions().size(), is(1)); + + AccountTransaction pA = accountA.getTransactions().get(0); + AccountTransaction pB = accountB.getTransactions().get(0); + + assertThat(pA.getType(), is(AccountTransaction.Type.TRANSFER_OUT)); + assertThat(pB.getType(), is(AccountTransaction.Type.TRANSFER_IN)); + + assertThat(pA.getSecurity(), nullValue()); + assertThat(pB.getSecurity(), nullValue()); + assertThat(pA.getAmount(), is(1000L * Values.Amount.factor())); + assertThat(pB.getAmount(), is(750L * Values.Amount.factor())); + assertThat(pA.getDateTime(), is(date)); + assertThat(pB.getDateTime(), is(date)); + + assertThat(accountA.getCurrentAmount(date.plusMinutes(1)), is(-1000L * Values.Amount.factor())); + assertThat(accountB.getCurrentAmount(date.plusMinutes(1)), is(750L * Values.Amount.factor())); + // check cross entity identification assertThat(entry.getCrossOwner(pA), is((Object) accountB)); assertThat(entry.getCrossTransaction(pA), is((Transaction) pB)); @@ -135,6 +200,49 @@ public void testAccountTransferEntry() assertThat(accountB.getTransactions().size(), is(0)); } + @Test + public void testAccountTransferEntryWithFees() + { + Account accountA = client.getAccounts().get(0); + Account accountB = client.getAccounts().get(1); + + AccountTransferEntry entry = new AccountTransferEntry(accountA, accountB); + LocalDateTime date = LocalDateTime.now(); + entry.setDate(date); + entry.setCurrencyCode(CurrencyUnit.EUR); + entry.getSourceTransaction().setAmount(1000 * Values.Amount.factor()); + entry.getSourceTransaction().addUnit(new Transaction.Unit(Transaction.Unit.Type.FEE, + Money.of("EUR", 100L * Values.Amount.factor()))); + entry.getTargetTransaction().setAmount(850 * Values.Amount.factor()); + entry.getTargetTransaction().addUnit( + new Transaction.Unit(Transaction.Unit.Type.FEE, Money.of("EUR", 50L * Values.Amount.factor()))); + entry.insert(); + + assertThat(accountA.getTransactions().size(), is(1)); + assertThat(accountB.getTransactions().size(), is(1)); + + AccountTransaction pA = accountA.getTransactions().get(0); + AccountTransaction pB = accountB.getTransactions().get(0); + + assertThat(pA.getType(), is(AccountTransaction.Type.TRANSFER_OUT)); + assertThat(pB.getType(), is(AccountTransaction.Type.TRANSFER_IN)); + + assertThat(pA.getSecurity(), nullValue()); + assertThat(pB.getSecurity(), nullValue()); + assertThat(pA.getDateTime(), is(date)); + assertThat(pB.getDateTime(), is(date)); + + assertThat(accountA.getCurrentAmount(date.plusMinutes(1)), is(-1000L * Values.Amount.factor())); + assertThat(accountB.getCurrentAmount(date.plusMinutes(1)), is(850L * Values.Amount.factor())); + + // check cross entity identification + assertThat(entry.getCrossOwner(pA), is((Object) accountB)); + assertThat(entry.getCrossTransaction(pA), is((Transaction) pB)); + + assertThat(entry.getCrossOwner(pB), is((Object) accountA)); + assertThat(entry.getCrossTransaction(pB), is((Transaction) pA)); + } + @Test public void testPortoflioTransferEntry() { diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java index 52702ffc6a..9aec217b16 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java @@ -126,6 +126,7 @@ public class Messages extends NLS public static String ColumnChangeOnPrevious_MenuLabel; public static String ColumnChangeOnPrevious_MenuLabelAmount; public static String ColumnColor; + public static String ColumnCredit; public static String ColumnCreditNote; public static String ColumnColumnLabel; public static String ColumnConvertedAmount; @@ -148,6 +149,7 @@ public class Messages extends NLS public static String ColumnDaysBetweenPostfix; public static String ColumnDaysHigh; public static String ColumnDaysLow; + public static String ColumnDebit; public static String ColumnDebitNote; public static String ColumnAbsolutePerformance_MenuLabel; public static String ColumnAbsolutePerformance_Description; diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransferDialog.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransferDialog.java index bb0515506a..45e21951b8 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransferDialog.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransferDialog.java @@ -123,6 +123,14 @@ protected void createFormElements(Composite editArea) // other input fields + Input removedFxAmount = new Input(editArea, Messages.ColumnDebit); + removedFxAmount.bindValue(Properties.removedFxAmount.name(), Messages.ColumnDebit, Values.Amount, true); + removedFxAmount.bindCurrency(Properties.sourceAccountCurrency.name()); + + Input sourceFee = new Input(editArea, "- " + Messages.ColumnFees); //$NON-NLS-1$ + sourceFee.bindValue(Properties.sourceFee.name(), Messages.ColumnFees, Values.Amount, false); + sourceFee.bindCurrency(Properties.sourceAccountCurrency.name()); + Input fxAmount = new Input(editArea, Messages.ColumnAmount); fxAmount.bindValue(Properties.fxAmount.name(), Messages.ColumnAmount, Values.Amount, true); fxAmount.bindCurrency(Properties.sourceAccountCurrency.name()); @@ -145,6 +153,14 @@ protected void createFormElements(Composite editArea) amount.bindValue(Properties.amount.name(), Messages.ColumnAmount, Values.Amount, true); amount.bindCurrency(Properties.targetAccountCurrency.name()); + Input targetFee = new Input(editArea, "- " + Messages.ColumnFees); //$NON-NLS-1$ $ + targetFee.bindValue(Properties.targetFee.name(), Messages.ColumnFees, Values.Amount, false); + targetFee.bindCurrency(Properties.targetAccountCurrency.name()); + + Input creditedAmount = new Input(editArea, Messages.ColumnCredit); + creditedAmount.bindValue(Properties.creditedAmount.name(), Messages.ColumnCredit, Values.Amount, true); + creditedAmount.bindCurrency(Properties.targetAccountCurrency.name()); + // note Label lblNote = new Label(editArea, SWT.LEFT); @@ -166,8 +182,18 @@ protected void createFormElements(Composite editArea) .thenBelow(dateTime.date.getControl()).label(dateTime.label).thenRight(dateTime.time) .thenRight(dateTime.button, 0); + // removedFxAmount + // sourceFee // fxAmount - exchange rate - amount - forms.thenBelow(fxAmount.value).width(amountWidth).label(fxAmount.label) // + // targetFee + // creditedAmount + forms.thenBelow(removedFxAmount.value).width(amountWidth).label(removedFxAmount.label) // + .suffix(removedFxAmount.currency, currencyWidth) // + // sourceFee + .thenBelow(sourceFee.value).left(fxAmount.value).width(amountWidth).label(sourceFee.label) // + .suffix(sourceFee.currency, currencyWidth) // + // fxAmount - exchange rate - amount + .thenBelow(fxAmount.value).left(removedFxAmount.value).width(amountWidth).label(fxAmount.label) // .thenRight(fxAmount.currency).width(currencyWidth) // .thenRight(exchangeRate.label) // .thenRight(exchangeRate.value).width(amountWidth) // @@ -175,8 +201,15 @@ protected void createFormElements(Composite editArea) .thenRight(exchangeRate.currency).width(amountWidth) // .thenRight(amount.label) // .thenRight(amount.value).width(amountWidth) // - // note .suffix(amount.currency, currencyWidth) // + // targetFee + .thenBelow(targetFee.value).left(fxAmount.value).width(amountWidth).label(targetFee.label) // + .suffix(targetFee.currency, currencyWidth) // + // creditedAmount + .thenBelow(creditedAmount.value).left(fxAmount.value).width(amountWidth) + .label(creditedAmount.label) // + .suffix(creditedAmount.currency, currencyWidth) // + // note .thenBelow(valueNote).height(SWTHelper.lineHeight(valueNote) * 3) .left(target.value.getControl()).right(amount.value).label(lblNote); diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransferModel.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransferModel.java index e171d6b52e..c77dca2f71 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransferModel.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransferModel.java @@ -26,9 +26,10 @@ public class AccountTransferModel extends AbstractModel { public enum Properties { - sourceAccount, targetAccount, date, time, fxAmount, exchangeRate, inverseExchangeRate, amount, // + sourceAccount, targetAccount, date, time, removedFxAmount, fxAmount, // + exchangeRate, inverseExchangeRate, amount, creditedAmount, // note, sourceAccountCurrency, targetAccountCurrency, exchangeRateCurrencies, // - inverseExchangeRateCurrencies, calculationStatus; + inverseExchangeRateCurrencies, calculationStatus, sourceFee, targetFee; } private final Client client; @@ -40,9 +41,13 @@ public enum Properties private LocalDate date = LocalDate.now(); private LocalTime time = PresetValues.getTime(); + private long removedFxAmount; private long fxAmount; private BigDecimal exchangeRate = BigDecimal.ONE; private long amount; + private long creditedAmount; + private long sourceFee; + private long targetFee; private String note; private IStatus calculationStatus = ValidationStatus.ok(); @@ -101,18 +106,12 @@ public void applyChanges() sourceTransaction.clearUnits(); - if (sourceAccount.getCurrencyCode().equals(targetAccount.getCurrencyCode())) - { - sourceTransaction.setAmount(amount); - t.getTargetTransaction().setAmount(amount); - } - else + sourceTransaction.setAmount(removedFxAmount); + if (!(sourceAccount.getCurrencyCode().equals(targetAccount.getCurrencyCode()))) { // TODO improve naming of fields: the source amount is called // 'fxAmount' while the target amount is just called 'amount' but // then the source account holds the 'forex' which is switched - sourceTransaction.setAmount(fxAmount); - t.getTargetTransaction().setAmount(amount); Transaction.Unit forex = new Transaction.Unit(Transaction.Unit.Type.GROSS_VALUE, // Money.of(sourceAccount.getCurrencyCode(), fxAmount), // @@ -121,6 +120,14 @@ public void applyChanges() sourceTransaction.addUnit(forex); } + t.getTargetTransaction().setAmount(amount - targetFee); + + if (sourceFee != 0) + sourceTransaction.addUnit(new Transaction.Unit(Transaction.Unit.Type.FEE, + Money.of(sourceAccount.getCurrencyCode(), sourceFee))); + if (targetFee != 0) + t.getTargetTransaction().addUnit(new Transaction.Unit(Transaction.Unit.Type.FEE, + Money.of(targetAccount.getCurrencyCode(), targetFee))); } @Override @@ -128,6 +135,8 @@ public void resetToNewTransaction() { this.source = null; + setSourceFee(0); + setTargetFee(0); setFxAmount(0); setAmount(0); setNote(null); @@ -145,8 +154,17 @@ public void setSource(AccountTransferEntry entry) this.time = transactionDate.toLocalTime(); this.note = entry.getSourceTransaction().getNote(); - this.fxAmount = entry.getSourceTransaction().getAmount(); - this.amount = entry.getTargetTransaction().getAmount(); + // In AccountTransferEntry getAmount() always includes the fees. Here + // amount includes the fees of the target account (but fxAmount is + // without any fees). This way the monetary value of amount equals the + // monetary value of fxAmount with respect to their currencies. + this.removedFxAmount = entry.getSourceTransaction().getAmount(); + this.creditedAmount = entry.getTargetTransaction().getAmount(); + + this.sourceFee = entry.getSourceTransaction().getUnitSum(Transaction.Unit.Type.FEE).getAmount(); + this.targetFee = entry.getTargetTransaction().getUnitSum(Transaction.Unit.Type.FEE).getAmount(); + this.fxAmount = removedFxAmount - sourceFee; + this.amount = creditedAmount + targetFee; Optional forex = entry.getSourceTransaction().getUnit(Transaction.Unit.Type.GROSS_VALUE); @@ -272,6 +290,16 @@ public void setTime(LocalTime time) firePropertyChange(Properties.time.name(), this.time, this.time = time); } + public long getRemovedFxAmount() + { + return removedFxAmount; + } + + public void setRemovedFxAmount(long foreignCurrencyAmount) + { + setFxAmount(foreignCurrencyAmount - sourceFee); + } + public long getFxAmount() { return fxAmount; @@ -280,6 +308,8 @@ public long getFxAmount() public void setFxAmount(long foreignCurrencyAmount) { firePropertyChange(Properties.fxAmount.name(), this.fxAmount, this.fxAmount = foreignCurrencyAmount); + firePropertyChange(Properties.removedFxAmount.name(), this.removedFxAmount, + this.removedFxAmount = foreignCurrencyAmount + sourceFee); triggerAmount(Math.round(exchangeRate.doubleValue() * foreignCurrencyAmount)); @@ -300,7 +330,7 @@ public void setExchangeRate(BigDecimal exchangeRate) firePropertyChange(Properties.exchangeRate.name(), this.exchangeRate, this.exchangeRate = newRate); firePropertyChange(Properties.inverseExchangeRate.name(), oldInverseRate, getInverseExchangeRate()); - triggerAmount(Math.round(newRate.doubleValue() * fxAmount)); + triggerAmount(Math.round(newRate.doubleValue() * (fxAmount - sourceFee))); firePropertyChange(Properties.calculationStatus.name(), this.calculationStatus, this.calculationStatus = calculateStatus()); @@ -344,9 +374,44 @@ public void setAmount(long amount) this.calculationStatus = calculateStatus()); } + public long getCreditedAmount() + { + return creditedAmount; + } + + public void setCreditedAmount(long amount) + { + setAmount(amount + targetFee); + } + + public long getSourceFee() + { + return sourceFee; + } + + public void setSourceFee(long sourceFeeAmount) + { + firePropertyChange(Properties.sourceFee.name(), this.sourceFee, this.sourceFee = sourceFeeAmount); + setFxAmount(removedFxAmount - sourceFeeAmount); + } + + public long getTargetFee() + { + return targetFee; + } + + public void setTargetFee(long targetFeeAmount) + { + firePropertyChange(Properties.targetFee.name(), this.targetFee, this.targetFee = targetFeeAmount); + firePropertyChange(Properties.creditedAmount.name(), this.creditedAmount, + this.creditedAmount = this.amount - targetFeeAmount); + } + public void triggerAmount(long amount) { firePropertyChange(Properties.amount.name(), this.amount, this.amount = amount); + firePropertyChange(Properties.creditedAmount.name(), this.creditedAmount, + this.creditedAmount = amount - targetFee); } public String getNote() diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties index 2dcbdf7bc6..bcfb0504dc 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties @@ -310,6 +310,8 @@ ColumnCount = Count ColumnCountByYear = Count by Year +ColumnCredit = Credit + ColumnCreditNote = Credit Note ColumnCurrency = Currency @@ -340,6 +342,8 @@ ColumnDaysHigh = Day's High ColumnDaysLow = Day's Low +ColumnDebit = Debit + ColumnDebitNote = Debit Note ColumnDeltaPctOfTotal = Delta % of Total diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_cs.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_cs.properties index bdb2185320..bce9623fda 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_cs.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_cs.properties @@ -300,6 +300,8 @@ ColumnCount = Po\u010Det ColumnCountByYear = Po\u010Det podle roku +ColumnCredit = Kredit + ColumnCreditNote = Ve prosp\u011Bch ColumnCurrency = M\u011Bna @@ -330,6 +332,8 @@ ColumnDaysHigh = Denn\u00ED maximum ColumnDaysLow = Denn\u00ED minimum +ColumnDebit = Debetn\u00ED + ColumnDebitNote = Na vrub ColumnDeltaPctOfTotal = Delta % z celku diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_da.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_da.properties index 7d5a934633..94f10b9b06 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_da.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_da.properties @@ -297,6 +297,8 @@ ColumnCount = T\u00E6l ColumnCountByYear = T\u00E6l efter \u00E5r +ColumnCredit = Kredit + ColumnCreditNote = Kreditnota ColumnCurrency = Valuta @@ -327,6 +329,8 @@ ColumnDaysHigh = Dagens h\u00F8jeste ColumnDaysLow = Dagens laveste +ColumnDebit = Debitering + ColumnDebitNote = Debetnota ColumnDeltaPctOfTotal = Delta % af Total diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_de.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_de.properties index 2059938de7..3264c69102 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_de.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_de.properties @@ -297,6 +297,8 @@ ColumnCount = Anzahl ColumnCountByYear = Anzahl pro Jahr +ColumnCredit = Gutschrift + ColumnCreditNote = Gutschrift ColumnCurrency = W\u00E4hrung @@ -327,6 +329,8 @@ ColumnDaysHigh = Hoch (Tag) ColumnDaysLow = Tief (Tag) +ColumnDebit = Belastung + ColumnDebitNote = Belastung ColumnDeltaPctOfTotal = Delta % am GV diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_es.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_es.properties index 3a21b6d82d..78ee9940ff 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_es.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_es.properties @@ -297,6 +297,8 @@ ColumnCount = Contar ColumnCountByYear = Recuento por a\u00F1o +ColumnCredit = Cr\u00E9dito + ColumnCreditNote = Nota de cr\u00E9dito ColumnCurrency = Divisa @@ -327,6 +329,8 @@ ColumnDaysHigh = M\u00E1ximo del d\u00EDa ColumnDaysLow = M\u00EDnimo del d\u00EDa +ColumnDebit = D\u00E9bito + ColumnDebitNote = Nota de d\u00E9bito ColumnDeltaPctOfTotal = Delta % del total diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_fr.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_fr.properties index ea574eaebc..ed1b48f6b1 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_fr.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_fr.properties @@ -298,6 +298,8 @@ ColumnCount = Nombre ColumnCountByYear = Nombre par ann\u00E9e +ColumnCredit = Cr\u00E9dit + ColumnCreditNote = Cr\u00E9dit ColumnCurrency = Devise @@ -328,6 +330,8 @@ ColumnDaysHigh = Plus haut (jour) ColumnDaysLow = Plus bas (jour) +ColumnDebit = D\u00E9bit + ColumnDebitNote = D\u00E9bit ColumnDeltaPctOfTotal = Delta % du total diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_it.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_it.properties index 8c00cef1a8..17b8b4c8c2 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_it.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_it.properties @@ -297,6 +297,8 @@ ColumnCount = Conta ColumnCountByYear = Conteggio per anno +ColumnCredit = A credito + ColumnCreditNote = Nota di credito ColumnCurrency = Valuta @@ -327,6 +329,8 @@ ColumnDaysHigh = Max giornaliero ColumnDaysLow = Min giornaliero +ColumnDebit = Addebito + ColumnDebitNote = Nota di debito ColumnDeltaPctOfTotal = Delta % del totale diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_nl.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_nl.properties index 66fb377398..5383d2cf5b 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_nl.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_nl.properties @@ -297,6 +297,8 @@ ColumnCount = Tellen ColumnCountByYear = Telling per jaar +ColumnCredit = Credit + ColumnCreditNote = Creditnota ColumnCurrency = Valuta @@ -327,6 +329,8 @@ ColumnDaysHigh = Hoogste koers van de dag ColumnDaysLow = Laagste koers van de dag +ColumnDebit = Debet + ColumnDebitNote = Debet Note ColumnDeltaPctOfTotal = Delta % van totaal @@ -2197,8 +2201,6 @@ PrefMsgConfigureUpdates = Configureer waar te controleren op updates. PrefMsgLanguageConfig = Om de taal te wijzigen, is een herstart vereist. -PrefNoteIndirectQuotation = Koersen die de eigen valuta van een land als prijsvaluta gebruiken\n(bijvoorbeeld EUR 0,9009 = USD 1,00 in de eurozone), staan bekend \nals een directe koers of een prijskoers (vanuit dat land gezien) \nen worden door de meeste landen gebruikt. \n\nKoersen die de eigen valuta van een land als eenheidsvaluta gebruiken\n(bijvoorbeeld USD 1,11 = EUR 1,00 in de eurozone), staan bekend\nals een indirecte koers of hoeveelheidskoers en worden gebruikt in \nBritse kranten en zijn veelvoorkomend in Australi\u00EB, Nieuw-Zeeland \nen de eurozone.\n\nBron: https://en.wikipedia.org/wiki/Exchange_rate\n\n\nVoorbeelden:\n\nIndirecte (hoeveelheids)koers: EUR/USD 1,1232 \n"Voor \u00E9\u00E9n euro krijg je 1,1232 USD."\n\nDirecte (prijs)koers: USD/EUR 0,89 \n"E\u00E9n USD kost 0,89 EUR." - PrefMyDividends24APIKey = myDividends24 API sleutel PrefNoteIndirectQuotation = Koersen met de thuisvaluta van een land als prijsvaluta \n(bijvoorbeeld EUR 0,9009 = USD 1,00 in de eurozone) zijn bekend \neen directe offerte of een prijsofferte (van dat land \nperspective) en worden door de meeste landen gebruikt. \n\nKoersen die de eigen valuta van een land gebruiken als de eenheidvaluta \n(bijvoorbeeld USD 1,11 = EUR 1,00 in de eurozone)\n\nZijn bekende indirecte offertes of kwantumoffertes en worden gebruikt in \nBritische kranten en zijn ook veel voorkomend in Australi\u00EB, Nieuw \nZealand en de eurozone.\n\nBron: https://en.wikipedia.org/wiki/Exchange_rate\n\nExamples:\n\nIndirect (Hoeveelheid) Offerte: EUR / USD 1.1232 \n "Voor \u00E9\u00E9n euro krijgt 1.1232 USD. "\n\nDirect (prijs) Offerte: USD / EUR 0,89 \n" E\u00E9n USD kost 0,89 EUR. " diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pl.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pl.properties index 1cc8262d66..2557cec633 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pl.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pl.properties @@ -297,6 +297,8 @@ ColumnCount = Liczba ColumnCountByYear = Policz wg roku +ColumnCredit = Kredyt + ColumnCreditNote = Nota kredytowa ColumnCurrency = Waluta @@ -327,6 +329,8 @@ ColumnDaysHigh = Dzienne maksimum ColumnDaysLow = Dzienne minumum +ColumnDebit = Obci\u0105\u017Cenie + ColumnDebitNote = Nota debetowa ColumnDeltaPctOfTotal = R\u00F3\u017Cnica % ca\u0142o\u015Bci diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pt.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pt.properties index 1dae099a2a..bb16a7c060 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pt.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pt.properties @@ -297,6 +297,8 @@ ColumnCount = Contagem ColumnCountByYear = Contagem por ano +ColumnCredit = Cr\u00E9dito + ColumnCreditNote = Nota de cr\u00E9dito ColumnCurrency = Moeda @@ -327,6 +329,8 @@ ColumnDaysHigh = M\u00E1xima Di\u00E1ria ColumnDaysLow = M\u00EDnima Di\u00E1ria +ColumnDebit = D\u00E9bito + ColumnDebitNote = Nota de D\u00E9bito ColumnDeltaPctOfTotal = Delta % do total diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_ru.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_ru.properties index 5b2a3b0d41..4169ef9a6a 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_ru.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_ru.properties @@ -297,6 +297,8 @@ ColumnCount = \u041A\u043E\u043B-\u0432\u043E ColumnCountByYear = \u041F\u043E\u0434\u0441\u0447\u0435\u0442 \u043F\u043E \u0433\u043E\u0434\u0430\u043C +ColumnCredit = \u041A\u0440\u0435\u0434\u0438\u0442 + ColumnCreditNote = \u0421\u0443\u043C\u043C\u0430 \u043D\u0435\u0442\u0442\u043E ColumnCurrency = \u0412\u0430\u043B\u044E\u0442\u0430 @@ -327,6 +329,8 @@ ColumnDaysHigh = \u041C\u0430\u043A\u0441 \u0434\u043D\u044F ColumnDaysLow = \u041C\u0438\u043D \u0434\u043D\u044F +ColumnDebit = \u0414\u0435\u0431\u0435\u0442 + ColumnDebitNote = \u0421\u0443\u043C\u043C\u0430 \u0441\u043F\u0438\u0441\u0430\u043D\u0438\u044F ColumnDeltaPctOfTotal = \u0414\u0435\u043B\u044C\u0442\u0430 % \u043E\u0442 \u043E\u0431\u0449\u0435\u0439 \u0441\u0443\u043C\u043C\u044B diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_sk.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_sk.properties index 66438de83f..a8b996e927 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_sk.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_sk.properties @@ -297,6 +297,8 @@ ColumnCount = Po\u010D\u00EDtajte ColumnCountByYear = Po\u010Dty po rokoch +ColumnCredit = Kredit + ColumnCreditNote = V prospech ColumnCurrency = Mena @@ -327,6 +329,8 @@ ColumnDaysHigh = Denn\u00E9 maximum ColumnDaysLow = Denn\u00E9 minimum +ColumnDebit = Debet + ColumnDebitNote = M\u00E1 da\u0165 ColumnDeltaPctOfTotal = Delta % z celkov\u00E9ho po\u010Dtu diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_zh.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_zh.properties index 79bd650b30..ea8c49fb98 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_zh.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_zh.properties @@ -297,6 +297,8 @@ ColumnCount = \u8BA1\u6570 ColumnCountByYear = \u6309\u5E74\u8BA1\u6570 +ColumnCredit = \u8D37\u65B9 + ColumnCreditNote = \u8D37\u8BB0 ColumnCurrency = \u8D27\u5E01 @@ -327,6 +329,8 @@ ColumnDaysHigh = \u5F53\u65E5\u6700\u9AD8 ColumnDaysLow = \u5F53\u65E5\u6700\u4F4E +ColumnDebit = \u501F\u6B3E + ColumnDebitNote = \u501F\u8BB0 ColumnDeltaPctOfTotal = \u53D8\u5316 % \u603B\u4F53 diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/TransactionsViewer.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/TransactionsViewer.java index 1602473aeb..84a544a8f7 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/TransactionsViewer.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/TransactionsViewer.java @@ -380,14 +380,29 @@ public Color getBackground(Object element) column = new Column("6", Messages.ColumnFees, SWT.RIGHT, 80); //$NON-NLS-1$ column.setLabelProvider(new TransactionLabelProvider(t -> Values.Money - .formatNonZero(t.getUnitSum(Transaction.Unit.Type.FEE), owner.getClient().getBaseCurrency()))); + .formatNonZero(t.getUnitSum(Transaction.Unit.Type.FEE), owner.getClient().getBaseCurrency()) + // In the overview only the TRANSFER_OUT transactions + // are shown and the corresponding TRANSFER_IN + // transactions are filtered to avoid "duplicates". Thus + // we show the full transaction fees in the TRANSFER_OUT + // here. + + ((t instanceof AccountTransaction accountTransaction + && accountTransaction.getType() == AccountTransaction.Type.TRANSFER_OUT) + ? " + " //$NON-NLS-1$ + + Values.Money.formatNonZero(t.getCrossEntry() + .getCrossTransaction(t) + .getUnitSum(Transaction.Unit.Type.FEE), + owner.getClient() + .getBaseCurrency()) + : ""))); //$NON-NLS-1$ ColumnViewerSorter.create(e -> ((TransactionPair) e).getTransaction().getUnitSum(Transaction.Unit.Type.FEE)) .attachTo(column); support.addColumn(column); column = new Column("7", Messages.ColumnTaxes, SWT.RIGHT, 80); //$NON-NLS-1$ column.setLabelProvider(new TransactionLabelProvider(t -> Values.Money - .formatNonZero(t.getUnitSum(Transaction.Unit.Type.TAX), owner.getClient().getBaseCurrency()))); + .formatNonZero(t.getUnitSum(Transaction.Unit.Type.TAX), owner.getClient().getBaseCurrency()) + )); ColumnViewerSorter.create(e -> ((TransactionPair) e).getTransaction().getUnitSum(Transaction.Unit.Type.TAX)) .attachTo(column); support.addColumn(column); diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/snapshot/ClientPerformanceSnapshot.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/snapshot/ClientPerformanceSnapshot.java index e166a5e9af..d83d073bba 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/snapshot/ClientPerformanceSnapshot.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/snapshot/ClientPerformanceSnapshot.java @@ -21,7 +21,6 @@ import name.abuchen.portfolio.model.Portfolio; import name.abuchen.portfolio.model.PortfolioTransaction; import name.abuchen.portfolio.model.Security; -import name.abuchen.portfolio.model.Transaction; import name.abuchen.portfolio.model.Transaction.Unit; import name.abuchen.portfolio.model.TransactionPair; import name.abuchen.portfolio.money.CurrencyConverter; @@ -450,10 +449,12 @@ private void addEarnings() taxesBySecurity.computeIfAbsent(t.getSecurity(), s -> MutableMoney.of(termCurrency)) .subtract(value); break; - case BUY: - case SELL: case TRANSFER_IN: case TRANSFER_OUT: + addFees(account, t, mFees); + break; + case BUY: + case SELL: // no operation break; default: @@ -497,6 +498,7 @@ private void addEarnings() case SELL: case TRANSFER_IN: case TRANSFER_OUT: + break; default: throw new UnsupportedOperationException(); @@ -565,6 +567,16 @@ private void addEarningTransaction(Account account, AccountTransaction transacti } } + private void addFees(Account account, AccountTransaction transaction, MutableMoney mFees) + { + Money fee = transaction.getUnitSum(Unit.Type.FEE, converter).with(converter.at(transaction.getDateTime())); + if (!fee.isZero()) + { + mFees.add(fee); + this.fees.add(new TransactionPair(account, transaction)); + } + } + private void addCurrencyGains() { Map currency2money = new HashMap<>(); @@ -644,16 +656,36 @@ private void addCurrencyGains() */ private Money determineTransferAmount(AccountTransaction t) { + // Fees are converted with the exchange rate provider to + // match + // other places where fees are calculated. + Money feesSource = t.getUnitSum(Unit.Type.FEE, converter).with(converter.at(t.getDateTime())); + Money feesTarget = t.getCrossEntry().getCrossTransaction(t).getUnitSum(Unit.Type.FEE, converter) + .with(converter.at(t.getDateTime())); + Money feesTotal = feesSource.add(feesTarget); + if (converter.getTermCurrency().equals(t.getCurrencyCode())) - return t.getMonetaryAmount(); + { + Money result = t.getMonetaryAmount(); + if(t.getType() == AccountTransaction.Type.TRANSFER_OUT) + result = result.subtract(feesTotal); + return result; + } - Transaction other = t.getCrossEntry().getCrossTransaction(t); + AccountTransaction other = (AccountTransaction) t.getCrossEntry().getCrossTransaction(t); if (converter.getTermCurrency().equals(other.getCurrencyCode())) - return other.getMonetaryAmount(); + { + Money result = other.getMonetaryAmount(); + if (other.getType() == AccountTransaction.Type.TRANSFER_OUT) + result = result.subtract(feesTotal); + return result; + } MutableMoney m = MutableMoney.of(converter.getTermCurrency()); m.add(t.getMonetaryAmount().with(converter.at(t.getDateTime()))); m.add(other.getMonetaryAmount().with(converter.at(t.getDateTime()))); + m.subtract(feesTotal); // Either the amount of t or of other is with + // fees. return m.divide(2).toMoney(); } }