Skip to content

Commit 9d6b9e3

Browse files
committed
Added ex-date column to transaction tables
Issue: portfolio-performance#5439
1 parent 1efdc4a commit 9d6b9e3

File tree

5 files changed

+209
-3
lines changed

5 files changed

+209
-3
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package name.abuchen.portfolio.ui.util.viewers;
2+
3+
import static org.hamcrest.MatcherAssert.assertThat;
4+
import static org.hamcrest.Matchers.is;
5+
import static org.hamcrest.Matchers.nullValue;
6+
7+
import java.time.LocalDate;
8+
import java.time.LocalDateTime;
9+
import java.util.concurrent.atomic.AtomicBoolean;
10+
import java.util.concurrent.atomic.AtomicReference;
11+
12+
import org.junit.Test;
13+
14+
import name.abuchen.portfolio.model.Account;
15+
import name.abuchen.portfolio.model.AccountTransaction;
16+
import name.abuchen.portfolio.model.TransactionPair;
17+
18+
@SuppressWarnings("nls")
19+
public class ExDateEditingSupportTest
20+
{
21+
@Test
22+
public void blankValueClearsExDateAndNotifiesNullAsNewValue()
23+
{
24+
var support = new ExDateEditingSupport();
25+
26+
var tx = new AccountTransaction();
27+
tx.setType(AccountTransaction.Type.DIVIDENDS);
28+
tx.setExDate(LocalDateTime.of(2026, 2, 21, 10, 15));
29+
30+
var element = new TransactionPair<>(new Account("Test Account"), tx);
31+
32+
AtomicBoolean notified = new AtomicBoolean(false);
33+
AtomicReference<Object> newValue = new AtomicReference<>();
34+
AtomicReference<Object> oldValue = new AtomicReference<>();
35+
36+
support.addListener((e, n, o) -> {
37+
notified.set(true);
38+
newValue.set(n);
39+
oldValue.set(o);
40+
});
41+
42+
support.setValue(element, " ");
43+
44+
assertThat(tx.getExDate(), is(nullValue()));
45+
assertThat(notified.get(), is(true));
46+
assertThat(newValue.get(), is(nullValue()));
47+
assertThat(oldValue.get(), is(LocalDateTime.of(2026, 2, 21, 10, 15)));
48+
assertThat(support.getValue(element), is(""));
49+
}
50+
51+
@Test
52+
public void parsedLocalDateIsStoredAsLocalDateTimeAtStartOfDay()
53+
{
54+
var support = new ExDateEditingSupport();
55+
56+
var tx = new AccountTransaction();
57+
tx.setType(AccountTransaction.Type.DIVIDENDS);
58+
59+
var element = new TransactionPair<>(new Account("Test Account"), tx);
60+
61+
support.setValue(element, "2026-02-22");
62+
63+
assertThat(tx.getExDate(), is(LocalDate.of(2026, 2, 22).atStartOfDay()));
64+
}
65+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package name.abuchen.portfolio.ui.util.viewers;
2+
3+
import java.text.MessageFormat;
4+
import java.time.LocalDate;
5+
import java.time.LocalDateTime;
6+
import java.time.format.DateTimeFormatter;
7+
import java.time.format.DateTimeParseException;
8+
import java.time.format.FormatStyle;
9+
10+
import org.eclipse.jface.viewers.CellEditor;
11+
import org.eclipse.jface.viewers.TextCellEditor;
12+
import org.eclipse.swt.widgets.Composite;
13+
import org.eclipse.swt.widgets.Text;
14+
15+
import name.abuchen.portfolio.model.AccountTransaction;
16+
import name.abuchen.portfolio.model.Adaptor;
17+
import name.abuchen.portfolio.money.Values;
18+
import name.abuchen.portfolio.ui.Messages;
19+
20+
public class ExDateEditingSupport extends ColumnEditingSupport
21+
{
22+
private static final DateTimeFormatter[] dateFormatters = new DateTimeFormatter[] {
23+
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM),
24+
DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT), //
25+
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG), //
26+
DateTimeFormatter.ofPattern("d.M.yyyy"), //$NON-NLS-1$
27+
DateTimeFormatter.ofPattern("d.M.yy"), //$NON-NLS-1$
28+
DateTimeFormatter.ISO_DATE };
29+
30+
@Override
31+
public boolean canEdit(Object element)
32+
{
33+
var tx = Adaptor.adapt(AccountTransaction.class, element);
34+
return tx != null && tx.getType() == AccountTransaction.Type.DIVIDENDS;
35+
}
36+
37+
@Override
38+
public Object getValue(Object element)
39+
{
40+
var tx = Adaptor.adapt(AccountTransaction.class, element);
41+
var exDate = tx.getExDate();
42+
return exDate != null ? Values.Date.format(exDate.toLocalDate()) : ""; //$NON-NLS-1$
43+
}
44+
45+
@Override
46+
public void setValue(Object element, Object value)
47+
{
48+
var tx = Adaptor.adapt(AccountTransaction.class, element);
49+
50+
var oldValue = tx.getExDate();
51+
52+
var inputValue = ((String) value).trim();
53+
54+
// ex-date is an optional value, therefore we remove the ex-date if not
55+
// input is given
56+
if (inputValue.isEmpty())
57+
{
58+
if (oldValue != null)
59+
{
60+
tx.setExDate(null);
61+
notify(element, null, oldValue);
62+
}
63+
return;
64+
}
65+
66+
LocalDateTime newValue = null;
67+
for (DateTimeFormatter formatter : dateFormatters)
68+
{
69+
try
70+
{
71+
newValue = LocalDate.parse(inputValue, formatter).atStartOfDay();
72+
break;
73+
}
74+
catch (DateTimeParseException ignore)
75+
{
76+
// continue with next formatter
77+
}
78+
}
79+
80+
// there is text input, but it cannot be parsed as a local date -> throw
81+
// an error
82+
if (newValue == null)
83+
throw new IllegalArgumentException(MessageFormat.format(Messages.MsgErrorNotAValidDate, value));
84+
85+
if (!newValue.equals(oldValue))
86+
{
87+
tx.setExDate(newValue);
88+
notify(element, newValue, oldValue);
89+
}
90+
}
91+
92+
@Override
93+
public CellEditor createEditor(Composite composite)
94+
{
95+
TextCellEditor textEditor = new TextCellEditor(composite);
96+
((Text) textEditor.getControl()).setTextLimit(20);
97+
return textEditor;
98+
}
99+
}

name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/TransactionsViewer.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package name.abuchen.portfolio.ui.views;
22

3+
import java.time.LocalDateTime;
34
import java.util.HashSet;
45
import java.util.List;
56
import java.util.Objects;
@@ -51,6 +52,7 @@
5152
import name.abuchen.portfolio.ui.util.viewers.CopyPasteSupport;
5253
import name.abuchen.portfolio.ui.util.viewers.DateTimeEditingSupport;
5354
import name.abuchen.portfolio.ui.util.viewers.DateTimeLabelProvider;
55+
import name.abuchen.portfolio.ui.util.viewers.ExDateEditingSupport;
5456
import name.abuchen.portfolio.ui.util.viewers.SharesLabelProvider;
5557
import name.abuchen.portfolio.ui.util.viewers.ShowHideColumnHelper;
5658
import name.abuchen.portfolio.ui.util.viewers.StringEditingSupport;
@@ -459,14 +461,28 @@ public String getToolTipText(Object e)
459461
return note == null || note.isEmpty() ? null : TextUtil.wordwrap(note);
460462
}
461463
});
462-
ColumnViewerSorter.createIgnoreCase(e -> ((TransactionPair<?>) e).getTransaction().getNote()).attachTo(column); // $NON-NLS-1$
464+
ColumnViewerSorter.createIgnoreCase(e -> ((TransactionPair<?>) e).getTransaction().getNote()).attachTo(column);
463465
new StringEditingSupport(Transaction.class, "note").addListener(this).attachTo(column); //$NON-NLS-1$
464466
support.addColumn(column);
465467

468+
column = new Column("exdate", Messages.ColumnExDate, SWT.None, 80); //$NON-NLS-1$
469+
Function<Transaction, LocalDateTime> exDateProvider = tx -> tx instanceof AccountTransaction atx
470+
? atx.getExDate()
471+
: null;
472+
column.setLabelProvider(new TransactionLabelProvider(tx -> {
473+
var exDate = exDateProvider.apply(tx);
474+
return exDate != null ? Values.Date.format(exDate.toLocalDate()) : null;
475+
}));
476+
ColumnViewerSorter.create(e -> exDateProvider.apply(((TransactionPair<?>) e).getTransaction()))
477+
.attachTo(column);
478+
new ExDateEditingSupport().addListener(this).attachTo(column);
479+
column.setVisible(false);
480+
support.addColumn(column);
481+
466482
column = new Column("source", Messages.ColumnSource, SWT.None, 200); //$NON-NLS-1$
467483
column.setLabelProvider(new TransactionLabelProvider(Transaction::getSource));
468484
ColumnViewerSorter.createIgnoreCase(e -> ((TransactionPair<?>) e).getTransaction().getSource())
469-
.attachTo(column); // $NON-NLS-1$
485+
.attachTo(column);
470486
support.addColumn(column);
471487
}
472488

name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/panes/AccountTransactionsPane.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import name.abuchen.portfolio.ui.util.viewers.CopyPasteSupport;
7272
import name.abuchen.portfolio.ui.util.viewers.DateTimeEditingSupport;
7373
import name.abuchen.portfolio.ui.util.viewers.DateTimeLabelProvider;
74+
import name.abuchen.portfolio.ui.util.viewers.ExDateEditingSupport;
7475
import name.abuchen.portfolio.ui.util.viewers.MoneyColorLabelProvider;
7576
import name.abuchen.portfolio.ui.util.viewers.SharesLabelProvider;
7677
import name.abuchen.portfolio.ui.util.viewers.ShowHideColumnHelper;
@@ -437,6 +438,27 @@ public Image getImage(Object e)
437438
column.getEditingSupport().addListener(this);
438439
transactionsColumns.addColumn(column);
439440

441+
column = new Column("exdate", Messages.ColumnExDate, SWT.None, 80); //$NON-NLS-1$
442+
column.setLabelProvider(new ColumnLabelProvider()
443+
{
444+
@Override
445+
public String getText(Object e)
446+
{
447+
var t = (AccountTransaction) e;
448+
return t.getExDate() != null ? Values.Date.format(t.getExDate().toLocalDate()) : null;
449+
}
450+
451+
@Override
452+
public Color getForeground(Object element)
453+
{
454+
return colorFor((AccountTransaction) element);
455+
}
456+
});
457+
ColumnViewerSorter.create(e -> ((AccountTransaction) e).getExDate()).attachTo(column);
458+
new ExDateEditingSupport().addListener(this).attachTo(column);
459+
column.setVisible(false);
460+
transactionsColumns.addColumn(column);
461+
440462
column = new Column("source", Messages.ColumnSource, SWT.None, 120); //$NON-NLS-1$
441463
column.setLabelProvider(new ColumnLabelProvider()
442464
{
@@ -452,7 +474,7 @@ public Color getForeground(Object element)
452474
return colorFor((AccountTransaction) element);
453475
}
454476
});
455-
ColumnViewerSorter.createIgnoreCase(e -> ((AccountTransaction) e).getSource()).attachTo(column); // $NON-NLS-1$
477+
ColumnViewerSorter.createIgnoreCase(e -> ((AccountTransaction) e).getSource()).attachTo(column);
456478
transactionsColumns.addColumn(column);
457479

458480
transactionsColumns.createColumns(true);

name.abuchen.portfolio/src/name/abuchen/portfolio/model/TransactionPair.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ public <A> A adapt(Class<A> type)
9797
return type.cast(transaction);
9898
else if (type == Transaction.class)
9999
return type.cast(transaction);
100+
else if (type == AccountTransaction.class && transaction instanceof AccountTransaction)
101+
return type.cast(transaction);
102+
else if (type == PortfolioTransaction.class && transaction instanceof PortfolioTransaction)
103+
return type.cast(transaction);
100104
else if (type == Named.class)
101105
return type.cast(transaction.getSecurity());
102106
else if (type == Security.class)

0 commit comments

Comments
 (0)