Skip to content

Commit b811c29

Browse files
ZfT2buchen
authored andcommitted
Allow editing the ex-date in the account transaction dialog
Issue: portfolio-performance#5439 Allow editing the ex-date in the account transaction dialog portfolio-performance#5511 - initial check-in
1 parent 67a02c5 commit b811c29

File tree

9 files changed

+222
-2
lines changed

9 files changed

+222
-2
lines changed

name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,12 +1095,14 @@ public class Messages extends NLS
10951095
public static String MsgConfirmInstall;
10961096
public static String MsgCreateTransactionsAutomaticallyUponOpening;
10971097
public static String MsgDateIsInTheFuture;
1098+
public static String MsgDateExIsAfterTheDate;
10981099
public static String MsgDeletionNotPossible;
10991100
public static String MsgDeletionNotPossibleDetail;
11001101
public static String MsgDialogInputRequired;
11011102
public static String MsgDialogNotAValidCurrency;
11021103
public static String MsgDialogNotAValidISIN;
11031104
public static String MsgEmbeddedBrowserError;
1105+
public static String MsgExDateNotAllowed;
11041106
public static String MsgErrorAccountNotExist;
11051107
public static String MsgErrorConvertedAmount;
11061108
public static String MsgErrorConvertToBuySellCurrencyMismatch;

name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AbstractTransactionDialog.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import java.text.DecimalFormat;
55
import java.text.MessageFormat;
66
import java.text.NumberFormat;
7+
import java.time.LocalDate;
8+
import java.time.LocalDateTime;
79
import java.time.LocalTime;
810
import java.util.ArrayList;
911
import java.util.List;
@@ -41,6 +43,7 @@
4143
import org.eclipse.swt.events.MouseListener;
4244
import org.eclipse.swt.layout.FormLayout;
4345
import org.eclipse.swt.widgets.Composite;
46+
import org.eclipse.swt.widgets.Button;
4447
import org.eclipse.swt.widgets.Control;
4548
import org.eclipse.swt.widgets.Label;
4649
import org.eclipse.swt.widgets.Shell;
@@ -59,7 +62,10 @@
5962
import name.abuchen.portfolio.ui.util.CurrencyProposalProvider;
6063
import name.abuchen.portfolio.ui.util.CurrencyToStringConverter;
6164
import name.abuchen.portfolio.ui.util.DatePicker;
65+
import name.abuchen.portfolio.ui.util.DateTimeToDateConverter;
66+
import name.abuchen.portfolio.ui.util.DateToDateTimeConverter;
6267
import name.abuchen.portfolio.ui.util.IValidatingConverter;
68+
import name.abuchen.portfolio.ui.util.NullableDateTimeDateSelectionProperty;
6369
import name.abuchen.portfolio.ui.util.SimpleDateTimeDateSelectionProperty;
6470
import name.abuchen.portfolio.ui.util.SimpleDateTimeTimeSelectionProperty;
6571
import name.abuchen.portfolio.ui.util.StringToCurrencyConverter;
@@ -320,6 +326,40 @@ public void linkActivated(HyperlinkEvent e)
320326
}
321327
}
322328

329+
public class ExDateInput
330+
{
331+
public final Button checkBox;
332+
public final DatePicker date;
333+
334+
public ExDateInput(Composite editArea)
335+
{
336+
checkBox = new Button(editArea, SWT.CHECK);
337+
checkBox.setText(Messages.ColumnExDate);
338+
date = new DatePicker(editArea);
339+
}
340+
341+
public void bindDate(String property)
342+
{
343+
UpdateValueStrategy<LocalDate, LocalDateTime> targetToModel = new UpdateValueStrategy<>();
344+
targetToModel.setConverter(new DateToDateTimeConverter());
345+
346+
UpdateValueStrategy<LocalDateTime, LocalDate> modelToTarget = new UpdateValueStrategy<>();
347+
modelToTarget.setConverter(new DateTimeToDateConverter());
348+
349+
IObservableValue<LocalDate> targetObservable = new NullableDateTimeDateSelectionProperty()
350+
.observe(date.getControl());
351+
IObservableValue<LocalDateTime> modelObservable = BeanProperties.value(property, LocalDateTime.class)
352+
.observe(model);
353+
context.bindValue(targetObservable, modelObservable, targetToModel, modelToTarget);
354+
}
355+
356+
public void setVisible(boolean visible)
357+
{
358+
checkBox.setVisible(visible);
359+
date.getControl().setVisible(visible);
360+
}
361+
}
362+
323363
public class ExchangeRateInput extends Input
324364
{
325365
public final ImageHyperlink buttonInvertExchangeRate;

name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransactionDialog.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.eclipse.swt.SWT;
2929
import org.eclipse.swt.events.SelectionAdapter;
3030
import org.eclipse.swt.events.SelectionEvent;
31+
import org.eclipse.swt.events.SelectionListener;
3132
import org.eclipse.swt.widgets.Button;
3233
import org.eclipse.swt.widgets.Composite;
3334
import org.eclipse.swt.widgets.Label;
@@ -121,6 +122,12 @@ protected void createFormElements(Composite editArea) // NOSONAR
121122
dateTime.bindTime(Properties.time.name());
122123
dateTime.bindButton(() -> model().getTime(), time -> model().setTime(time));
123124

125+
// ex-date
126+
127+
var exDate = new ExDateInput(editArea);
128+
exDate.bindDate(Properties.exDate.name());
129+
exDate.setVisible(model().supportsSecurity());
130+
124131
// shares
125132

126133
Input shares = new Input(editArea, Messages.ColumnShares);
@@ -251,6 +258,20 @@ public void widgetSelected(SelectionEvent e)
251258

252259
startingWith(dateTime.date.getControl()).thenRight(dateTime.time).thenRight(dateTime.button, 0);
253260

261+
if (model().supportsSecurity())
262+
{
263+
startingWith(dateTime.button).thenRight(exDate.checkBox).thenRight(exDate.date.getControl());
264+
265+
var hasExDate = model().getExDate() != null;
266+
toggleExDatePicker(exDate, hasExDate);
267+
268+
exDate.checkBox.addSelectionListener(SelectionListener.widgetSelectedAdapter(event -> {
269+
var button = (Button) event.widget;
270+
toggleExDatePicker(exDate, button.getSelection());
271+
editArea.layout();
272+
}));
273+
}
274+
254275
// shares [- amount per share]
255276
forms.thenBelow(shares.value).width(amountWidth).label(shares.label).suffix(btnShares) //
256277
// fxAmount - exchange rate - amount
@@ -352,6 +373,22 @@ public void widgetSelected(SelectionEvent e)
352373
model.firePropertyChange(Properties.exchangeRateCurrencies.name(), "", model().getExchangeRateCurrencies()); //$NON-NLS-1$
353374
}
354375

376+
private void toggleExDatePicker(ExDateInput exDate, boolean enable)
377+
{
378+
exDate.checkBox.setSelection(enable);
379+
exDate.date.getControl().setVisible(enable);
380+
381+
if (enable)
382+
{
383+
model().setExDate(exDate.date.getSelection().atStartOfDay());
384+
}
385+
else
386+
{
387+
model().setExDate(null);
388+
exDate.date.setSelection(model.getDate());
389+
}
390+
}
391+
355392
private ComboInput setupSecurities(Composite editArea)
356393
{
357394
List<Security> activeSecurities = new ArrayList<>();

name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/transactions/AccountTransactionModel.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class AccountTransactionModel extends AbstractModel
3232
{
3333
public enum Properties
3434
{
35-
security, account, date, time, shares, fxGrossAmount, dividendAmount, exchangeRate, inverseExchangeRate, grossAmount, // NOSONAR
35+
security, account, date, exDate, time, shares, fxGrossAmount, dividendAmount, exchangeRate, inverseExchangeRate, grossAmount, // NOSONAR
3636
fxTaxes, taxes, fxFees, fees, total, note, exchangeRateCurrencies, inverseExchangeRateCurrencies, // NOSONAR
3737
accountCurrencyCode, securityCurrencyCode, fxCurrencyCode, calculationStatus; // NOSONAR
3838
}
@@ -52,6 +52,7 @@ public enum Properties
5252
private Account account;
5353
private LocalDate date = PresetValues.getLastTransactionDate();
5454
private LocalTime time = PresetValues.getTime();
55+
private LocalDateTime exDate;
5556
private long shares;
5657

5758
private long fxGrossAmount;
@@ -103,6 +104,9 @@ public void applyChanges()
103104
if (account == null)
104105
throw new UnsupportedOperationException(Messages.MsgMissingAccount);
105106

107+
if (exDate != null && (security == null || EMPTY_SECURITY.equals(security)))
108+
throw new UnsupportedOperationException(Messages.MsgExDateNotAllowed);
109+
106110
AccountTransaction t;
107111

108112
if (sourceTransaction != null && sourceAccount.equals(account))
@@ -122,14 +126,14 @@ public void applyChanges()
122126

123127
// preserve the source field from the original transaction
124128
t.setSource(sourceTransaction.getSource());
125-
t.setExDate(sourceTransaction.getExDate());
126129

127130
sourceTransaction = null;
128131
sourceAccount = null;
129132
}
130133
}
131134

132135
t.setDateTime(LocalDateTime.of(date, time));
136+
t.setExDate(exDate);
133137
t.setSecurity(!EMPTY_SECURITY.equals(security) ? security : null);
134138
t.setShares(supportsShares() ? shares : 0);
135139
t.setAmount(total);
@@ -182,6 +186,7 @@ public void resetToNewTransaction()
182186
setFxTaxes(0);
183187
setNote(null);
184188
setTime(PresetValues.getTime());
189+
setExDate(null);
185190
}
186191

187192
public boolean supportsShares()
@@ -234,6 +239,7 @@ public void presetFromSource(Account account, AccountTransaction transaction)
234239
LocalDateTime transactionDate = transaction.getDateTime();
235240
this.date = transactionDate.toLocalDate();
236241
this.time = transactionDate.toLocalTime();
242+
this.exDate = transaction.getExDate();
237243
this.shares = transaction.getShares();
238244
this.total = transaction.getAmount();
239245

@@ -292,6 +298,9 @@ public IStatus getCalculationStatus()
292298
*/
293299
private IStatus calculateStatus()
294300
{
301+
if (exDate != null && exDate.toLocalDate().isAfter(date))
302+
return ValidationStatus.error(Messages.MsgDateExIsAfterTheDate);
303+
295304
// check whether converted amount is in range
296305
long upper = Math.round(fxGrossAmount * exchangeRate.add(BigDecimal.valueOf(0.0001)).doubleValue());
297306
long lower = Math.round(fxGrossAmount * exchangeRate.add(BigDecimal.valueOf(-0.0001)).doubleValue());
@@ -449,6 +458,19 @@ public void setTime(LocalTime time)
449458
firePropertyChange(Properties.time.name(), this.time, this.time = time); // NOSONAR
450459
}
451460

461+
public LocalDateTime getExDate()
462+
{
463+
return exDate;
464+
}
465+
466+
public void setExDate(LocalDateTime exDate)
467+
{
468+
firePropertyChange(Properties.exDate.name(), this.exDate, this.exDate = exDate); // NOSONAR
469+
470+
firePropertyChange(Properties.calculationStatus.name(), this.calculationStatus,
471+
this.calculationStatus = calculateStatus()); // NOSONAR
472+
}
473+
452474
public long getShares()
453475
{
454476
return shares;

name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,6 +2182,8 @@ MsgCreateTransactionsAutomaticallyUponOpening = Create transactions automaticall
21822182

21832183
MsgDateIsInTheFuture = Attention: the date of this transaction is in the future.
21842184

2185+
MsgDateExIsAfterTheDate = Attention: the Ex date is newer than the transaction date.
2186+
21852187
MsgDeletionNotPossible = Deletion not possible
21862188

21872189
MsgDeletionNotPossibleDetail = Securities with associated transactions cannot be deleted.\nThe securities are needed to calculate the historical performance.\n\nSecurities with transactions: {0}.
@@ -2194,6 +2196,8 @@ MsgDialogNotAValidISIN = Invalid ISIN: {0}
21942196

21952197
MsgEmbeddedBrowserError = Unable to create embedded browser to display charts\n\n* Are you running the latest version of your browser?\n* On Linux one has to install the packet ''libwebkitgtk-3.0-0''\n\nThe error message\n "No more handles [Unknown Mozilla path (MOZILLA_FIVE_HOME not set)]"\nindicates that libwebkitgtk has to be installed. On Ubuntu 18.04, run\n sudo apt-get install libwebkitgtk-3.0-0\n\n\nDetails:\n{0}
21962198

2199+
MsgExDateNotAllowed = Ex-Date is only allowed for dividend transactions.
2200+
21972201
MsgErrorAccountNotExist = There is no account.\nPlease create a new account.
21982202

21992203
MsgErrorConvertToBuySellCurrencyMismatch = Transaction with currency {0} cannot be booked on account ''{2}'' with the currency {1}.

name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_de.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,6 +2175,8 @@ MsgCreateTransactionsAutomaticallyUponOpening = Buchungen automatisch nach dem \
21752175

21762176
MsgDateIsInTheFuture = Achtung: Das Datum dieser Buchung liegt in der Zukunft.
21772177

2178+
MsgDateExIsAfterTheDate = Achtung: Der Ex-Tag darf nicht neuer sein als das Transaktionsdatum.
2179+
21782180
MsgDeletionNotPossible = L\u00F6schung nicht m\u00F6glich
21792181

21802182
MsgDeletionNotPossibleDetail = Wertpapiere mit Ums\u00E4tzen k\u00F6nnen nicht gel\u00F6scht werden.\nDie Wertpapiere werden zur Berechnung der historischen Performance ben\u00F6tigt.\n\nWertpapiere mit Ums\u00E4tzen: {0}.
@@ -2187,6 +2189,8 @@ MsgDialogNotAValidISIN = Ung\u00FCltige ISIN: {0}
21872189

21882190
MsgEmbeddedBrowserError = Fehler beim \u00D6ffnen des Web Browsers zur Darstellung der Grafiken\n\n* Aktuellste Version des Browsers installiert?\n* Unter Linux muss das Paket ''libwebkitgtk-3.0-0'' installiert sein.\n\nDer Fehlertext\n "No more handles [Unknown Mozilla path (MOZILLA_FIVE_HOME not set)]"\nhei\u00DFt meistens, dass libwebkitgtk installiert werden muss. Unter Ubuntu 18.04 z. B. mit\n sudo apt-get install libwebkitgtk-3.0-0\n\n\nDetails:\n{0}
21892191

2192+
MsgExDateNotAllowed = Ex-Datum ist nur f\u00FCr Dividenden-Transaktionen erlaubt.
2193+
21902194
MsgErrorAccountNotExist = Es ist kein Konto vorhanden.\nBitte erstellen Sie ein Konto.
21912195

21922196
MsgErrorConvertToBuySellCurrencyMismatch = Buchung in der W\u00E4hrung {0} kann nicht auf Konto ''{2}'' mit der W\u00E4hrung {1} gebucht werden.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package name.abuchen.portfolio.ui.util;
2+
3+
import java.time.LocalDate;
4+
import java.time.LocalDateTime;
5+
6+
import org.eclipse.core.databinding.conversion.IConverter;
7+
8+
public class DateTimeToDateConverter implements IConverter<LocalDateTime, LocalDate>
9+
{
10+
11+
@Override
12+
public Object getFromType()
13+
{
14+
return LocalDateTime.class;
15+
}
16+
17+
@Override
18+
public Object getToType()
19+
{
20+
return LocalDate.class;
21+
}
22+
23+
@Override
24+
public LocalDate convert(LocalDateTime fromObject)
25+
{
26+
return fromObject != null ? fromObject.toLocalDate() : null;
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package name.abuchen.portfolio.ui.util;
2+
3+
import java.time.LocalDate;
4+
import java.time.LocalDateTime;
5+
6+
import org.eclipse.core.databinding.conversion.IConverter;
7+
8+
public class DateToDateTimeConverter implements IConverter<LocalDate, LocalDateTime>
9+
{
10+
11+
@Override
12+
public Object getFromType()
13+
{
14+
return LocalDate.class;
15+
}
16+
17+
@Override
18+
public Object getToType()
19+
{
20+
return LocalDateTime.class;
21+
}
22+
23+
@Override
24+
public LocalDateTime convert(LocalDate fromObject)
25+
{
26+
return fromObject != null ? fromObject.atStartOfDay() : null;
27+
}
28+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package name.abuchen.portfolio.ui.util;
2+
3+
import java.time.LocalDate;
4+
import java.time.LocalDateTime;
5+
import java.time.ZoneId;
6+
import java.util.Date;
7+
8+
import org.eclipse.nebula.widgets.cdatetime.CDateTime;
9+
import org.eclipse.swt.widgets.Control;
10+
import org.eclipse.swt.widgets.DateTime;
11+
12+
public class NullableDateTimeDateSelectionProperty extends SimpleDateTimeDateSelectionProperty
13+
{
14+
@Override
15+
protected LocalDate doGetValue(Control source)
16+
{
17+
if (source instanceof CDateTime dateTime)
18+
{
19+
var date = dateTime.getSelection();
20+
return date == null ? null
21+
: LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalDate();
22+
}
23+
24+
return super.doGetValue(source);
25+
}
26+
27+
@Override
28+
protected void doSetValue(Control source, LocalDate date)
29+
{
30+
if (source instanceof DateTime dateTime)
31+
{
32+
if (date == null)
33+
{
34+
// DateTime does not support null
35+
var now = LocalDate.now();
36+
dateTime.setDate(now.getYear(), now.getMonthValue() - 1, now.getDayOfMonth());
37+
}
38+
else
39+
{
40+
// DateTime widget has zero-based months
41+
dateTime.setDate(date.getYear(), date.getMonthValue() - 1, date.getDayOfMonth());
42+
}
43+
}
44+
else if (source instanceof CDateTime dateTime)
45+
{
46+
dateTime.setSelection(
47+
date != null ? Date.from(date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())
48+
: null);
49+
}
50+
else
51+
{
52+
throw new UnsupportedOperationException();
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)