diff --git a/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle.properties b/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle.properties
index 25132548fc..1bd219005a 100644
--- a/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle.properties
+++ b/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle.properties
@@ -5,6 +5,7 @@ Bundle-Vendor = Andreas Buchen
category.name = Portfolio Performance
+command.consistencyChecks.name = Check consistency...
command.export.name = Export...
command.import.name = Import...
command.newFile.mnemonic = N
diff --git a/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle_de.properties b/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle_de.properties
index 5dcfbd8697..6bfc307eaa 100644
--- a/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle_de.properties
+++ b/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle_de.properties
@@ -5,6 +5,7 @@ Bundle-Vendor = Andreas Buchen
category.name = Portfolio Performance
+command.consistencyChecks.name = Auf Konsistenz pr\u00FCfen...
command.export.name = Exportieren...
command.import.name = Importieren...
command.newFile.mnemonic = N
diff --git a/name.abuchen.portfolio.ui/icons/check.gif b/name.abuchen.portfolio.ui/icons/check.gif
new file mode 100644
index 0000000000..d86b18c167
Binary files /dev/null and b/name.abuchen.portfolio.ui/icons/check.gif differ
diff --git a/name.abuchen.portfolio.ui/icons/quickfix.gif b/name.abuchen.portfolio.ui/icons/quickfix.gif
new file mode 100644
index 0000000000..542dddca3f
Binary files /dev/null and b/name.abuchen.portfolio.ui/icons/quickfix.gif differ
diff --git a/name.abuchen.portfolio.ui/plugin.xml b/name.abuchen.portfolio.ui/plugin.xml
index 99540f57c3..167fec0546 100644
--- a/name.abuchen.portfolio.ui/plugin.xml
+++ b/name.abuchen.portfolio.ui/plugin.xml
@@ -111,6 +111,14 @@
id="org.eclipse.ui.file.save"
commandId="org.eclipse.ui.file.saveAs">
+
+
+
+
@@ -242,6 +250,11 @@
id="name.abuchen.portfolio.ui.commands.updateCommand"
name="%command.update.name">
+
+
+
+
+
+
+
+
+
issues = Checker.runAll(client);
+
+ if (issues.isEmpty())
+ {
+ if (reportSuccess)
+ {
+ Display.getDefault().asyncExec(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ MessageDialog.openInformation(Display.getCurrent().getActiveShell(), Messages.LabelInfo,
+ Messages.MsgNoIssuesFound);
+ }
+ });
+ }
+ }
+ else
+ {
+ Display.getDefault().asyncExec(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ SelectQuickFixDialog dialog = new SelectQuickFixDialog(Display.getCurrent().getActiveShell(),
+ issues);
+ dialog.open();
+
+ for (ReportedIssue issue : dialog.issues)
+ {
+ if (issue.isFixed())
+ {
+ editor.markDirty();
+ break;
+ }
+ }
+ }
+ });
+
+ }
+
+ return Status.OK_STATUS;
+ }
+
+ private static class SelectQuickFixDialog extends TitleAreaDialog
+ {
+ private final List issues;
+ private Menu contextMenu;
+ private TableViewer tableViewer;
+
+ private ReportedIssue currentIssue;
+
+ public SelectQuickFixDialog(Shell shell, List issues)
+ {
+ super(shell);
+
+ this.issues = new ArrayList();
+ for (Issue issue : issues)
+ this.issues.add(new ReportedIssue(issue));
+ }
+
+ @Override
+ protected void setShellStyle(int newShellStyle)
+ {
+ super.setShellStyle(newShellStyle | SWT.RESIZE);
+ }
+
+ protected void createButtonsForButtonBar(Composite parent)
+ {
+ createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
+ }
+
+ @Override
+ protected Control createContents(Composite parent)
+ {
+ Control contents = super.createContents(parent);
+ setTitle(Messages.DialogConsistencyChecksTitle);
+ setMessage(Messages.DialogConssitencyChecksMessage);
+ return contents;
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent)
+ {
+ Composite composite = (Composite) super.createDialogArea(parent);
+
+ composite.addDisposeListener(new DisposeListener()
+ {
+ @Override
+ public void widgetDisposed(DisposeEvent e)
+ {
+ SelectQuickFixDialog.this.widgetDisposed(e);
+ }
+ });
+
+ Composite tableArea = new Composite(composite, SWT.NONE);
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(tableArea);
+ tableArea.setLayout(new FillLayout());
+
+ TableColumnLayout layout = new TableColumnLayout();
+ tableArea.setLayout(layout);
+
+ tableViewer = new TableViewer(tableArea, SWT.BORDER | SWT.FULL_SELECTION);
+ final Table table = tableViewer.getTable();
+ table.setHeaderVisible(true);
+ table.setLinesVisible(true);
+
+ TableViewerColumn col = new TableViewerColumn(tableViewer, SWT.NONE);
+ col.getColumn().setText(Messages.ColumnDate);
+ col.setLabelProvider(new ColumnLabelProvider()
+ {
+ @Override
+ public String getText(Object element)
+ {
+ return Values.Date.format(((ReportedIssue) element).getDate());
+ }
+ });
+ layout.setColumnData(col.getColumn(), new ColumnPixelData(80));
+ ColumnViewerSorter.create(ReportedIssue.class, "date").attachTo(tableViewer, col, true); //$NON-NLS-1$
+
+ col = new TableViewerColumn(tableViewer, SWT.NONE);
+ col.getColumn().setText(Messages.ColumnEntity);
+ col.setLabelProvider(new ColumnLabelProvider()
+ {
+ @Override
+ public String getText(Object element)
+ {
+ return ((ReportedIssue) element).getEntity().toString();
+ }
+
+ @Override
+ public Image getImage(Object element)
+ {
+ ReportedIssue issue = (ReportedIssue) element;
+ if (issue.getEntity() instanceof Account)
+ return PortfolioPlugin.image(PortfolioPlugin.IMG_ACCOUNT);
+ else if (issue.getEntity() instanceof Portfolio)
+ return PortfolioPlugin.image(PortfolioPlugin.IMG_PORTFOLIO);
+ else
+ return null;
+ }
+ });
+ layout.setColumnData(col.getColumn(), new ColumnPixelData(100));
+ ColumnViewerSorter.create(ReportedIssue.class, "entity").attachTo(tableViewer, col); //$NON-NLS-1$
+
+ col = new TableViewerColumn(tableViewer, SWT.RIGHT);
+ col.getColumn().setText(Messages.ColumnAmount);
+ col.setLabelProvider(new ColumnLabelProvider()
+ {
+ @Override
+ public String getText(Object element)
+ {
+ Long amount = ((ReportedIssue) element).getAmount();
+ return amount != null ? Values.Amount.format(amount) : null;
+ }
+ });
+ layout.setColumnData(col.getColumn(), new ColumnPixelData(80));
+ ColumnViewerSorter.create(ReportedIssue.class, "amount").attachTo(tableViewer, col); //$NON-NLS-1$
+
+ col = new TableViewerColumn(tableViewer, SWT.NONE);
+ col.getColumn().setText(Messages.ColumnIssue);
+ col.setLabelProvider(new OwnerDrawLabelProvider()
+ {
+ protected void measure(Event event, Object element)
+ {
+ ReportedIssue line = (ReportedIssue) element;
+ Point size = event.gc.textExtent(line.getLabel());
+ event.width = table.getColumn(event.index).getWidth();
+ event.width = size.x + 1;
+ event.height = size.y;
+ }
+
+ protected void paint(Event event, Object element)
+ {
+ ReportedIssue entry = (ReportedIssue) element;
+ event.gc.drawText(entry.getLabel(), event.x, event.y, true);
+ }
+ });
+ layout.setColumnData(col.getColumn(), new ColumnPixelData(300));
+ ColumnViewerSorter.create(ReportedIssue.class, "label").attachTo(tableViewer, col); //$NON-NLS-1$
+
+ col = new TableViewerColumn(tableViewer, SWT.NONE);
+ col.getColumn().setText(Messages.ColumnFix);
+ col.setLabelProvider(new ColumnLabelProvider()
+ {
+ @Override
+ public String getText(Object element)
+ {
+ ReportedIssue issue = (ReportedIssue) element;
+ return issue.isFixed() ? issue.getFixedMessage() : null;
+ }
+
+ @Override
+ public Image getImage(Object element)
+ {
+ ReportedIssue issue = (ReportedIssue) element;
+
+ return PortfolioPlugin.image(issue.isFixed() ? PortfolioPlugin.IMG_CHECK
+ : PortfolioPlugin.IMG_QUICKFIX);
+ }
+ });
+ layout.setColumnData(col.getColumn(), new ColumnPixelData(100));
+
+ table.addMouseListener(new MouseAdapter()
+ {
+ public void mouseDown(MouseEvent e)
+ {
+ Point point = new Point(e.x, e.y);
+ TableItem item = table.getItem(point);
+ if (item != null && item.getBounds(4).contains(point))
+ {
+ ReportedIssue issue = (ReportedIssue) item.getData();
+ if (!issue.isFixed())
+ showContextMenu(issue);
+ }
+ }
+ });
+
+ tableViewer.setContentProvider(new ArrayContentProvider());
+ tableViewer.setInput(issues);
+
+ return composite;
+ }
+
+ protected void widgetDisposed(DisposeEvent e)
+ {
+ if (contextMenu != null)
+ contextMenu.dispose();
+ }
+
+ private void showContextMenu(ReportedIssue issue)
+ {
+ this.currentIssue = issue;
+ if (contextMenu == null)
+ {
+ MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
+ menuMgr.setRemoveAllWhenShown(true);
+ menuMgr.addMenuListener(new IMenuListener()
+ {
+ @Override
+ public void menuAboutToShow(IMenuManager manager)
+ {
+ for (final QuickFix fix : currentIssue.getAvailableFixes())
+ {
+ manager.add(new Action(fix.getLabel())
+ {
+ @Override
+ public void run()
+ {
+ fix.execute();
+ currentIssue.setFixedMessage(fix.getDoneLabel());
+ tableViewer.refresh(currentIssue);
+ }
+ });
+ }
+ }
+ });
+
+ contextMenu = menuMgr.createContextMenu(getShell());
+ }
+
+ contextMenu.setVisible(true);
+ }
+ }
+
+ public static class ReportedIssue
+ {
+ private Issue delegate;
+ private List fixes;
+ private String fixedMessage;
+
+ public ReportedIssue(Issue delegate)
+ {
+ this.delegate = delegate;
+ this.fixes = delegate.getAvailableFixes();
+ }
+
+ public Date getDate()
+ {
+ return delegate.getDate();
+ }
+
+ public Object getEntity()
+ {
+ return delegate.getEntity();
+ }
+
+ public Long getAmount()
+ {
+ return delegate.getAmount();
+ }
+
+ public String getLabel()
+ {
+ return delegate.getLabel();
+ }
+
+ public List getAvailableFixes()
+ {
+ return fixes;
+ }
+
+ public void setFixedMessage(String message)
+ {
+ this.fixedMessage = message;
+ }
+
+ protected String getFixedMessage()
+ {
+ return fixedMessage;
+ }
+
+ public boolean isFixed()
+ {
+ return fixedMessage != null;
+ }
+ }
+}
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 d27c3e0419..6035f5a015 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
@@ -39,12 +39,15 @@ public class Messages extends NLS
public static String ColumnDelta;
public static String ColumnDeltaPercent;
public static String ColumnDeltaValue;
+ public static String ColumnEntity;
public static String ColumnFees;
+ public static String ColumnFix;
public static String ColumnIndex;
public static String ColumnIRR;
public static String ColumnIRRPerformance;
public static String ColumnIRRPerformanceOption;
public static String ColumnISIN;
+ public static String ColumnIssue;
public static String ColumnLable;
public static String ColumnLastTrade;
public static String ColumnLatest;
@@ -104,6 +107,8 @@ public class Messages extends NLS
public static String CSVImportWizardDescription;
public static String CSVImportWizardTitle;
public static String CurrencyConverter_MsgNotANumber;
+ public static String DialogConsistencyChecksTitle;
+ public static String DialogConssitencyChecksMessage;
public static String DialogTitleCounterTransactionNotFound;
public static String DialogTitleMultipleCounterTransactions;
public static String EditWizardIndustryClassificationDescription;
@@ -150,6 +155,7 @@ public class Messages extends NLS
public static String JobMsgErrorUpdatingIndeces;
public static String JobMsgErrorUpdatingQuotes;
public static String JobMsgLoadingExchanges;
+ public static String JobMsgRunningConsistencyChecks;
public static String JobMsgSamplingHistoricalQuotes;
public static String LabelAccounts;
public static String LabelAggregationDaily;
@@ -231,6 +237,7 @@ public class Messages extends NLS
public static String MsgMissingAccount;
public static String MsgMissingReferenceAccount;
public static String MsgMissingSecurity;
+ public static String MsgNoIssuesFound;
public static String MsgNoProfileFound;
public static String MsgNoSecuritiesMaintained;
public static String MsgNoUpdatesAvailable;
diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/PortfolioPlugin.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/PortfolioPlugin.java
index 4e007e49df..ff4902193b 100644
--- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/PortfolioPlugin.java
+++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/PortfolioPlugin.java
@@ -88,6 +88,9 @@ public interface Preferences
public static final String IMG_VIEW_TREEMAP = "view_treemap"; //$NON-NLS-1$
public static final String IMG_VIEW_PIECHART = "view_piechart"; //$NON-NLS-1$
+ public static final String IMG_CHECK = "check"; //$NON-NLS-1$
+ public static final String IMG_QUICKFIX = "quickfix"; //$NON-NLS-1$
+
private static PortfolioPlugin instance;
public PortfolioPlugin()
@@ -116,7 +119,8 @@ protected void initializeImageRegistry(ImageRegistry registry)
Bundle bundle = Platform.getBundle(PLUGIN_ID);
for (String key : new String[] { IMG_LOGO, IMG_ACCOUNT, IMG_PORTFOLIO, IMG_SECURITY, IMG_WATCHLIST, IMG_PLUS,
- IMG_CONFIG, IMG_EXPORT, IMG_VIEW_TABLE, IMG_VIEW_TREEMAP, IMG_VIEW_PIECHART })
+ IMG_CONFIG, IMG_EXPORT, IMG_VIEW_TABLE, IMG_VIEW_TREEMAP, IMG_VIEW_PIECHART, IMG_CHECK,
+ IMG_QUICKFIX })
{
IPath path = new Path("icons/" + key + ".gif"); //$NON-NLS-1$ //$NON-NLS-2$
URL url = FileLocator.find(bundle, path, null);
@@ -139,7 +143,7 @@ public static void log(Throwable t)
{
log(new Status(Status.ERROR, PLUGIN_ID, t.getMessage(), t));
}
-
+
public static void log(ArrayList errors)
{
for (Exception e : errors)
diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/handlers/RunConsistencyChecksHandler.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/handlers/RunConsistencyChecksHandler.java
new file mode 100644
index 0000000000..6a83adff4c
--- /dev/null
+++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/handlers/RunConsistencyChecksHandler.java
@@ -0,0 +1,31 @@
+package name.abuchen.portfolio.ui.handlers;
+
+import name.abuchen.portfolio.model.Client;
+import name.abuchen.portfolio.ui.ClientEditor;
+import name.abuchen.portfolio.ui.ConsistencyChecksJob;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+public class RunConsistencyChecksHandler extends AbstractHandler
+{
+ public Object execute(ExecutionEvent event) throws ExecutionException
+ {
+ IWorkbenchPage page = HandlerUtil.getActiveWorkbenchWindow(event).getActivePage();
+ final IEditorPart activeEditor = page.getActiveEditor();
+
+ if (!(activeEditor instanceof ClientEditor))
+ return null;
+
+ ClientEditor editor = (ClientEditor) activeEditor;
+ final Client client = editor.getClient();
+
+ new ConsistencyChecksJob(editor, client, true).schedule();
+
+ return null;
+ }
+}
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 2ed5393be5..fa4feb1c9c 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
@@ -103,8 +103,12 @@ ColumnDeltaPercent = Delta %
ColumnDeltaValue = Delta Value
+ColumnEntity = Entity
+
ColumnFees = Fees
+ColumnFix = Fix
+
ColumnIRR = IRR
ColumnIRRPerformance = IRR Performance
@@ -115,6 +119,8 @@ ColumnISIN = ISIN
ColumnIndex = Index
+ColumnIssue = Issue
+
ColumnLable = Label
ColumnLastTrade = Last Trade
@@ -197,6 +203,10 @@ ConsumerPriceIndexMenuDelete = Delete
CurrencyConverter_MsgNotANumber = Not a valid number %s
+DialogConsistencyChecksTitle = Results of the consistency checks
+
+DialogConssitencyChecksMessage = Resolve a reported issue by clicking into the last column and select one of the proposed fixes.\nRe-run the consistency check via the main menu.
+
DialogTitleCounterTransactionNotFound = Counter transaction not found
DialogTitleMultipleCounterTransactions = Multiple potential counter transactions found
@@ -289,6 +299,8 @@ JobMsgErrorUpdatingQuotes = Errors while updating quotes
JobMsgLoadingExchanges = Loading exchanges
+JobMsgRunningConsistencyChecks = Running consistency checks
+
JobMsgSamplingHistoricalQuotes = Sampling historical quotes from exchange {0}
LabelAccounts = Accounts
@@ -451,6 +463,8 @@ MsgMissingReferenceAccount = Reference account of portfolio missing
MsgMissingSecurity = Security is missing
+MsgNoIssuesFound = No issues found.
+
MsgNoProfileFound = No installation profile found. Running from IDE?
MsgNoSecuritiesMaintained = Transactions about securities can only be entered if a security has been created in the section "Securities" -> "All securities" beforehand.
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 00e56a6d09..cb681163eb 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
@@ -103,8 +103,12 @@ ColumnDeltaPercent = Delta %
ColumnDeltaValue = Delta Wert
+ColumnEntity = Objekt
+
ColumnFees = Geb\u00FChren
+ColumnFix = Fix
+
ColumnIRR = IZF
ColumnIRRPerformance = IZF Performance
@@ -115,6 +119,8 @@ ColumnISIN = ISIN
ColumnIndex = Index
+ColumnIssue = Problem
+
ColumnLable = Bezeichnung
ColumnLastTrade = Letzter Handel
@@ -197,6 +203,10 @@ ConsumerPriceIndexMenuDelete = L\u00F6schen
CurrencyConverter_MsgNotANumber = Keine g\u00FCltige Zahl %s
+DialogConsistencyChecksTitle = Ergebnis der Konsistenzpr\u00FCfung
+
+DialogConssitencyChecksMessage = Mit einem Klick in die letzte Spalte kann ein Problem durch eine der vorgeschlagenen L\u00F6sung behoben werden.\nDie Konsistenzpr\u00FCfung kann jederzeit \u00FCber das Men\u00FC erneut ausgef\u00FChrt werden.
+
DialogTitleCounterTransactionNotFound = Gegenbuchung nicht gefunden
DialogTitleMultipleCounterTransactions = Mehrere potenzielle Gegenbuchungen gefunden
diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/ColumnViewerSorter.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/ColumnViewerSorter.java
index f9c1b14b99..85f01eb576 100644
--- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/ColumnViewerSorter.java
+++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/ColumnViewerSorter.java
@@ -76,7 +76,9 @@ private BeanComparator(Class> clazz, String attribute)
Class> returnType = method.getReturnType();
- if (returnType.isAssignableFrom(String.class))
+ if (returnType.equals(Object.class))
+ type = 0;
+ else if (returnType.isAssignableFrom(String.class))
type = 1;
else if (returnType.isAssignableFrom(Enum.class))
type = 2;
@@ -148,8 +150,7 @@ public static ColumnViewerSorter create(Class> clazz, String... attributes)
for (String attribute : attributes)
comparators.add(new BeanComparator(clazz, attribute));
- return new ColumnViewerSorter(comparators.size() == 1 ? comparators.get(0) : new ChainedComparator(
- comparators));
+ return new ColumnViewerSorter(comparators.size() == 1 ? comparators.get(0) : new ChainedComparator(comparators));
}
public static ColumnViewerSorter create(Comparator