/* ***************************************************************************** * JFire - it's hot - Free ERP System - http://jfire.org * * Copyright (C) 2004-2005 NightLabs - http://NightLabs.org * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin St, Fifth Floor, * * Boston, MA 02110-1301 USA * * * * Or get it online : * * http://opensource.org/licenses/lgpl-license.php * * * * * ******************************************************************************/ package org.nightlabs.jfire.trade.admin.ui.gridpriceconfig; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jdo.FetchPlan; import javax.jdo.JDOHelper; import javax.security.auth.login.LoginException; import javax.swing.Timer; import org.apache.log4j.Logger; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.nightlabs.base.ui.composite.XComposite; import org.nightlabs.base.ui.notification.IDirtyStateManager; import org.nightlabs.base.ui.wizard.DynamicPathWizardDialog; import org.nightlabs.jdo.NLJDOHelper; import org.nightlabs.jfire.accounting.TariffMapper; import org.nightlabs.jfire.accounting.dao.TariffMappingDAO; import org.nightlabs.jfire.accounting.gridpriceconfig.AssignInnerPriceConfigCommand; import org.nightlabs.jfire.accounting.gridpriceconfig.GridPriceConfig; import org.nightlabs.jfire.accounting.gridpriceconfig.PriceCalculationException; import org.nightlabs.jfire.accounting.gridpriceconfig.PriceCalculator; import org.nightlabs.jfire.accounting.priceconfig.FetchGroupsPriceConfig; import org.nightlabs.jfire.accounting.priceconfig.IInnerPriceConfig; import org.nightlabs.jfire.accounting.priceconfig.PriceConfigName; import org.nightlabs.jfire.accounting.priceconfig.id.PriceConfigID; import org.nightlabs.jfire.base.login.ui.Login; import org.nightlabs.jfire.store.ProductType; import org.nightlabs.jfire.store.id.ProductTypeID; import org.nightlabs.jfire.trade.CustomerGroupMapper; import org.nightlabs.jfire.trade.admin.ui.gridpriceconfig.wizard.AbstractChooseGridPriceConfigWizard; import org.nightlabs.jfire.trade.admin.ui.resource.Messages; import org.nightlabs.jfire.trade.dao.CustomerGroupMappingDAO; import org.nightlabs.progress.NullProgressMonitor; import org.nightlabs.progress.ProgressMonitor; /** * This composite can be used to display and edit the whole grid-price-configuration * of one ProductType. * * @author Marco Schulze - marco at nightlabs dot de */ public abstract class PriceConfigComposite extends XComposite { private static final Logger logger = Logger.getLogger(PriceConfigComposite.class); private ProductTypeSelector productTypeSelector; private DimensionValueSelector dimensionValueSelector; private DimensionXYSelector dimensionXYSelector; private PriceConfigGrid priceConfigGrid; private CellDetail cellDetail; private PriceCalculator priceCalculator; // -------> Kai: 2009-11-13 // Handles property changes related to all components in this PriceConfigComposite more efficiently. // Was previously handled specifically in PriceConfigGrid. public static final String PROPERTY_CHANGE_KEY_PRICE_CONFIG_CHANGED = "priceConfigChanged"; //$NON-NLS-1$ public static final String PROPERTY_CHANGE_KEY_PRICE_CONFIG_ERROR = "priceConfigError"; //$NON-NLS-1$ public static final String PROPERTY_CHANGE_KEY_NO_PRICE_CONFIG_COMPOSITE = "noPriceConfigComp"; //$NON-NLS-1$ private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); // <------- protected ProductTypeSelector createProductTypeSelector(Composite parent) { return new ProductTypeSelectorListImpl(parent, SWT.NONE); } /** * @return Returns the productTypeSelector. */ public ProductTypeSelector getProductTypeSelector() { return productTypeSelector; } protected DimensionValueSelector createDimensionValueSelector(Composite parent) { return new DimensionValueSelectorComboImpl(parent, SWT.NONE); } /** * @return Returns the dimensionValueSelector. */ public DimensionValueSelector getDimensionValueSelector() { return dimensionValueSelector; } protected DimensionXYSelector createDimensionXYSelector(Composite parent) { return new DimensionXYSelectorComboImpl(parent, SWT.NONE, getDimensionValueSelector()); } protected PriceConfigGrid createPriceConfigGrid(Composite parent) { return new PriceConfigGrid( parent, getProductTypeSelector(), getDimensionValueSelector(), getDimensionXYSelector()); } protected CellDetail createCellDetail(Composite parent) { return new CellDetail(parent, SWT.NONE, getPriceConfigGrid(), this); } /** * @return Returns the priceConfigGrid. */ public PriceConfigGrid getPriceConfigGrid() { return priceConfigGrid; } /** * @return Returns the dimensionXYSelector. */ public DimensionXYSelector getDimensionXYSelector() { return dimensionXYSelector; } /** * @return Returns the cellDetail. */ public CellDetail getCellDetail() { return cellDetail; } /** * @return Returns the priceCalculator. */ public PriceCalculator getPriceCalculator() { return priceCalculator; } public PriceConfigComposite(Composite parent) { this(parent, null); } private IDirtyStateManager dirtyStateManager; public IDirtyStateManager getDirtyStateManager() { return dirtyStateManager; } private boolean changed = true; public boolean isChanged() { return changed; } private Composite productTypeNotSetComposite; private Composite stackWrapper; private StackLayout stackLayout; private Composite priceConfigEditComposite = null; private PriceConfigInInnerProductTypeNotEditableComposite priceConfigInInnerProductTypeNotEditableComposite = null; private Composite noPriceConfigAssignedComposite = null; public PriceConfigComposite(Composite parent, IDirtyStateManager dirtyStateManager) { super(parent, SWT.NONE, LayoutMode.TIGHT_WRAPPER); this.dirtyStateManager = dirtyStateManager; stackWrapper = new XComposite(this, SWT.NONE); stackLayout = new StackLayout(); stackWrapper.setLayout(stackLayout); stackWrapper.setLayoutData(new GridData(GridData.FILL_BOTH)); // priceConfigEditComposite = createPriceConfigEditComposite(stackWrapper); // noPriceConfigAssignedComposite = createNoPriceConfigAssignedComposite(stackWrapper); // stackLayout.topControl = priceConfigEditComposite; productTypeNotSetComposite = new XComposite(stackWrapper, SWT.NONE); new Label(productTypeNotSetComposite, SWT.NONE).setText(Messages.getString("org.nightlabs.jfire.trade.admin.ui.gridpriceconfig.PriceConfigComposite.label.text")); //$NON-NLS-1$ stackLayout.topControl = productTypeNotSetComposite; // Forces the composites to be created. getPriceConfigEditComposite(); // Incorporates the inactivity-timeout, to check if formula is valid while idle after typing. initKeyTimeOutListener(); } /** * Initialises the inactivity-timeout, to check if formula is valid while idle after typing. * Should call this only after getPriceConfigEditComposite() has been executed. */ protected void initKeyTimeOutListener() { // Kai 2009-11-19: // Incorporating the inactivity-timeout, to check if formula is valid. // Alternatively, we can use the TimerText class. But that would require further amendments to // the JSEdiorComposite class, which should then extend the TimerTextClass and manage its Document. keyTimeOutListener = new KeyTimeOutListener(); timer = new Timer(TIMEOUT, keyTimeOutListener); cellDetail.getCellDetailText().addKeyListener(keyTimeOutListener); cellDetail.getCellDetailFallbackText().addKeyListener(keyTimeOutListener); timer.setRepeats(true); } // Kai 2009-11-19: // Very lightweight machination (instead of calibrating the TimerTextClass?), in addition to the initKeyTimeOutListener(). public static final int TIMEOUT = 1005; // milliseconds. Seems to be a comfortable timeout delay. private Timer timer; private KeyTimeOutListener keyTimeOutListener; private class KeyTimeOutListener implements KeyListener, ActionListener { private Object delayedModifyListenersMutex = new Object(); private long lastEventMarkTm = System.currentTimeMillis(); @Override public void keyPressed(KeyEvent event) {} @Override public void keyReleased(KeyEvent event) { recordLastEvent(event); } private void recordLastEvent(KeyEvent event) { synchronized (delayedModifyListenersMutex) { if (!timer.isRunning()) timer.start(); lastEventMarkTm = System.currentTimeMillis(); } } @Override public void actionPerformed(ActionEvent event) { synchronized (delayedModifyListenersMutex) { if (System.currentTimeMillis() - lastEventMarkTm >= TIMEOUT) { timer.stop(); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { cellDetail.forceFocus(); // Need this to trigger the formula checking. The current listeners will know how to react already. cellDetail.setFocus(); // To return the focus and cursor back to the last typed position. } }); } } } } protected Composite createLeftCarrierComposite(Composite parent) { return new XComposite(parent, SWT.NONE, LayoutMode.TIGHT_WRAPPER); } protected Composite createRightCarrierComposite(Composite parent) { return new XComposite(parent, SWT.NONE, LayoutMode.TIGHT_WRAPPER); } protected Composite getPriceConfigEditComposite() { if (priceConfigEditComposite == null) priceConfigEditComposite = createPriceConfigEditComposite(stackWrapper); return priceConfigEditComposite; } protected PriceConfigInInnerProductTypeNotEditableComposite getPriceConfigInInnerProductTypeNotEditableComposite() { if (priceConfigInInnerProductTypeNotEditableComposite == null) priceConfigInInnerProductTypeNotEditableComposite = createPriceConfigInInnerProductTypeNotEditableComposite(stackWrapper); return priceConfigInInnerProductTypeNotEditableComposite; } class PriceConfigInInnerProductTypeNotEditableComposite extends XComposite { private Text priceConfigName; public PriceConfigInInnerProductTypeNotEditableComposite(Composite parent) { super(parent, SWT.NONE); getGridLayout().numColumns = 2; Label title = new Label(this, SWT.NONE); title.setText(Messages.getString("org.nightlabs.jfire.trade.admin.ui.gridpriceconfig.PriceConfigComposite.label.title")); //$NON-NLS-1$ priceConfigName = new Text(this, getBorderStyle() | SWT.READ_ONLY); priceConfigName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); Label info = new Label(this, SWT.NONE | SWT.WRAP); GridData gdInfo = new GridData(GridData.FILL_HORIZONTAL); gdInfo.horizontalSpan = 2; info.setLayoutData(gdInfo); info.setText(Messages.getString("org.nightlabs.jfire.trade.admin.ui.gridpriceconfig.PriceConfigComposite.label.info.text")); //$NON-NLS-1$ } public void setPackageProductType(ProductType packageProductType) { if (packageProductType == null) priceConfigName.setText(""); //$NON-NLS-1$ else priceConfigName.setText(packageProductType.getInnerPriceConfig().getName().getText()); } } protected PriceConfigInInnerProductTypeNotEditableComposite createPriceConfigInInnerProductTypeNotEditableComposite(Composite parent) { return new PriceConfigInInnerProductTypeNotEditableComposite(parent); } protected Composite createPriceConfigEditComposite(Composite parent) { SashForm sfLeftRight = new SashForm(parent, SWT.NONE | SWT.HORIZONTAL); sfLeftRight.setLayoutData(new GridData(GridData.FILL_BOTH)); Composite left = createLeftCarrierComposite(sfLeftRight); Composite right = createRightCarrierComposite(sfLeftRight); sfLeftRight.setWeights(new int[] {1, 2}); productTypeSelector = createProductTypeSelector(left); dimensionValueSelector = createDimensionValueSelector(left); dimensionXYSelector = createDimensionXYSelector(right); SashForm sfGrid = new SashForm(right, SWT.NONE | SWT.VERTICAL); sfGrid.setLayoutData(new GridData(GridData.FILL_BOTH)); priceConfigGrid = createPriceConfigGrid(sfGrid); priceConfigGrid.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { propertyChangeSupport.firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()); if (dirtyStateManager != null) dirtyStateManager.markDirty(); } }); cellDetail = createCellDetail(sfGrid); // add listeners to notify dirty state // cellDetail.getCellDetailText().addModifyListener(cellEditModifyListener); // cellDetail.getCellDetailFallbackText().addModifyListener(cellEditModifyListener); // cellDetail.getCellDetailText().getDocument().addDocumentListener(cellEditModifyListener); // cellDetail.getCellDetailFallbackText().getDocument().addDocumentListener(cellEditModifyListener); // dimensionValueSelector.addPropertyChangeListener( // DimensionValueSelector.PROPERTYCHANGEKEY_ADDDIMENSIONVALUE, new PropertyChangeListener() { // public void propertyChange(PropertyChangeEvent evt) { // if (priceCalculator != null) { // priceCalculator.preparePriceCalculation_createPackagedResultPriceConfigs(); // } // } // } // ); dimensionValueSelector.addPropertyChangeListener( DimensionValueSelector.PROPERTYCHANGEKEY_ADDDIMENSIONVALUE, dimensionValueChangeListener); dimensionValueSelector.addPropertyChangeListener( DimensionValueSelector.PROPERTYCHANGEKEY_REMOVEDIMENSIONVALUE, dimensionValueChangeListener); sfGrid.setWeights(new int[] {1, 1}); return sfLeftRight; } protected Composite getNoPriceConfigAssignedComposite() { if (noPriceConfigAssignedComposite == null) noPriceConfigAssignedComposite = createNoPriceConfigAssignedComposite(stackWrapper); return noPriceConfigAssignedComposite; } protected Composite createNoPriceConfigAssignedComposite(Composite parent) { Composite noPriceConfigComp = new XComposite(parent, SWT.NONE); Label label = new Label(noPriceConfigComp, SWT.NONE); label.setText(Messages.getString("org.nightlabs.jfire.trade.admin.ui.gridpriceconfig.PriceConfigComposite.noPriceConfigAssignedLabel.text")); //$NON-NLS-1$ Button assignButton = new Button(noPriceConfigComp, SWT.NONE); assignButton.setText(Messages.getString("org.nightlabs.jfire.trade.admin.ui.gridpriceconfig.PriceConfigComposite.assignButton.text")); //$NON-NLS-1$ assignButton.addSelectionListener(new SelectionListener(){ public void widgetSelected(SelectionEvent e) { AbstractChooseGridPriceConfigWizard wizard = createChoosePriceConfigWizard( (ProductTypeID) JDOHelper.getObjectId(packageProductType.getExtendedProductType())); DynamicPathWizardDialog dialog = new DynamicPathWizardDialog(getShell(), wizard); // dialog.setTitle("Choose Price Configuration"); int returnCode = dialog.open(); if (returnCode == Window.OK) { assignNewPriceConfig(wizard); // if (dirtyStateManager instanceof SectionPart) { // SectionPart sectionPart = (SectionPart) dirtyStateManager; // sectionPart.getSection().setText(packageProductType.getInnerPriceConfig().getName().getText()); // } // Kai: 2009-11-13 // A revision of the above codes: Without explicitly casting dirtyStateManager as a SectionPart. // SEE -- The corresponding registered listener in AbstractGridPriceConfigSection. // BUT -- Is the triggering of the properChangeListener appropriate here?? Mebbe other more appropriate listeners are avaiable. String innerPriceConfigName = packageProductType.getInnerPriceConfig().getName().getText(); propertyChangeSupport.firePropertyChange(PriceConfigComposite.PROPERTY_CHANGE_KEY_NO_PRICE_CONFIG_COMPOSITE, null, innerPriceConfigName); } } public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } }); return noPriceConfigComp; } protected static final String[] FETCH_GROUPS_INNER_PRICE_CONFIG_FOR_EDITING = { FetchPlan.DEFAULT, FetchGroupsPriceConfig.FETCH_GROUP_EDIT}; protected abstract IInnerPriceConfig retrieveInnerPriceConfigForEditing(PriceConfigID priceConfigID); // private ModifyListener cellEditModifyListener = new ModifyListener(){ // public void modifyText(ModifyEvent e) { // if (dirtyStateManager != null) // dirtyStateManager.markDirty(); // } // }; // private boolean initalState = false; // /** // * sets the initalState, to determine that while in this state no calls to the // * dirtyStateManager are performed, to avoid dirty states when calling // * setPackageProductType(ProductType packageProductType) // * // * @param initalState the initalState to set // */ // public void setInitaliseState(boolean initalState) { // this.initalState = initalState; // } // /** // * returns the initalState // * @return the initalState // */ // public boolean isInitalState() { // return initalState; // } // private IDocumentListener cellEditModifyListener = new IDocumentListener(){ // public void documentAboutToBeChanged(DocumentEvent arg0) { // } // // public void documentChanged(DocumentEvent arg0) { //// // FIXME: documentChanged is called even if only a productType is selected //// if (dirtyStateManager != null && !initalState) //// dirtyStateManager.markDirty(); // } // }; /** * This listener is triggered, whenever a DimensionValue is either added or removed. * It is NOT triggered, when a DimensionValue is merely selected (i.e. no real change to the PriceConfig). */ private PropertyChangeListener dimensionValueChangeListener = new PropertyChangeListener(){ public void propertyChange(PropertyChangeEvent evt) { if (logger.isDebugEnabled()) logger.debug("dimensionValueChangeListener#propertyChange: propertyName=" + evt.getPropertyName()); //$NON-NLS-1$ // if (dirtyStateManager != null && !initalState) // dirtyStateManager.markDirty(); if (priceCalculator != null) priceCalculator.preparePriceCalculation_createPackagedResultPriceConfigs(); if (dirtyStateManager != null) dirtyStateManager.markDirty(); } }; public static final String[] FETCH_GROUPS_TARIFF_MAPPING = { FetchPlan.DEFAULT }; public static final String[] FETCH_GROUPS_CUSTOMER_GROUP_MAPPING = { FetchPlan.DEFAULT }; protected CustomerGroupMapper createCustomerGroupMapper() { return new CustomerGroupMapper( CustomerGroupMappingDAO.sharedInstance().getCustomerGroupMappings( FETCH_GROUPS_CUSTOMER_GROUP_MAPPING, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, new NullProgressMonitor())); // TODO do this asynchronously with a real ProgressMonitor?! } protected TariffMapper createTariffMapper() { return new TariffMapper( TariffMappingDAO.sharedInstance().getTariffMappings( FETCH_GROUPS_TARIFF_MAPPING, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, new NullProgressMonitor())); // TODO do this asynchronously with a real ProgressMonitor?! } protected PriceCalculator createPriceCalculator(ProductType packageProductType) { return new PriceCalculator(packageProductType, createCustomerGroupMapper(), createTariffMapper()); } private ProductType packageProductType = null; public ProductType getPackageProductType() { return packageProductType; } public void _setPackageProductType(ProductType packageProductType) // throws ModuleException { if (productTypeSelector != null) productTypeSelector.setPackageProductType(null); if (dimensionValueSelector != null) dimensionValueSelector.setGridPriceConfig(null); if (priceConfigInInnerProductTypeNotEditableComposite != null) priceConfigInInnerProductTypeNotEditableComposite.setPackageProductType(null); priceCalculator = null; this.packageProductType = packageProductType; if (packageProductType == null) { stackLayout.topControl = productTypeNotSetComposite; } else { if (packageProductType.getInnerPriceConfig() == null) { stackLayout.topControl = getNoPriceConfigAssignedComposite(); } else { if (packageProductType.getPackageNature() == ProductType.PACKAGE_NATURE_INNER) { stackLayout.topControl = getPriceConfigInInnerProductTypeNotEditableComposite(); getPriceConfigInInnerProductTypeNotEditableComposite().setPackageProductType(packageProductType); } else { // package nature outer // show price config comp stackLayout.topControl = getPriceConfigEditComposite(); // if (packageProductType.getInnerPriceConfig() != null || packageProductType.getPackagePriceConfig() != null) { priceCalculator = createPriceCalculator(packageProductType); priceCalculator.preparePriceCalculation(); try { priceCalculator.calculatePrices(); } catch (PriceCalculationException e) { throw new RuntimeException(e); } // The packagePriceConfig defines all parameters (dimension values) we need to know. // When the packagePriceConfig comes from the server (after preparePriceCalculation hase been called) // it has the same parameters (except PriceFragmentTypes) as the innerPriceConfig. // The PriceFragmentTypes have already been collected from all the packaged PriceConfigs. GridPriceConfig gridPriceConfig = (GridPriceConfig) packageProductType.getPackagePriceConfig(); if (gridPriceConfig == null) { // if the package priceConfig is null in the prodcut-type we // take the one created by the PriceCalculator (this might be a non-persistent temporal one) gridPriceConfig = (GridPriceConfig) priceCalculator.getPackagePriceConfig(); } if (gridPriceConfig == null) throw new IllegalStateException("packageProductType.getPackagePriceConfig() and priceCalculator.getPackagePriceConfig() both returned null!"); //$NON-NLS-1$ // gridPriceConfig = (GridPriceConfig) packageProductType.getInnerPriceConfig(); dimensionValueSelector.setGridPriceConfig(gridPriceConfig); productTypeSelector.setPackageProductType(packageProductType); // } } // package nature outer } // if (packageProductType.getInnerPriceConfig() != null) { } // if (packageProductType != null) { stackWrapper.layout(true, true); if (priceConfigGrid != null) priceConfigGrid.setPriceCalculator(priceCalculator); } /** * stores the Price Configurations * * @param priceConfigs the priceConfigs to store * @param assignInnerPriceConfigCommand TODO * @return a Collection of the stored gridPriceConfigs */ protected abstract <P extends GridPriceConfig> Collection<P> storePriceConfigs(Collection<P> priceConfigs, AssignInnerPriceConfigCommand assignInnerPriceConfigCommand); /** * returns an implementation of {@link AbstractChooseGridPriceConfigWizard} to let the user * choose a Price Configuration, if no Price Configuration has been assigned yet * * @param parentProductTypeID the parent product Type * @return an implementation of AbstractChooseGridPriceConfigWizard */ public abstract AbstractChooseGridPriceConfigWizard createChoosePriceConfigWizard(ProductTypeID parentProductTypeID); /** * Create an implementation of {@link CellReferenceProductTypeSelector} to let the user * choose a <code>ProductType</code> or return <code>null</code>, if this dimension is not * used in the concrete price config. * * @return an implementation of {@link CellReferenceProductTypeSelector} or <code>null</code>. */ public abstract CellReferenceProductTypeSelector createCellReferenceProductTypeSelector(); /** * Stores the current PriceConfig. * Prior to saving it will show the user which other ProductTypes * are affected by this change and give him the possibility to * cancel this action. * * @return Whether the PriceConfig was saved or not. */ public boolean submit() { // ProductType packageProductType = productTypeSelector.getPackageProductType(); if (packageProductType == null) return false; ProductTypeID productTypeID = (ProductTypeID) JDOHelper.getObjectId(packageProductType); // PriceConfigID innerPriceConfigID = (PriceConfigID) JDOHelper.getObjectId(packageProductType.getInnerPriceConfig()); // This doesn't work, if the PC is new! IInnerPriceConfig ipc = packageProductType.getInnerPriceConfig(); PriceConfigID innerPriceConfigID = ipc == null ? null : PriceConfigID.create(ipc.getOrganisationID(), ipc.getPriceConfigID()); String localOrganisationID; try { localOrganisationID = Login.getLogin().getOrganisationID(); } catch (LoginException e) { throw new RuntimeException(e); } // collect all price configurations Set<GridPriceConfig> priceConfigs; Set<PriceConfigID> priceConfigIDs; Map<GridPriceConfig, List<ProductTypeSelector.Item>> priceConfig2ProductTypeSelectorItemList = null; if (packageProductType.getPackageNature() == ProductType.PACKAGE_NATURE_INNER) { priceConfigs = new HashSet<GridPriceConfig>(1); priceConfigs.add((GridPriceConfig) packageProductType.getInnerPriceConfig()); } else { if (packageProductType.getInnerPriceConfig() == null) { priceConfigs = new HashSet<GridPriceConfig>(0); } else { priceConfig2ProductTypeSelectorItemList = new HashMap<GridPriceConfig, List<ProductTypeSelector.Item>>(productTypeSelector.getProductTypeItems().size()); for (ProductTypeSelector.Item item : productTypeSelector.getProductTypeItems()) { if (!localOrganisationID.equals(item.getProductType().getOrganisationID())) // ignore partner-ProductTypes as we must not modify their prices continue; GridPriceConfig priceConfig = item.getPriceConfig(); if (priceConfig != null) { List<ProductTypeSelector.Item> items = priceConfig2ProductTypeSelectorItemList.get(priceConfig); if (items == null) { items = new ArrayList<ProductTypeSelector.Item>(); priceConfig2ProductTypeSelectorItemList.put(priceConfig, items); } items.add(item); } } priceConfigs = new HashSet<GridPriceConfig>(priceConfig2ProductTypeSelectorItemList.keySet()); // we copy the set, because the keySet is not serializable and cannot be sent to the server } } // remove null values - maybe not every product type has a price config assigned while (priceConfigs.remove(null)); priceConfigIDs = NLJDOHelper.getObjectIDSet(priceConfigs); priceConfigIDs = new HashSet<PriceConfigID>(priceConfigIDs); // if there are priceConfigs which have never been stored (i.e. no ID assigned), we ignore them silently. while (priceConfigIDs.remove(null)); // Kai: 2009-11-13 // Check to see if there are STILL any more errors contained in the price configs; i.e. dont save if errors persist. if (priceCalculator != null){ try { priceCalculator.calculatePrices(); } catch (PriceCalculationException e) { throw new RuntimeException("Invalid or incomplete formula in the price configuration(s): " + e.getShortenedErrorMessage()); } } if (!priceConfigIDs.isEmpty()) { // show the consequences and ask the user whether he really wants to save Shell shell = null; try { shell = getShell(); } catch (SWTException e) { // the composite might be disposed already, try to get another shell shell = Display.getDefault().getActiveShell(); } StorePriceConfigsConfirmationDialog dialog = new StorePriceConfigsConfirmationDialog( shell, priceConfigIDs, productTypeID, innerPriceConfigID); if (dialog.open() != Window.OK) return false; } // store the price configs to the server (it will recalculate all affected product types) Collection<GridPriceConfig> newPCs = storePriceConfigs( priceConfigs, new AssignInnerPriceConfigCommand( productTypeID, innerPriceConfigID, packageProductType.getFieldMetaData(ProductType.FieldName.innerPriceConfig).isValueInherited())); // and replace the local price configs by the new ones (freshly detached from the server) if (priceConfig2ProductTypeSelectorItemList != null) { for (GridPriceConfig priceConfig : newPCs) { List<ProductTypeSelector.Item> items = priceConfig2ProductTypeSelectorItemList.get(priceConfig); for (ProductTypeSelector.Item item : items) item.setPriceConfig(priceConfig); } } // In case the new price configs have not all data that's necessary now // (e.g. that's the case with DynamicTradePriceConfig, which does not store packagingResultPriceConfigs), // we ensure this now: if (packageProductType.getInnerPriceConfig() != null) { priceCalculator.preparePriceCalculation(); try { // We must actually recalculate as well, because otherwise the TransientStablePriceConfig contains only 0.00 // in every cell (which will be shown in the UI as soon as a dimension-value is modified). // See: https://www.jfire.org/modules/bugs/view.php?id=1084 priceCalculator.calculatePrices(); } catch (PriceCalculationException e) { throw new RuntimeException(e); } } return true; } public void assignNewPriceConfig(IInnerPriceConfig innerPC, final boolean inherited, final ProgressMonitor monitor) { monitor.beginTask(Messages.getString("org.nightlabs.jfire.trade.admin.ui.gridpriceconfig.PriceConfigComposite.job.assignPriceConfig.name"), 100); //$NON-NLS-1$ try { if (innerPC != null) { PriceConfigID innerPCID = (PriceConfigID) JDOHelper.getObjectId(innerPC); if (innerPCID != null) innerPC = retrieveInnerPriceConfigForEditing(innerPCID); } monitor.worked(80); final IInnerPriceConfig finalInnerPC = innerPC; Display.getDefault().syncExec(new Runnable() { public void run() { if (isDisposed()) return; packageProductType.setInnerPriceConfig(finalInnerPC); packageProductType.getFieldMetaData(ProductType.FieldName.innerPriceConfig).setValueInherited(inherited); if (packageProductType.getInnerPriceConfig() != null) { GridPriceConfig packagePriceConfig = (GridPriceConfig)packageProductType.getPackagePriceConfig(); if (packagePriceConfig != null) packagePriceConfig.adoptParameters(packageProductType.getInnerPriceConfig(), false); } _setPackageProductType(packageProductType); dirtyStateManager.markDirty(); monitor.worked(20); } }); } finally { monitor.done(); } } public void assignNewPriceConfig(AbstractChooseGridPriceConfigWizard wizard) // TODO ProgressMonitor { wizard.getAbstractChooseGridPriceConfigPage().configureProductType(packageProductType); PriceConfigName newName = null; IInnerPriceConfig innerPC = packageProductType.getInnerPriceConfig(); if (innerPC != null) { newName = packageProductType.getInnerPriceConfig().getName(); //Backup it!!! PriceConfigID innerPCID = (PriceConfigID) JDOHelper.getObjectId(innerPC); if (innerPCID != null) { innerPC = retrieveInnerPriceConfigForEditing(innerPCID); packageProductType.setInnerPriceConfig(innerPC); } } if (newName != null) packageProductType.getInnerPriceConfig().getName().copyFrom(newName); if (packageProductType.getInnerPriceConfig() != null) { GridPriceConfig packagePriceConfig = (GridPriceConfig)packageProductType.getPackagePriceConfig(); if (packagePriceConfig != null) packagePriceConfig.adoptParameters(packageProductType.getInnerPriceConfig(), false); } _setPackageProductType(packageProductType); dirtyStateManager.markDirty(); } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(propertyName, listener); } }