Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public void testDuplicateDetection4AccountTransaction() throws IntrospectionExce
{
DetectDuplicatesAction action = new DetectDuplicatesAction(new Client());

new PropertyChecker<AccountTransaction>(
AccountTransaction.class, "note", "source", "forex", "monetaryAmount", "updatedAt")
new PropertyChecker<AccountTransaction>(AccountTransaction.class, "note", "source", "forex", "monetaryAmount",
"updatedAt", "originalTransaction")
.before((name, o, c) -> assertThat(name,
action.process(o, account(c)).getCode(), is(Code.WARNING)))
.after((name, o, c) -> assertThat(name, action.process(o, account(c)).getCode(),
Expand All @@ -52,7 +52,7 @@ public void testDuplicateDetection4PortfolioTransaction()
DetectDuplicatesAction action = new DetectDuplicatesAction(new Client());

new PropertyChecker<PortfolioTransaction>(PortfolioTransaction.class, "fees", "taxes", "note", "source",
"forex", "monetaryAmount", "updatedAt") //
"forex", "monetaryAmount", "updatedAt", "originalTransaction") //
.before((name, o, c) -> assertThat(name,
action.process(o, portfolio(c)).getCode(), is(Code.WARNING)))
.after((name, o, c) -> assertThat(name,
Expand All @@ -68,7 +68,7 @@ public void testDuplicateDetectionWithPurchaseAndDeliveryPairs()
DetectDuplicatesAction action = new DetectDuplicatesAction(new Client());

new PropertyChecker<PortfolioTransaction>(PortfolioTransaction.class, "type", "fees", "taxes", "note", "source",
"forex", "monetaryAmount", "updatedAt") //
"forex", "monetaryAmount", "updatedAt", "originalTransaction") //
.before((name, o, c) -> {
o.setType(PortfolioTransaction.Type.BUY);
c.setType(PortfolioTransaction.Type.DELIVERY_INBOUND);
Expand All @@ -80,7 +80,7 @@ public void testDuplicateDetectionWithPurchaseAndDeliveryPairs()
.run();

new PropertyChecker<PortfolioTransaction>(PortfolioTransaction.class, "type", "fees", "taxes", "note", "source",
"forex", "monetaryAmount", "updatedAt") //
"forex", "monetaryAmount", "updatedAt", "originalTransaction") //
.before((name, o, c) -> {
o.setType(PortfolioTransaction.Type.SELL);
c.setType(PortfolioTransaction.Type.DELIVERY_OUTBOUND);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ public void testPortfolioTransactionAttributes() throws IntrospectionException
assertThat(properties, hasItem("note"));
assertThat(properties, hasItem("source"));
assertThat(properties, hasItem("updatedAt"));
assertThat(properties, hasItem("originalTransaction"));

assertThat(properties.size(), is(10));
assertThat(properties.size(), is(11));
}
}
Binary file added name.abuchen.portfolio.ui/icons/calculator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added name.abuchen.portfolio.ui/icons/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ public enum Images

ARROW_FORWARD("arrow_forward.png"), //$NON-NLS-1$
ARROW_BACK("arrow_back.png"), //$NON-NLS-1$

CALCULATOR("calculator.png"), //$NON-NLS-1$

// 3rd party logos

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
Expand All @@ -18,6 +19,7 @@
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.TransactionPair;
import name.abuchen.portfolio.ui.Messages;
import name.abuchen.portfolio.ui.dialogs.transactions.AccountTransactionDialog;
Expand All @@ -44,38 +46,51 @@ public TransactionContextMenu(AbstractFinanceView owner)

public void menuAboutToShow(IMenuManager manager, boolean fullContextMenu, IStructuredSelection selection)
{
if (selection.isEmpty() && fullContextMenu)
if (selection.isEmpty())
{
new SecurityContextMenu(owner).menuAboutToShow(manager, null, null);
if (fullContextMenu)
new SecurityContextMenu(owner).menuAboutToShow(manager, null, null);

return;
}

if (selection.size() == 1)
// convert the selection to a list of unwrapped transaction pairs to
// ensure that any downstream action does not work on the filtered
// client

// editing, copying, deletion is only possible, if the transaction has
// not been created for calculation purposes

@SuppressWarnings("unchecked")
var txs = selection.stream().map(o -> ((TransactionPair<Transaction>) o).unwrap()).toList();

if (txs.size() == 1)
{
TransactionPair<?> tx = (TransactionPair<?>) selection.getFirstElement();
var tx = txs.getFirst();

tx.withAccountTransaction().ifPresent(t -> fillContextMenuAccountTx(manager, fullContextMenu, t));
tx.withPortfolioTransaction().ifPresent(t -> fillContextMenuPortfolioTx(manager, fullContextMenu, t));

manager.add(new Separator());
}

if (!selection.isEmpty())
if (fullContextMenu)
{
if (fullContextMenu)
{
fillContextMenuAccountTxList(manager, selection);
fillContextMenuPortfolioTxList(manager, selection);
}
fillContextMenuAccountTxList(manager, txs);
fillContextMenuPortfolioTxList(manager, txs);
}

manager.add(new Separator());
manager.add(new Separator());

manager.add(new SimpleAction(Messages.MenuTransactionDelete, a -> {
for (Object tx : selection.toArray())
((TransactionPair<?>) tx).deleteTransaction(owner.getClient());
var deletableTx = txs.stream().filter(tx -> tx.getTransaction().getOriginalTransaction() == null).toList();

owner.markDirty();
}));
}
var deleteAction = new SimpleAction(Messages.MenuTransactionDelete, a -> {
for (var tx : deletableTx)
tx.deleteTransaction(owner.getClient());
owner.markDirty();
});
deleteAction.setEnabled(!deletableTx.isEmpty());
manager.add(deleteAction);
}

public void handleEditKey(KeyEvent e, IStructuredSelection selection)
Expand All @@ -85,7 +100,12 @@ public void handleEditKey(KeyEvent e, IStructuredSelection selection)
if (selection.isEmpty())
return;

TransactionPair<?> tx = (TransactionPair<?>) selection.getFirstElement();
TransactionPair<?> tx = ((TransactionPair<?>) selection.getFirstElement()).unwrap();

// do not edit derived transactions
if (tx.getTransaction().getOriginalTransaction() != null)
return;

tx.withAccountTransaction().ifPresent(t -> createEditAccountTransactionAction(t).run());
tx.withPortfolioTransaction().ifPresent(t -> createEditPortfolioTransactionAction(t).run());
}
Expand All @@ -94,24 +114,29 @@ public void handleEditKey(KeyEvent e, IStructuredSelection selection)
if (selection.isEmpty())
return;

TransactionPair<?> tx = (TransactionPair<?>) selection.getFirstElement();
TransactionPair<?> tx = ((TransactionPair<?>) selection.getFirstElement()).unwrap();

// do not edit derived transactions
if (tx.getTransaction().getOriginalTransaction() != null)
return;

tx.withAccountTransaction().ifPresent(t -> createCopyAccountTransactionAction(t).run());
tx.withPortfolioTransaction().ifPresent(t -> createCopyPortfolioTransactionAction(t).run());
}
}

private void fillContextMenuAccountTxList(IMenuManager manager, IStructuredSelection selection)
private void fillContextMenuAccountTxList(IMenuManager manager, List<TransactionPair<Transaction>> selection)
{
@SuppressWarnings("unchecked")
var accountTxPairs = selection.stream() //
.filter(p -> ((TransactionPair<?>) p).getTransaction().getOriginalTransaction() == null)
.filter(p -> ((TransactionPair<?>) p).isAccountTransaction())
.map(p -> ((TransactionPair<AccountTransaction>) p)) //
.map(p -> p.withAccountTransaction().get()) //
.toList();

if (accountTxPairs.size() != selection.size())
return;

var transfers = accountTxPairs.stream()
var transfers = accountTxPairs.stream().filter(p -> p.getTransaction().getOriginalTransaction() == null)
.filter(p -> p.getTransaction().getType() == AccountTransaction.Type.TRANSFER_IN
|| p.getTransaction().getType() == AccountTransaction.Type.TRANSFER_OUT)
.map(p -> p.getTransaction()).toList();
Expand All @@ -121,7 +146,7 @@ private void fillContextMenuAccountTxList(IMenuManager manager, IStructuredSelec
manager.add(new ConvertTransferToDepositRemovalAction(owner.getClient(), transfers));
}

var dividends = accountTxPairs.stream()
var dividends = accountTxPairs.stream().filter(p -> p.getTransaction().getOriginalTransaction() == null)
.filter(p -> p.getTransaction().getType() == AccountTransaction.Type.DIVIDENDS).toList();

if (dividends.size() == accountTxPairs.size())
Expand All @@ -130,14 +155,15 @@ private void fillContextMenuAccountTxList(IMenuManager manager, IStructuredSelec
}
}

private void fillContextMenuPortfolioTxList(IMenuManager manager, IStructuredSelection selection)
private void fillContextMenuPortfolioTxList(IMenuManager manager, List<TransactionPair<Transaction>> selection)
{
Collection<TransactionPair<PortfolioTransaction>> txCollection = new ArrayList<>(selection.size());
Iterator<?> it = selection.iterator();
while (it.hasNext())
{
TransactionPair<?> foo = (TransactionPair<?>) it.next();
foo.withPortfolioTransaction().ifPresent(txCollection::add);
if (foo.getTransaction().getOriginalTransaction() == null)
foo.withPortfolioTransaction().ifPresent(txCollection::add);
}

if (txCollection.size() != selection.size())
Expand Down Expand Up @@ -173,10 +199,12 @@ private void fillContextMenuAccountTx(IMenuManager manager, boolean fullContextM
{
Action action = createEditAccountTransactionAction(tx);
action.setAccelerator(SWT.MOD1 | 'E');
action.setEnabled(tx.getTransaction().getOriginalTransaction() == null);
manager.add(action);

Action duplicateAction = createCopyAccountTransactionAction(tx);
duplicateAction.setAccelerator(SWT.MOD1 | 'D');
duplicateAction.setEnabled(tx.getTransaction().getOriginalTransaction() == null);
manager.add(duplicateAction);

if (fullContextMenu)
Expand All @@ -194,10 +222,12 @@ private void fillContextMenuPortfolioTx(IMenuManager manager, boolean fullContex

Action editAction = createEditPortfolioTransactionAction(tx);
editAction.setAccelerator(SWT.MOD1 | 'E');
editAction.setEnabled(ptx.getOriginalTransaction() == null);
manager.add(editAction);

Action duplicateAction = createCopyPortfolioTransactionAction(tx);
duplicateAction.setAccelerator(SWT.MOD1 | 'D');
duplicateAction.setEnabled(ptx.getOriginalTransaction() == null);
manager.add(duplicateAction);

manager.add(new Separator());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,18 +263,32 @@ public Color getBackground(Object element)
});

ColumnViewerSorter.create(TransactionPair.BY_DATE).attachTo(column, SWT.DOWN);
new DateTimeEditingSupport(Transaction.class, "dateTime").addListener(this).attachTo(column); //$NON-NLS-1$
new DateTimeEditingSupport(Transaction.class, "dateTime") //$NON-NLS-1$
.setCanEditCheck(
t -> ((TransactionPair<?>) t).getTransaction().getOriginalTransaction() == null)
.addListener(this).attachTo(column);
support.addColumn(column);

column = new Column("1", Messages.ColumnTransactionType, SWT.None, 80); //$NON-NLS-1$
column.setLabelProvider(new TransactionLabelProvider(t -> {
var typeLabelProvider = new TransactionLabelProvider(t -> {
if (t instanceof PortfolioTransaction pt)
return pt.getType().toString();
else if (t instanceof AccountTransaction at)
return at.getType().toString();
else
return null;
}));
})
{
@Override
public Image getImage(Object element)
{
return element instanceof TransactionPair<?> pair
&& pair.getTransaction().getOriginalTransaction() != null ? Images.CALCULATOR.image()
: null;
}
};

column = new Column("1", Messages.ColumnTransactionType, SWT.None, 80); //$NON-NLS-1$
column.setLabelProvider(typeLabelProvider);
ColumnViewerSorter.createIgnoreCase(e -> {
Transaction t = ((TransactionPair<?>) e).getTransaction();
if (t instanceof PortfolioTransaction pt)
Expand All @@ -284,7 +298,16 @@ else if (t instanceof AccountTransaction at)
else
return null;
}).attachTo(column);
new TransactionTypeEditingSupport(owner.getClient()).addListener(this).attachTo(column);
new TransactionTypeEditingSupport(owner.getClient())
{
@Override
public boolean canEdit(Object element)
{
if (((TransactionPair<?>) element).getTransaction().getOriginalTransaction() != null)
return false;
return super.canEdit(element);
}
}.addListener(this).attachTo(column);
support.addColumn(column);

column = new Column("2", Messages.ColumnSecurity, SWT.None, 250); //$NON-NLS-1$
Expand Down Expand Up @@ -339,12 +362,14 @@ public Color getBackground(Object element)
});
ColumnViewerSorter.create(e -> ((TransactionPair<?>) e).getTransaction().getShares()).attachTo(column);
new ValueEditingSupport(Transaction.class, "shares", Values.Share) //$NON-NLS-1$
.setCanEditCheck(e -> ((TransactionPair<?>) e).getTransaction() instanceof PortfolioTransaction
|| (((TransactionPair<?>) e).getTransaction() instanceof AccountTransaction
&& ((AccountTransaction) ((TransactionPair<?>) e)
.getTransaction())
.getType() == AccountTransaction.Type.DIVIDENDS))
.addListener(this).attachTo(column);
.setCanEditCheck(e -> {
var tx = (TransactionPair<?>) e;
if (tx.getTransaction().getOriginalTransaction() != null)
return false;
return tx.isPortfolioTransaction() || tx.withAccountTransaction().map(
ta -> ta.getTransaction().getType() == AccountTransaction.Type.DIVIDENDS)
.orElse(false);
}).addListener(this).attachTo(column);
support.addColumn(column);

column = new Column("4", Messages.ColumnQuote, SWT.RIGHT, 80); //$NON-NLS-1$
Expand Down Expand Up @@ -408,7 +433,15 @@ public String getText(Object element)
}
});
new TransactionOwnerListEditingSupport(owner.getClient(), TransactionOwnerListEditingSupport.EditMode.OWNER)
.addListener(this).attachTo(column);
{
@Override
public boolean canEdit(Object element)
{
if (((TransactionPair<?>) element).getTransaction().getOriginalTransaction() != null)
return false;
return super.canEdit(element);
}
}.addListener(this).attachTo(column);
ColumnViewerSorter.createIgnoreCase(e -> ((TransactionPair<?>) e).getOwner().toString()).attachTo(column);
support.addColumn(column);

Expand All @@ -419,7 +452,16 @@ public String getText(Object element)
? t.getTransaction().getCrossEntry().getCrossOwner(t.getTransaction())
: null));
new TransactionOwnerListEditingSupport(owner.getClient(),
TransactionOwnerListEditingSupport.EditMode.CROSSOWNER).addListener(this).attachTo(column);
TransactionOwnerListEditingSupport.EditMode.CROSSOWNER)
{
@Override
public boolean canEdit(Object element)
{
if (((TransactionPair<?>) element).getTransaction().getOriginalTransaction() != null)
return false;
return super.canEdit(element);
}
}.addListener(this).attachTo(column);
ColumnViewerSorter.createIgnoreCase(e -> {
Transaction t = ((TransactionPair<?>) e).getTransaction();
return t.getCrossEntry() != null ? t.getCrossEntry().getCrossOwner(t).toString() : null;
Expand Down Expand Up @@ -456,7 +498,9 @@ public String getToolTipText(Object e)
}
});
ColumnViewerSorter.createIgnoreCase(e -> ((TransactionPair<?>) e).getTransaction().getNote()).attachTo(column); // $NON-NLS-1$
new StringEditingSupport(Transaction.class, "note").addListener(this).attachTo(column); //$NON-NLS-1$
new StringEditingSupport(Transaction.class, "note") //$NON-NLS-1$
.setCanEditCheck(t -> ((Transaction) t).getOriginalTransaction() == null).addListener(this)
.addListener(this).attachTo(column);
support.addColumn(column);

column = new Column("source", Messages.ColumnSource, SWT.None, 200); //$NON-NLS-1$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.List;

import jakarta.inject.Inject;
import jakarta.inject.Named;

import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
Expand All @@ -25,6 +26,7 @@
import name.abuchen.portfolio.model.TransactionPair;
import name.abuchen.portfolio.ui.Images;
import name.abuchen.portfolio.ui.Messages;
import name.abuchen.portfolio.ui.UIConstants;
import name.abuchen.portfolio.ui.editor.AbstractFinanceView;
import name.abuchen.portfolio.ui.util.DropDown;
import name.abuchen.portfolio.ui.util.SimpleAction;
Expand All @@ -37,6 +39,7 @@ public class TransactionsPane implements InformationPanePage
{

@Inject
@Named(UIConstants.Context.ACTIVE_CLIENT)
private Client client;

@Inject
Expand Down
Loading