package org.sigmah.shared.dto.element; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.util.Date; import java.util.List; import org.sigmah.client.dispatch.CommandResultHandler; import org.sigmah.client.i18n.I18N; import org.sigmah.client.ui.notif.ConfirmCallback; import org.sigmah.client.ui.notif.N10N; import org.sigmah.client.ui.widget.HistoryTokenText; import org.sigmah.client.util.DateUtils; import org.sigmah.shared.command.GetCountries; import org.sigmah.shared.command.GetCountry; import org.sigmah.shared.command.GetSitesCount; import org.sigmah.shared.command.GetUsersByOrganization; import org.sigmah.shared.command.result.ListResult; import org.sigmah.shared.command.result.SiteResult; import org.sigmah.shared.command.result.ValueResult; import org.sigmah.shared.dto.ProjectDTO; import org.sigmah.shared.dto.UserDTO; import org.sigmah.shared.dto.country.CountryDTO; import org.sigmah.shared.dto.element.event.RequiredValueEvent; import org.sigmah.shared.dto.element.event.ValueEvent; import org.sigmah.shared.dto.history.HistoryTokenListDTO; import org.sigmah.shared.dto.orgunit.OrgUnitDTO; import org.sigmah.shared.dto.referential.DefaultFlexibleElementType; import org.sigmah.shared.dto.referential.DimensionType; import org.sigmah.shared.util.Filter; import com.allen_sauer.gwt.log.client.Log; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.DatePickerEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.event.SelectionChangedEvent; import com.extjs.gxt.ui.client.event.SelectionChangedListener; import com.extjs.gxt.ui.client.store.ListStore; import com.extjs.gxt.ui.client.widget.Component; import com.extjs.gxt.ui.client.widget.form.ComboBox; import com.extjs.gxt.ui.client.widget.form.ComboBox.TriggerAction; import com.extjs.gxt.ui.client.widget.form.DateField; import com.extjs.gxt.ui.client.widget.form.Field; import com.extjs.gxt.ui.client.widget.form.LabelField; import com.extjs.gxt.ui.client.widget.form.NumberField; import com.extjs.gxt.ui.client.widget.form.TextField; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.user.client.rpc.AsyncCallback; /** * DTO mapping class for entity element.DefaultFlexibleElement. * * @author Tom Miette (tmiette@ideia.fr) * @author Denis Colliot (dcolliot@ideia.fr) * @author Raphaƫl Calabro (rcalabro@ideia.fr) */ public class DefaultFlexibleElementDTO extends AbstractDefaultFlexibleElementDTO { /** * Serial version UID. */ private static final long serialVersionUID = 3746586633233053639L; public static final String ENTITY_NAME = "element.DefaultFlexibleElement"; private static final String EMPTY_VALUE = "-"; private transient ListStore<UserDTO> usersStore; protected transient DefaultFlexibleElementContainer container; /** * Creates a new default flexible element DTO. */ public DefaultFlexibleElementDTO() { // Empty constructor. } /** * Creates a new default flexible DTO with the given type. * * @param type * Type of the default flexible element DTO to create. */ public DefaultFlexibleElementDTO(final DefaultFlexibleElementType type) { setType(type); } /** * {@inheritDoc} */ @Override public String getEntityName() { return ENTITY_NAME; } // Type. public DefaultFlexibleElementType getType() { return get("type"); } public void setType(DefaultFlexibleElementType type) { set("type", type); } @Override public String getFormattedLabel() { return getLabel() != null ? getLabel() : DefaultFlexibleElementType.getName(getType()); } public ListStore<CountryDTO> getCountriesStore() { return countriesStore; } public ListStore<UserDTO> getManagersStore() { return usersStore; } /** * {@inheritDoc} */ @Override protected Component getComponent(ValueResult valueResult, boolean enabled) { if (currentContainerDTO instanceof DefaultFlexibleElementContainer) { container = (DefaultFlexibleElementContainer) currentContainerDTO; } if (valueResult != null && valueResult.isValueDefined()) return getComponentWithValue(valueResult, enabled); else return getComponent(enabled); } /** * {@inheritDoc} */ @Override protected Component getComponentInBanner(ValueResult valueResult) { if (currentContainerDTO instanceof DefaultFlexibleElementContainer) { container = (DefaultFlexibleElementContainer) currentContainerDTO; } else { throw new IllegalArgumentException( "The flexible elements container isn't an instance of DefaultFlexibleElementContainer. The default flexible element connot be instanciated."); } // Budget case handled by the budget element itself return super.getComponentInBanner(valueResult); } protected Component getComponent(boolean enabled) { if (currentContainerDTO == null) { throw new IllegalArgumentException( "The flexible elements container isn't an instance of DefaultFlexibleElementContainer. The default flexible element connot be instanciated."); } final Component component; switch (getType()) { // Project code. case CODE: component = buildCodeField(container.getName(), enabled); break; // Project title. case TITLE: component = buildTitleField(container.getFullName(), enabled); break; case START_DATE: component = buildStartDateField(container.getStartDate(), enabled); break; case END_DATE: component = buildEndDateField(container.getEndDate(), enabled); break; case COUNTRY: component = buildCountryField(container.getCountry(), enabled); break; case OWNER: component = buildOwnerField(container.getOwnerFirstName(), container.getOwnerName()); break; case MANAGER: component = buildManagerField(container.getManager(), enabled); break; case ORG_UNIT: component = buildOrgUnitField(I18N.CONSTANTS.orgunit(), container.getOrgUnitId(), enabled); break; default: component = createLabelField("ERROR: The default element type '" + getType() + "' is not supported. Your model may need to be updated."); break; } return component; } protected Component getComponentWithValue(ValueResult valueResult, boolean enabled) { final Component component; switch (getType()) { // Project code. case CODE: component = buildCodeField(valueResult.getValueObject(), enabled); break; // Project title. case TITLE: component = buildTitleField(valueResult.getValueObject(), enabled); break; case START_DATE: component = buildStartDateField(valueResult.getValueObject(), enabled); break; case END_DATE: component = buildEndDateField(valueResult.getValueObject(), enabled); break; case COUNTRY: component = buildCountryField(valueResult.getValueObject(), enabled); break; case OWNER: component = buildOwnerField(valueResult.getValueObject()); break; case MANAGER: component = buildManagerField(valueResult.getValueObject(), enabled); break; case ORG_UNIT: component = buildOrgUnitField(I18N.CONSTANTS.orgunit(), valueResult.getValueObject(), enabled); break; default: component = createLabelField("ERROR: The default element type '" + getType() + "' is not supported. Your model may need to be updated."); break; } return component; } @Override public boolean isCorrectRequiredValue(ValueResult result) { // These elements don't have any value. return true; } /** * Creates the code field. * * @param value Code of the container. * @param enabled <code>true</code> if the field must be editable, <code>false</code> otherwise. * @return The code field. */ private Field<?> buildCodeField(String value, boolean enabled) { return buildTextField(I18N.CONSTANTS.projectName(), value, 50, enabled, false); } /** * Creates the title field. * * @param value Title of the container. * @param enabled <code>true</code> if the field must be editable, <code>false</code> otherwise. * @return The title field. */ private Field<?> buildTitleField(String value, boolean enabled) { return buildTextField(I18N.CONSTANTS.projectFullName(), value, 500, enabled, false); } /** * Creates the start date field. * * @param date Start date of the container. * @param enabled <code>true</code> if the field must be editable, <code>false</code> otherwise. * @return The start date field. */ private Field<?> buildStartDateField(Date date, boolean enabled) { return buildDateField(I18N.CONSTANTS.projectStartDate(), date, enabled); } /** * Creates the start date field. * * @param date Start date of the container. * @param enabled <code>true</code> if the field must be editable, <code>false</code> otherwise. * @return The start date field. */ private Field<?> buildStartDateField(String date, boolean enabled) { return buildDateField(I18N.CONSTANTS.projectStartDate(), new Date(Long.parseLong(date)), enabled); } /** * Creates the end date field. * * @param date End date of the container. * @param enabled <code>true</code> if the field must be editable, <code>false</code> otherwise. * @return The end date field. */ private Field<?> buildEndDateField(Date date, boolean enabled) { return buildDateField(I18N.CONSTANTS.projectEndDate(), date, enabled); } /** * Creates the end date field. * * @param date End date of the container. * @param enabled <code>true</code> if the field must be editable, <code>false</code> otherwise. * @return The end date field. */ private Field<?> buildEndDateField(String date, boolean enabled) { return buildDateField(I18N.CONSTANTS.projectEndDate(), new Date(Long.parseLong(date)), enabled); } /** * Creates the owner field. * This field is always read-only. * * @param firstName First name of the owner. * @param lastName Last name of the owner. * @return The owner field.@ */ private Field<?> buildOwnerField(String firstName, String lastName) { return buildOwnerField(firstName != null ? firstName + ' ' + lastName : lastName); } /** * Creates the owner field. * This field is always read-only. * * @param fullName Full name of the owner. * @return The owner field. */ private Field<?> buildOwnerField(String fullName) { final LabelField labelField = createLabelField(); // Sets the field label. setLabel(I18N.CONSTANTS.projectOwner()); labelField.setFieldLabel(getLabel()); // Sets the value to the field. labelField.setValue(fullName); return labelField; } /** * Creates the manager field. * * @param manager Manager of the container. * @param enabled <code>true</code> if the field must be editable, <code>false</code> otherwise. * @return The manager field. */ private Field<?> buildManagerField(final UserDTO manager, boolean enabled) { final Field<?> field; if (enabled) { final ComboBox<UserDTO> comboBox = new ComboBox<UserDTO>(); comboBox.setEmptyText(I18N.CONSTANTS.flexibleElementDefaultSelectManager()); // Sets the value to the field. // BUGFIX #756 : Iterating through users to give the right instance to the combobox. final ConfirmCallback listener; if(manager != null && manager.getId() != null) { listener = new ConfirmCallback() { @Override public void onAction() { for (final UserDTO model : usersStore.getModels()) { if (manager.getId().equals(model.getId())) { comboBox.setValue(model); return; } } } }; } else { listener = null; } // Load the user store if needed ensureUserStore(listener); comboBox.setStore(usersStore); comboBox.setDisplayField(UserDTO.COMPLETE_NAME); comboBox.setValueField(UserDTO.ID); comboBox.setTriggerAction(TriggerAction.ALL); comboBox.setEditable(true); comboBox.setAllowBlank(true); // Listens to the selection changes. comboBox.addSelectionChangedListener(new SelectionChangedListener<UserDTO>() { @Override public void selectionChanged(SelectionChangedEvent<UserDTO> se) { String value = null; final boolean isValueOn; // Gets the selected choice. final UserDTO choice = se.getSelectedItem(); // Checks if the choice isn't the default empty choice. isValueOn = choice != null && choice.getId() != null && choice.getId() != -1; if (choice != null) { value = String.valueOf(choice.getId()); } if (value != null) { // Fires value change event. handlerManager.fireEvent(new ValueEvent(DefaultFlexibleElementDTO.this, value)); } // Required element ? if (getValidates()) { handlerManager.fireEvent(new RequiredValueEvent(isValueOn)); } } }); field = comboBox; } else { final LabelField labelField = createLabelField(); if (manager == null) { labelField.setValue(EMPTY_VALUE); } else { labelField.setValue(manager.getFirstName() != null ? manager.getFirstName() + ' ' + manager.getName() : manager.getName()); } field = labelField; } // Sets the field label. setLabel(I18N.CONSTANTS.projectManager()); field.setFieldLabel(getLabel()); return field; } /** * Creates the manager field. * * @param managerId ID of the manager. * @param enabled <code>true</code> if the field must be editable, <code>false</code> otherwise. * @return The manager field. */ private Field<?> buildManagerField(String managerId, boolean enabled) { final Field<?> field = buildManagerField((UserDTO) null, enabled); final int userId = Integer.parseInt(managerId); dispatch.execute(new GetUsersByOrganization(auth().getOrganizationId(), userId, null), new CommandResultHandler<ListResult<UserDTO>>() { @Override public void onCommandFailure(final Throwable caught) { // Field is already set to null. Nothing to do. } @Override public void onCommandSuccess(final ListResult<UserDTO> result) { // BUGFIX #694: Disable events on first set. field.enableEvents(false); if(!result.isEmpty()) { final UserDTO manager = result.getList().get(0); if(field instanceof ComboBox) { ((ComboBox<UserDTO>)field).setValue(manager); } else if(field instanceof LabelField) { ((LabelField)field).setValue(manager.getFirstName() != null ? manager.getFirstName() + ' ' + manager.getName() : manager.getName()); } } field.enableEvents(true); } }); return field; } @Override protected void addOrgUnitSelectionChangedListener(final ComboBox<OrgUnitDTO> comboBox) { comboBox.addSelectionChangedListener(new SelectionChangedListener<OrgUnitDTO>() { @Override public void selectionChanged(final SelectionChangedEvent<OrgUnitDTO> se) { final boolean countryChanged = isProjectCountryChanged(se); // Action called to save the new value. final Runnable fireChangeEventRunnable = new Runnable() { @Override public void run() { String value = null; final boolean isValueOn; // Gets the selected choice. final OrgUnitDTO choice = se.getSelectedItem(); // Checks if the choice isn't the default empty choice. isValueOn = choice != null && choice.getId() != null && choice.getId() != -1; if (choice != null) { value = String.valueOf(choice.getId()); } if (value != null) { // Fires value change event. handlerManager.fireEvent(new ValueEvent(DefaultFlexibleElementDTO.this, value, countryChanged)); } // Required element ? if (getValidates()) { handlerManager.fireEvent(new RequiredValueEvent(isValueOn)); } } }; if (countryChanged) { Log.debug("Country changed."); final Filter filter = new Filter(); filter.addRestriction(DimensionType.Database, container.getId()); dispatch.execute(new GetSitesCount(filter), new CommandResultHandler<SiteResult>() { @Override public void onCommandSuccess(final SiteResult result) { if (result != null && result.getSiteCount() > 0) { // If the new OrgUnit's country different from the current country of project inform users // that it will continue use the country of project not new OrgUnit's. Log.debug("[getSitesCountCmd]-Site count is: " + result.getSiteCount()); N10N.confirmation(I18N.CONSTANTS.changeOrgUnit(), I18N.CONSTANTS.changeOrgUnitDetails(), new ConfirmCallback() { // YES callback. @Override public void onAction() { fireChangeEventRunnable.run(); } }, new ConfirmCallback() { // NO callback. @Override public void onAction() { comboBox.setValue(orgUnitsStore.findModel(OrgUnitDTO.ID, container.getOrgUnitId())); } }); } else { fireChangeEventRunnable.run(); } } }); } else { // Non project container Log.debug("Country did not changed."); fireChangeEventRunnable.run(); } } }); } /** * Checks if the country of the enclosing project was changed. * * @param event * Change event fired by an OrgUnit default flexible element. * @return <code>true</code> if the container is a project and if the new * org unit has a different country, <code>false</code> otherwise. */ private boolean isProjectCountryChanged(final SelectionChangedEvent<OrgUnitDTO> event) { if (container instanceof ProjectDTO) { // Gets the selected choice. final OrgUnitDTO choice = event.getSelectedItem(); // Current poject's country final CountryDTO projectCountry = container.getCountry(); // New OrgUnit's country final CountryDTO orgUnitCountry = choice != null ? choice.getOfficeLocationCountry() : null; if (projectCountry == null) { return orgUnitCountry != null; } else if (orgUnitCountry == null) { // Rejecting changes to null values. return false; } else { return !projectCountry.equals(orgUnitCountry); } } else { // No country change if the container is not a project. return false; } } private String formatManager(String value) { if (cache != null) { try { final UserDTO u = cache.getUserCache().get(Integer.valueOf(value)); if (u != null) { return u.getFirstName() != null ? u.getFirstName() + ' ' + u.getName() : u.getName(); } else { return '#' + value; } } catch(NumberFormatException e) { return ""; } } else { return '#' + value; } } /** * Creates and populates the shared user store if needed. */ private void ensureUserStore(final ConfirmCallback onLoad) { if (usersStore == null) { usersStore = new ListStore<UserDTO>(); } if (usersStore.getCount() == 0) { if (cache != null) { cache.getUserCache().get(new AsyncCallback<List<UserDTO>>() { @Override public void onFailure(final Throwable e) { Log.error("[getComponent] Error while getting users list.", e); } @Override public void onSuccess(final List<UserDTO> result) { // Fills the store. usersStore.add(result); if(onLoad != null) { onLoad.onAction(); } } }); } else /* cache is null */ { dispatch.execute(new GetUsersByOrganization(auth().getOrganizationId(), null), new CommandResultHandler<ListResult<UserDTO>>() { @Override public void onCommandFailure(final Throwable caught) { Log.error("[getComponent] Error while getting users list.", caught); } @Override public void onCommandSuccess(final ListResult<UserDTO> result) { // Fills the store. usersStore.add(result.getList()); if(onLoad != null) { onLoad.onAction(); } } }); } } else if(onLoad != null) { onLoad.onAction(); } } @Override protected Field<?> buildCountryField(CountryDTO country, boolean enabled) { // COUNTRY of project should not be changeable except OrgUnit's enabled &= !(currentContainerDTO instanceof ProjectDTO); return super.buildCountryField(country, enabled); } @Override protected Field<?> buildOrgUnitField(String label, Integer orgUnitId, boolean enabled) { // Org unit field is always read-only for org unit. enabled &= !(container instanceof OrgUnitDTO); return super.buildOrgUnitField(label, orgUnitId, enabled); } @Override public Object renderHistoryToken(HistoryTokenListDTO token) { ensureHistorable(); final String value = token.getTokens().get(0).getValue(); if (getType() != null) { switch (getType()) { case COUNTRY: return new HistoryTokenText(formatCountry(value)); case START_DATE: case END_DATE: return new HistoryTokenText(formatDate(value)); case MANAGER: return new HistoryTokenText(formatManager(value)); case ORG_UNIT: return new HistoryTokenText(formatOrgUnit(value)); default: return super.renderHistoryToken(token); } } else { return super.renderHistoryToken(token); } } @Override public String toHTML(String value) { if(value == null || value.length() == 0) { return ""; } if (getType() != null) { switch (getType()) { case COUNTRY: return formatCountry(value); case START_DATE: case END_DATE: return formatDate(value); case MANAGER: return formatManager(value); case ORG_UNIT: return formatOrgUnit(value); default: return formatText(value); } } else { return formatText(value); } } }