package name.abuchen.portfolio.ui.dialogs.transactions; import static name.abuchen.portfolio.ui.util.FormDataFactory.startingWith; import static name.abuchen.portfolio.ui.util.SWTHelper.amountWidth; import static name.abuchen.portfolio.ui.util.SWTHelper.currencyWidth; import java.time.LocalDate; import java.util.Collections; import java.util.List; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.core.databinding.beans.BeanProperties; import org.eclipse.e4.core.di.extensions.Preference; import org.eclipse.e4.ui.services.IServiceConstants; import org.eclipse.jface.databinding.swt.WidgetProperties; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import name.abuchen.portfolio.model.BuySellEntry; import name.abuchen.portfolio.model.Client; import name.abuchen.portfolio.model.Portfolio; import name.abuchen.portfolio.model.PortfolioTransaction; import name.abuchen.portfolio.model.Security; import name.abuchen.portfolio.model.TransactionPair; import name.abuchen.portfolio.money.CurrencyUnit; import name.abuchen.portfolio.money.ExchangeRateProviderFactory; import name.abuchen.portfolio.money.Values; import name.abuchen.portfolio.ui.Messages; import name.abuchen.portfolio.ui.UIConstants; import name.abuchen.portfolio.ui.dialogs.transactions.AbstractSecurityTransactionModel.Properties; import name.abuchen.portfolio.ui.util.DateTimePicker; import name.abuchen.portfolio.ui.util.SimpleDateTimeSelectionProperty; @SuppressWarnings("restriction") public class SecurityTransactionDialog extends AbstractTransactionDialog // NOSONAR { @Inject private Client client; @Preference(value = UIConstants.Preferences.USE_INDIRECT_QUOTATION) @Inject private boolean useIndirectQuotation = false; @Inject public SecurityTransactionDialog(@Named(IServiceConstants.ACTIVE_SHELL) Shell parentShell) { super(parentShell); } @PostConstruct private void createModel(ExchangeRateProviderFactory factory, PortfolioTransaction.Type type) // NOSONAR { boolean isBuySell = type == PortfolioTransaction.Type.BUY || type == PortfolioTransaction.Type.SELL; AbstractSecurityTransactionModel model = isBuySell ? new BuySellModel(client, type) : new SecurityDeliveryModel(client, type); model.setExchangeRateProviderFactory(factory); setModel(model); // set portfolio only if exactly one exists // (otherwise force user to choose) List<Portfolio> activePortfolios = client.getActivePortfolios(); if (activePortfolios.size() == 1) model.setPortfolio(activePortfolios.get(0)); List<Security> activeSecurities = client.getActiveSecurities(); if (!activeSecurities.isEmpty()) model.setSecurity(activeSecurities.get(0)); } private AbstractSecurityTransactionModel model() { return (AbstractSecurityTransactionModel) this.model; } @Override protected void createFormElements(Composite editArea) { // // input elements // // security ComboInput securities = new ComboInput(editArea, Messages.ColumnSecurity); securities.value.setInput(including(client.getActiveSecurities(), model().getSecurity())); securities.bindValue(Properties.security.name(), Messages.MsgMissingSecurity); securities.bindCurrency(Properties.securityCurrencyCode.name()); // portfolio + reference account ComboInput portfolio = new ComboInput(editArea, Messages.ColumnPortfolio); portfolio.value.setInput(including(client.getActivePortfolios(), model().getPortfolio())); portfolio.bindValue(Properties.portfolio.name(), Messages.MsgMissingPortfolio); ComboInput comboInput = new ComboInput(editArea, null); if (model() instanceof BuySellModel) { comboInput.value.setInput(including(client.getActiveAccounts(), ((BuySellModel) model()).getAccount())); comboInput.bindValue(Properties.account.name(), Messages.MsgMissingAccount); } else { List<CurrencyUnit> availableCurrencies = CurrencyUnit.getAvailableCurrencyUnits(); Collections.sort(availableCurrencies); comboInput.value.setInput(availableCurrencies); comboInput.bindValue(Properties.transactionCurrency.name(), Messages.MsgMissingAccount); } // date Label lblDate = new Label(editArea, SWT.RIGHT); lblDate.setText(Messages.ColumnDate); DateTimePicker valueDate = new DateTimePicker(editArea); context.bindValue(new SimpleDateTimeSelectionProperty().observe(valueDate.getControl()), BeanProperties.value(Properties.date.name()).observe(model)); // other input fields Input shares = new Input(editArea, Messages.ColumnShares); shares.bindValue(Properties.shares.name(), Messages.ColumnShares, Values.Share, true); Input quote = new Input(editArea, "x " + Messages.ColumnQuote); //$NON-NLS-1$ quote.bindBigDecimal(Properties.quote.name(), Values.Quote.pattern()); quote.bindCurrency(Properties.securityCurrencyCode.name()); Input grossValue = new Input(editArea, "="); //$NON-NLS-1$ grossValue.bindValue(Properties.grossValue.name(), Messages.ColumnSubTotal, Values.Amount, true); grossValue.bindCurrency(Properties.securityCurrencyCode.name()); Input exchangeRate = new Input(editArea, useIndirectQuotation ? "/ " : "x "); //$NON-NLS-1$ //$NON-NLS-2$ exchangeRate.bindBigDecimal( useIndirectQuotation ? Properties.inverseExchangeRate.name() : Properties.exchangeRate.name(), Values.ExchangeRate.pattern()); exchangeRate.bindCurrency(useIndirectQuotation ? Properties.inverseExchangeRateCurrencies.name() : Properties.exchangeRateCurrencies.name()); model().addPropertyChangeListener(Properties.exchangeRate.name(), e -> exchangeRate.value.setToolTipText(AbstractModel.createCurrencyToolTip( model().getExchangeRate(), model().getTransactionCurrencyCode(), model().getSecurityCurrencyCode()))); final Input convertedGrossValue = new Input(editArea, "="); //$NON-NLS-1$ convertedGrossValue.bindValue(Properties.convertedGrossValue.name(), Messages.ColumnSubTotal, Values.Amount, true); convertedGrossValue.bindCurrency(Properties.transactionCurrencyCode.name()); // fees Label plusForexFees = new Label(editArea, SWT.NONE); plusForexFees.setText("+"); //$NON-NLS-1$ Input forexFees = new Input(editArea, sign() + Messages.ColumnFees); forexFees.bindValue(Properties.forexFees.name(), Messages.ColumnFees, Values.Amount, false); forexFees.bindCurrency(Properties.securityCurrencyCode.name()); Input fees = new Input(editArea, sign() + Messages.ColumnFees); fees.bindValue(Properties.fees.name(), Messages.ColumnFees, Values.Amount, false); fees.bindCurrency(Properties.transactionCurrencyCode.name()); // taxes Label plusForexTaxes = new Label(editArea, SWT.NONE); plusForexTaxes.setText("+"); //$NON-NLS-1$ Input forexTaxes = new Input(editArea, sign() + Messages.ColumnTaxes); forexTaxes.bindValue(Properties.forexTaxes.name(), Messages.ColumnTaxes, Values.Amount, false); forexTaxes.bindCurrency(Properties.securityCurrencyCode.name()); Input taxes = new Input(editArea, sign() + Messages.ColumnTaxes); taxes.bindValue(Properties.taxes.name(), Messages.ColumnTaxes, Values.Amount, false); taxes.bindCurrency(Properties.transactionCurrencyCode.name()); // total String label = getTotalLabel(); Input total = new Input(editArea, "= " + label); //$NON-NLS-1$ total.bindValue(Properties.total.name(), label, Values.Amount, true); total.bindCurrency(Properties.transactionCurrencyCode.name()); // note Label lblNote = new Label(editArea, SWT.LEFT); lblNote.setText(Messages.ColumnNote); Text valueNote = new Text(editArea, SWT.BORDER); context.bindValue(WidgetProperties.text(SWT.Modify).observe(valueNote), BeanProperties.value(Properties.note.name()).observe(model)); // // form layout // int width = amountWidth(grossValue.value); int currencyWidth = currencyWidth(grossValue.currency); startingWith(securities.value.getControl(), securities.label).suffix(securities.currency) .thenBelow(portfolio.value.getControl()).label(portfolio.label) .suffix(comboInput.value.getControl()).thenBelow(valueDate.getControl()).label(lblDate) // shares - quote - gross value .thenBelow(shares.value).width(width).label(shares.label).thenRight(quote.label) .thenRight(quote.value).width(width).thenRight(quote.currency).width(width) .thenRight(grossValue.label).thenRight(grossValue.value).width(width) .thenRight(grossValue.currency); startingWith(quote.value).thenBelow(exchangeRate.value).width(width).label(exchangeRate.label) .thenRight(exchangeRate.currency).width(width); startingWith(grossValue.value) // converted gross value .thenBelow(convertedGrossValue.value).width(width).label(convertedGrossValue.label) .suffix(convertedGrossValue.currency) // fees .thenBelow(fees.value).width(width).label(fees.label).suffix(fees.currency) // taxes .thenBelow(taxes.value).width(width).label(taxes.label).suffix(taxes.currency) // total .thenBelow(total.value).width(width).label(total.label).suffix(total.currency) // note .thenBelow(valueNote).left(securities.value.getControl()).right(total.value).label(lblNote); startingWith(fees.value).thenLeft(plusForexFees).thenLeft(forexFees.currency).width(currencyWidth) .thenLeft(forexFees.value).width(width).thenLeft(forexFees.label); startingWith(taxes.value).thenLeft(plusForexTaxes).thenLeft(forexTaxes.currency).width(currencyWidth) .thenLeft(forexTaxes.value).width(width).thenLeft(forexTaxes.label); // // hide / show exchange rate if necessary // model.addPropertyChangeListener(Properties.exchangeRateCurrencies.name(), event -> { // NOSONAR String securityCurrency = model().getSecurityCurrencyCode(); String accountCurrency = model().getTransactionCurrencyCode(); // make exchange rate visible if both are set but different boolean visible = securityCurrency.length() > 0 && accountCurrency.length() > 0 && !securityCurrency.equals(accountCurrency); exchangeRate.setVisible(visible); convertedGrossValue.setVisible(visible); forexFees.setVisible(visible); plusForexFees.setVisible(visible); fees.label.setVisible(!visible); forexTaxes.setVisible(visible); plusForexTaxes.setVisible(visible); taxes.label.setVisible(!visible); // set fx taxes and tx fees to 0 if not visible if (!visible) { model().setForexFees(0); model().setForexTaxes(0); } }); WarningMessages warnings = new WarningMessages(this); warnings.add(() -> model().getDate().isAfter(LocalDate.now()) ? Messages.MsgDateIsInTheFuture : null); warnings.add(() -> new StockSplitWarning().check(model().getSecurity(), model().getDate())); model.addPropertyChangeListener(Properties.security.name(), e -> warnings.check()); model.addPropertyChangeListener(Properties.date.name(), e -> warnings.check()); model.firePropertyChange(Properties.exchangeRateCurrencies.name(), "", model().getExchangeRateCurrencies()); //$NON-NLS-1$ } private String sign() { switch (model().getType()) { case BUY: case DELIVERY_INBOUND: return "+ "; //$NON-NLS-1$ case SELL: case DELIVERY_OUTBOUND: return "- "; //$NON-NLS-1$ default: throw new UnsupportedOperationException(); } } private String getTotalLabel() { switch (model().getType()) { case BUY: return Messages.ColumnDebitNote; case DELIVERY_INBOUND: return Messages.LabelValueInboundDelivery; case SELL: return Messages.ColumnCreditNote; case DELIVERY_OUTBOUND: return Messages.LabelValueOutboundDelivery; default: throw new UnsupportedOperationException(); } } @Override public void setPortfolio(Portfolio portfolio) { model().setPortfolio(portfolio); } @Override public void setSecurity(Security security) { model().setSecurity(security); } public void setBuySellEntry(BuySellEntry entry) { if (!model().accepts(entry.getPortfolioTransaction().getType())) throw new IllegalArgumentException(); model().setSource(entry); } public void setDeliveryTransaction(TransactionPair<PortfolioTransaction> pair) { if (!model().accepts(pair.getTransaction().getType())) throw new IllegalArgumentException(); model().setSource(pair); } }