package org.sigmah.client.ui.presenter;
/*
* #%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.HashMap;
import java.util.Map;
import java.util.Set;
import org.sigmah.client.dispatch.CommandResultHandler;
import org.sigmah.client.event.UpdateEvent;
import org.sigmah.client.i18n.I18N;
import org.sigmah.client.inject.Injector;
import org.sigmah.client.page.Page;
import org.sigmah.client.page.PageRequest;
import org.sigmah.client.page.RequestParameter;
import org.sigmah.client.ui.notif.ConfirmCallback;
import org.sigmah.client.ui.notif.N10N;
import org.sigmah.client.ui.presenter.base.AbstractPagePresenter;
import org.sigmah.client.ui.view.CreateProjectView;
import org.sigmah.client.ui.view.base.ViewInterface;
import org.sigmah.client.ui.widget.Loadable;
import org.sigmah.client.ui.widget.button.Button;
import org.sigmah.client.ui.widget.form.FormPanel;
import org.sigmah.client.util.ClientUtils;
import org.sigmah.client.util.MessageType;
import org.sigmah.client.util.NumberUtils;
import org.sigmah.shared.command.CreateEntity;
import org.sigmah.shared.command.GetOrgUnits;
import org.sigmah.shared.command.GetProjectModels;
import org.sigmah.shared.command.GetTestProjects;
import org.sigmah.shared.command.UpdateEntity;
import org.sigmah.shared.command.result.CreateResult;
import org.sigmah.shared.command.result.ListResult;
import org.sigmah.shared.command.result.VoidResult;
import org.sigmah.shared.dto.ProjectDTO;
import org.sigmah.shared.dto.ProjectModelDTO;
import org.sigmah.shared.dto.orgunit.OrgUnitDTO;
import org.sigmah.shared.dto.referential.ProjectModelStatus;
import org.sigmah.shared.dto.referential.ProjectModelType;
import com.allen_sauer.gwt.log.client.Log;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ButtonEvent;
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.event.SelectionListener;
import com.extjs.gxt.ui.client.store.StoreEvent;
import com.extjs.gxt.ui.client.store.StoreListener;
import com.extjs.gxt.ui.client.widget.form.ComboBox;
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.extjs.gxt.ui.client.widget.grid.Grid;
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* Create project presenter.
*
* @author Tom Miette (tmiette@ideia.fr)
*/
@Singleton
public class CreateProjectPresenter extends AbstractPagePresenter<CreateProjectPresenter.View> {
/**
* View interface.
*/
@ImplementedBy(CreateProjectView.class)
public static interface View extends ViewInterface {
void setTitle(String title);
FormPanel getFormPanel();
TextField<String> getNameField();
TextField<String> getFullNameField();
NumberField getBudgetField();
ComboBox<OrgUnitDTO> getOrgUnitsField();
ComboBox<ProjectModelDTO> getModelsField();
void setProjectModelType(ProjectModelType type);
LabelField getBaseProjectBudgetField();
NumberField getAmountField();
LabelField getPercentageField();
Button getCreateButton();
void setDeleteTestProjectAction(DeleteTestProjectAction deleteTestProjectAction);
Grid<ProjectDTO> getTestProjectsField();
}
/**
* Defines the different modes for the creation.
*
* @author Tom Miette (tmiette@ideia.fr)
*/
public static enum Mode {
/**
* Create project mode.
*/
PROJECT,
/**
* Create a project which is funding the base project (must be passed through the page request).
*/
FUNDING_ANOTHER_PROJECT,
/**
* Create a project which is funded by the base project (must be passed through the page request).
*/
FUNDED_BY_ANOTHER_PROJECT,
/**
* Create a test project.
*/
TEST_PROJECT;
private static final Mode DEFAULT_MODE = PROJECT;
private static Mode fromRequestParameterValue(String param) {
if (ClientUtils.isNotBlank(param)) {
for (final Mode m : Mode.values()) {
if (param.equalsIgnoreCase(m.name())) {
return m;
}
}
}
return null;
}
}
/**
* The action to delete a test project. This action is passed to the view.
*
* @author Tom Miette (tmiette@ideia.fr)
*/
public static interface DeleteTestProjectAction {
/**
* Deletes the given test project.
*
* @param testProject
* The test project.
* @param loadable
* The corresponding {@link Loadable} component.
*/
void deleteTestProject(ProjectDTO testProject, Loadable loadable);
}
// Utility constants.
private static final double ZERO = 0.0d;
private static final String ZERO_PERCENT = "0 %";
/**
* The current creation mode.
*/
private Mode currentMode;
/**
* The base project for funding / funded case.
*/
private ProjectDTO baseProject;
@Inject
public CreateProjectPresenter(final View view, final Injector injector) {
super(view, injector);
}
/**
* {@inheritDoc}
*/
@Override
public Page getPage() {
return Page.CREATE_PROJECT;
}
/**
* {@inheritDoc}
*/
@Override
public void onBind() {
// Project models store listeners.
view.getModelsField().getStore().addStoreListener(new StoreListener<ProjectModelDTO>() {
@Override
public void storeClear(StoreEvent<ProjectModelDTO> se) {
view.getModelsField().setEnabled(false);
}
@Override
public void storeAdd(StoreEvent<ProjectModelDTO> se) {
view.getModelsField().setEnabled(true);
}
});
// Org unit models store listeners.
view.getOrgUnitsField().getStore().addStoreListener(new StoreListener<OrgUnitDTO>() {
@Override
public void storeClear(StoreEvent<OrgUnitDTO> se) {
view.getOrgUnitsField().setEnabled(false);
}
@Override
public void storeAdd(StoreEvent<OrgUnitDTO> se) {
view.getOrgUnitsField().setEnabled(true);
}
});
// Project models field listener.
view.getModelsField().addSelectionChangedListener(new SelectionChangedListener<ProjectModelDTO>() {
@Override
public void selectionChanged(SelectionChangedEvent<ProjectModelDTO> se) {
final ProjectModelType type;
if (se != null && se.getSelectedItem() != null) {
type = se.getSelectedItem().getVisibility(injector.getAuthenticationProvider().get().getOrganizationId());
} else {
type = null;
}
view.setProjectModelType(type);
}
});
// Amount field listener.
view.getAmountField().addListener(Events.Change, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
final NumberField budgetField = view.getBudgetField();
final NumberField amountField = view.getAmountField();
final LabelField percentageField = view.getPercentageField();
// Checks that values are filled.
if (amountField.getValue() == null || amountField.getValue().doubleValue() < ZERO) {
amountField.setValue(ZERO);
}
if (budgetField.getValue() == null || budgetField.getValue().doubleValue() < ZERO) {
budgetField.setValue(ZERO);
amountField.setValue(ZERO);
}
// If there is no base project, the percentage cannot be computed.
if (baseProject == null) {
percentageField.setValue(I18N.CONSTANTS.createProjectPercentageNotAvailable());
return;
}
// --
// -- Computes the ratio between the amount and the base project.
// --
final double baseBudget = baseProject.getPlannedBudget() != null ? baseProject.getPlannedBudget().doubleValue() : ZERO;
final double budget = budgetField.getValue().doubleValue();
final double amountBudget = amountField.getValue().doubleValue();
final double minBudget = Math.min(baseBudget, budget);
switch (currentMode) {
// --
// -- CASE: the created project is funded by the base project.
// --
case FUNDED_BY_ANOTHER_PROJECT:
// If the budget of the created project is zero, the it is funded to the amount of 0%.
if (budget <= ZERO) {
amountField.setValue(ZERO);
percentageField.setValue(ZERO_PERCENT);
}
// Else if the amount budget is higher than the minimum budget between the created project and the base
// project, the amount budget is set the that minimum. Then, computes the ratio.
else if (minBudget < amountBudget) {
amountField.setValue(minBudget);
percentageField.setValue(NumberUtils.ratioAsString(minBudget, budget));
}
// Else, simply computes the ratio.
else {
percentageField.setValue(NumberUtils.ratioAsString(amountBudget, budget));
}
break;
// --
// -- CASE: the created project is funding the base project.
// --
case FUNDING_ANOTHER_PROJECT:
// If the budget of the base project is zero, then it is funded to the amount of 0%.
if (baseBudget <= ZERO) {
percentageField.setValue(ZERO_PERCENT);
}
// If the budget of the created project is zero, the it is funding the base project to the amount of 0%.
else if (budget <= ZERO) {
amountField.setValue(ZERO);
percentageField.setValue(ZERO_PERCENT);
}
// Else if the amount budget is higher than the minimum budget between the created project and the base
// project, the amount budget is set the that minimum. Then, computes the ratio.
else if (minBudget < amountBudget) {
amountField.setValue(minBudget);
percentageField.setValue(NumberUtils.ratioAsString(minBudget, baseBudget));
}
// Else, simply computes the ratio.
else {
percentageField.setValue(NumberUtils.ratioAsString(amountBudget, baseBudget));
}
break;
// --
// -- CASE: creates a test project (must not happen).
// --
case TEST_PROJECT:
budgetField.setValue(ZERO);
amountField.setValue(ZERO);
percentageField.setValue(I18N.CONSTANTS.createProjectPercentageNotAvailable());
break;
// --
// -- CASE: creates a simple project (must not happen).
// --
default:
amountField.setValue(ZERO);
percentageField.setValue(I18N.CONSTANTS.createProjectPercentageNotAvailable());
break;
}
}
});
// Delete button action.
view.setDeleteTestProjectAction(new DeleteTestProjectAction() {
@Override
public void deleteTestProject(final ProjectDTO testProject, final Loadable loadable) {
// Request confirm test project delete
N10N.confirmation(I18N.CONSTANTS.deleteTestProjectHeader(), I18N.CONSTANTS.deleteTestProjectConfirm(), new ConfirmCallback() {
@Override
public void onAction() {
// Builds the command.
final Map<String, Object> changes = new HashMap<String, Object>();
changes.put("dateDeleted", new Date());
// Executes the command.
dispatch.execute(new UpdateEntity(testProject, changes), new CommandResultHandler<VoidResult>() {
@Override
public void onCommandFailure(final Throwable arg0) {
N10N.error(I18N.CONSTANTS.createProjectFailed(), I18N.CONSTANTS.deleteTestProject());
}
@Override
public void onCommandSuccess(final VoidResult result) {
view.getTestProjectsField().getStore().remove(testProject);
view.getTestProjectsField().getStore().commitChanges();
N10N.notification(I18N.CONSTANTS.deleteProjectNotificationTitle(), I18N.CONSTANTS.deleteProjectNotificationContent(), MessageType.VALID);
eventBus.fireEvent(new UpdateEvent(UpdateEvent.PROJECT_DRAFT_DELETE, testProject));
}
}, loadable, view.getCreateButton());
}
});
}
});
// Create button action.
view.getCreateButton().addSelectionListener(new SelectionListener<ButtonEvent>() {
@Override
public void componentSelected(ButtonEvent ce) {
createProject();
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void onPageRequest(final PageRequest request) {
// Retrieves the current creation mode.
currentMode = Mode.fromRequestParameterValue(request != null ? request.getParameter(RequestParameter.TYPE) : null);
if (currentMode == null) {
currentMode = Mode.DEFAULT_MODE;
}
// Retrieves the funded / funding project.
baseProject = null;
if (currentMode == Mode.FUNDED_BY_ANOTHER_PROJECT || currentMode == Mode.FUNDING_ANOTHER_PROJECT) {
baseProject = (ProjectDTO) (request != null ? request.getData(RequestParameter.DTO) : null);
}
// Clears the form.
view.getFormPanel().clearAll();
view.getModelsField().setEnabled(false);
view.getOrgUnitsField().setEnabled(false);
view.getTestProjectsField().getStore().removeAll();
// Fill pre-defined values if provided.
String preselectedModelName = null;
if(request != null) {
// Code.
if(request.getData(RequestParameter.CODE) != null) {
view.getNameField().setValue((String) request.getData(RequestParameter.CODE));
}
// Title.
if(request.getData(RequestParameter.TITLE) != null) {
view.getFullNameField().setValue((String) request.getData(RequestParameter.TITLE));
}
// Planned budget.
if(request.getData(RequestParameter.BUDGET) != null) {
view.getBudgetField().setValue((Double) request.getData(RequestParameter.BUDGET));
}
// Project model name.
if(request.getData(RequestParameter.MODEL) != null) {
preselectedModelName = (String) request.getData(RequestParameter.MODEL);
}
}
// Loads the lists.
loadOrgUnits(currentMode);
loadProjectModels(currentMode, preselectedModelName);
loadTestProjects(currentMode);
// --
// -- Customizes the form for the current mode.
// --
final boolean showBudgetField;
final boolean showOrgUnitsField;
final boolean showAmountField;
final String amountFieldLabel;
final boolean showTestProjectsField;
final String viewTitle;
final String baseProjectBudgetLabel;
final Double baseProjectBudget;
switch (currentMode) {
// --
// -- CASE: the created project is funded by the base project.
// --
case FUNDED_BY_ANOTHER_PROJECT:
showBudgetField = true;
showOrgUnitsField = true;
showAmountField = true;
amountFieldLabel = I18N.MESSAGES.projectFinancesDetails(baseProject.getName()) + " (" + I18N.CONSTANTS.currencyEuro() + ')';
baseProjectBudgetLabel = null;
baseProjectBudget = null;
showTestProjectsField = false;
viewTitle = I18N.CONSTANTS.createProjectTypePartnerCreateDetails();
break;
// --
// -- CASE: the created project is funding the base project.
// --
case FUNDING_ANOTHER_PROJECT:
showBudgetField = true;
showOrgUnitsField = true;
showAmountField = true;
amountFieldLabel = I18N.MESSAGES.projectFundedByDetails(baseProject.getName()) + " (" + I18N.CONSTANTS.currencyEuro() + ')';
baseProjectBudgetLabel = I18N.MESSAGES.projectFundsDetails(baseProject.getName()) + " (" + I18N.CONSTANTS.currencyEuro() + ')' + I18N.CONSTANTS.form_label_separator();
baseProjectBudget = baseProject.getPlannedBudget();
showTestProjectsField = false;
viewTitle = I18N.CONSTANTS.createProjectTypeFundingCreateDetails();
break;
// --
// -- CASE: creates a test project.
// --
case TEST_PROJECT:
showBudgetField = false;
showOrgUnitsField = false;
showAmountField = false;
amountFieldLabel = null;
baseProjectBudgetLabel = null;
baseProjectBudget = null;
showTestProjectsField = true;
viewTitle = I18N.CONSTANTS.createProjectTest();
break;
// --
// -- CASE: creates a simple project.
// --
default:
showBudgetField = true;
showOrgUnitsField = true;
showAmountField = false;
amountFieldLabel = null;
baseProjectBudgetLabel = null;
baseProjectBudget = null;
showTestProjectsField = false;
viewTitle = I18N.CONSTANTS.createProject();
break;
}
// Budget field.
view.getBudgetField().setVisible(showBudgetField);
view.getBudgetField().setAllowBlank(!showBudgetField);
// Org units field.
view.getOrgUnitsField().setVisible(showOrgUnitsField);
view.getOrgUnitsField().setAllowBlank(!showOrgUnitsField);
// Linked budget field.
view.getBaseProjectBudgetField().setFieldLabel(baseProjectBudgetLabel);
view.getBaseProjectBudgetField().setValue(baseProjectBudget != null ? baseProjectBudget : ZERO);
view.getBaseProjectBudgetField().setVisible(baseProjectBudget != null);
// Amount field.
view.getAmountField().setValue(ZERO);
view.getAmountField().setVisible(showAmountField);
view.getAmountField().setAllowBlank(!showAmountField);
view.getAmountField().setFieldLabel(amountFieldLabel);
// Percentage field.
view.getPercentageField().setVisible(showAmountField);
// Test projects field.
view.getTestProjectsField().setVisible(showTestProjectsField);
// View title.
view.setTitle(viewTitle);
}
/**
* Loads the organization units list.
*
* @param mode
* The current creation mode.
*/
private void loadOrgUnits(final Mode mode) {
dispatch.execute(new GetOrgUnits(OrgUnitDTO.Mode.WITH_TREE), new CommandResultHandler<ListResult<OrgUnitDTO>>() {
@Override
public void onCommandFailure(Throwable e) {
if (Log.isErrorEnabled()) {
Log.error("Error while getting the organization units list.", e);
}
N10N.error(I18N.CONSTANTS.createProjectDisableOrgUnitError());
hideView();
}
@Override
public void onCommandSuccess(final ListResult<OrgUnitDTO> result) {
if (result == null) {
N10N.warn(I18N.CONSTANTS.createProjectDisableOrgUnit());
hideView();
return;
}
for (OrgUnitDTO orgUnitDTO : result.getData()) {
fillOrgUnitsCombobox(orgUnitDTO);
}
}
}, view.getCreateButton());
}
/**
* Fills combobox with given the children of the given root org units.
*
* @param unit
* The root org unit.
*/
private void fillOrgUnitsCombobox(OrgUnitDTO unit) {
if (unit.isCanContainProjects() && view.getOrgUnitsField().getStore().findModel(OrgUnitDTO.ID, unit.getId()) == null) {
view.getOrgUnitsField().getStore().add(unit);
}
final Set<OrgUnitDTO> children = unit.getChildrenOrgUnits();
if (children != null && !children.isEmpty()) {
for (final OrgUnitDTO child : children) {
fillOrgUnitsCombobox(child);
}
}
}
/**
* Loads the project models list.
*
* @param mode
* The current creation mode.
* @param preselectedModelName
* If not <code>null</code> and found, select this model in the list.
*/
private void loadProjectModels(final Mode mode, final String preselectedModelName) {
final GetProjectModels command;
if (mode == Mode.TEST_PROJECT) {
command = new GetProjectModels(ProjectModelDTO.Mode.WITH_VISIBILITIES, ProjectModelStatus.DRAFT);
} else {
command = new GetProjectModels(ProjectModelDTO.Mode.WITH_VISIBILITIES);
}
dispatch.execute(command, new CommandResultHandler<ListResult<ProjectModelDTO>>() {
@Override
public void onCommandFailure(final Throwable e) {
if (Log.isErrorEnabled()) {
Log.error("Error while getting the project models list.", e);
}
N10N.error(I18N.CONSTANTS.createProjectDisableModelError());
hideView();
}
@Override
public void onCommandSuccess(final ListResult<ProjectModelDTO> result) {
if (result.isEmpty()) {
N10N.warn(I18N.CONSTANTS.createProjectDisableModel());
hideView();
return;
}
view.getModelsField().getStore().add(result.getList());
view.getModelsField().getStore().commitChanges();
if(preselectedModelName != null) {
for(final ProjectModelDTO projectModel : result.getList()) {
if(preselectedModelName.equals(projectModel.getName())) {
view.getModelsField().setValue(projectModel);
}
}
}
}
}, view.getCreateButton());
}
/**
* Loads the test projects list.
*
* @param mode
* The current creation mode.
*/
private void loadTestProjects(final Mode mode) {
if (mode != Mode.TEST_PROJECT) {
// Nothing to do.
return;
}
dispatch.execute(new GetTestProjects(), new CommandResultHandler<ListResult<ProjectDTO>>() {
@Override
public void onCommandFailure(final Throwable e) {
if (Log.isErrorEnabled()) {
Log.error("Error while getting the test projects list.", e);
}
N10N.error(I18N.CONSTANTS.createProjectDisableModelError());
hideView();
}
@Override
public void onCommandSuccess(final ListResult<ProjectDTO> result) {
view.getTestProjectsField().getStore().add(result.getList());
view.getTestProjectsField().getStore().commitChanges();
}
}, view.getCreateButton());
}
/**
* Create the project with the current form values.
*/
private void createProject() {
// Checks that the form is valid.
if (!view.getFormPanel().isValid()) {
return;
}
// Retrieves values.
final String name = view.getNameField().getValue();
final String fullName = view.getFullNameField().getValue();
final double budget = view.getBudgetField().getValue() != null ? view.getBudgetField().getValue().doubleValue() : ZERO;
final Integer projectModelId = view.getModelsField().getValue() != null ? view.getModelsField().getValue().getId() : null;
final String orgUnitId = view.getOrgUnitsField().getValue() != null ? String.valueOf(view.getOrgUnitsField().getValue().getId()) : null;
if (Log.isDebugEnabled()) {
final StringBuilder sb = new StringBuilder();
sb.append("Create a new project with parameters: ");
sb.append("name = ");
sb.append(name);
sb.append(" ; full name = ");
sb.append(fullName);
sb.append(" ; budget = ");
sb.append(budget);
sb.append(" ; model id = ");
sb.append(projectModelId);
sb.append(" ; org unit id = ");
sb.append(orgUnitId);
Log.debug(sb.toString());
}
// Stores the project properties in a map to be send to the server.
final HashMap<String, Object> projectProperties = new HashMap<String, Object>();
projectProperties.put(ProjectDTO.NAME, name);
projectProperties.put(ProjectDTO.FULL_NAME, fullName);
projectProperties.put(ProjectDTO.BUDGET, budget);
projectProperties.put(ProjectDTO.MODEL_ID, projectModelId);
projectProperties.put(ProjectDTO.ORG_UNIT_ID, orgUnitId);
projectProperties.put(ProjectDTO.CALENDAR_NAME, I18N.CONSTANTS.calendarDefaultName());
projectProperties.put(ProjectDTO.CREATION_MODE, currentMode);
if(currentMode == Mode.FUNDING_ANOTHER_PROJECT || currentMode == Mode.FUNDED_BY_ANOTHER_PROJECT) {
projectProperties.put(ProjectDTO.AMOUNT, view.getAmountField().getValue().doubleValue());
projectProperties.put(ProjectDTO.BASE_PROJECT, baseProject);
}
// Creates the project.
dispatch.execute(new CreateEntity(ProjectDTO.ENTITY_NAME, projectProperties), new CommandResultHandler<CreateResult>() {
@Override
public void onCommandFailure(Throwable arg0) {
N10N.error(I18N.CONSTANTS.createProjectFailed(), I18N.CONSTANTS.createProjectFailedDetails(), null);
}
@Override
public void onCommandSuccess(CreateResult result) {
final ProjectDTO project = (ProjectDTO) result.getEntity();
if (Log.isDebugEnabled()) {
Log.debug("Project created with id #" + project.getId() + ".");
}
// --
// -- Launches the creation event.
// --
final Object[] eventParams = new Object[3];
eventParams[0] = currentMode; // the creation mode.
eventParams[1] = project; // The created project.
switch (currentMode) {
case FUNDED_BY_ANOTHER_PROJECT:
eventParams[2] = view.getAmountField().getValue().doubleValue(); // The funded amount.
break;
case FUNDING_ANOTHER_PROJECT:
eventParams[2] = view.getAmountField().getValue().doubleValue(); // The funding amount.
break;
case TEST_PROJECT:
// Add the new test project to the test projects field.
view.getTestProjectsField().getStore().add(project);
view.getTestProjectsField().getStore().commitChanges();
break;
default:
break;
}
// Hides the view unless the test mode is active.
if (currentMode != Mode.TEST_PROJECT) {
hideView();
}
// Notifies the project creation.
eventBus.fireEvent(new UpdateEvent(UpdateEvent.PROJECT_CREATE, eventParams));
}
}, view.getCreateButton());
}
}