/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 WirelessGalicia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.expensesheet;
import static org.libreplan.web.I18nHelper._;
import java.math.BigDecimal;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.LogFactory;
import org.joda.time.LocalDate;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.expensesheet.entities.ExpenseSheet;
import org.libreplan.business.expensesheet.entities.ExpenseSheetLine;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.users.entities.UserRole;
import org.libreplan.web.common.BaseCRUDController;
import org.libreplan.web.common.IndexController;
import org.libreplan.web.common.Level;
import org.libreplan.web.common.Util;
import org.libreplan.web.common.components.bandboxsearch.BandboxSearch;
import org.libreplan.web.common.entrypoints.IURLHandlerRegistry;
import org.libreplan.web.common.entrypoints.MatrixParameters;
import org.libreplan.web.security.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.CheckEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Button;
import org.zkoss.zul.Constraint;
import org.zkoss.zul.Datebox;
import org.zkoss.zul.Decimalbox;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Row;
import org.zkoss.zul.RowRenderer;
import org.zkoss.zul.Textbox;
/**
* Controller for CRUD actions over a {@link ExpenseSheet}
*
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
*/
public class ExpenseSheetCRUDController extends
BaseCRUDController<ExpenseSheet> implements IExpenseSheetCRUDController {
private static final org.apache.commons.logging.Log LOG = LogFactory
.getLog(ExpenseSheetCRUDController.class);
@Autowired
private IExpenseSheetModel expenseSheetModel;
/*
* components editWindow
*/
private Datebox dateboxExpenseDate;
private Grid gridExpenseLines;
private BandboxSearch bandboxTasks;
private Decimalbox dboxValue;
private BandboxSearch bandboxResource;
private Textbox tbConcept;
private ExpenseSheetLineRenderer expenseSheetLineRenderer = new ExpenseSheetLineRenderer();
private IURLHandlerRegistry URLHandlerRegistry;
private boolean fromUserDashboard = false;
private boolean cancel = false;
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
checkUserHasProperRoleOrSendForbiddenCode();
URLHandlerRegistry.getRedirectorFor(IExpenseSheetCRUDController.class)
.register(this, page);
}
private void checkUserHasProperRoleOrSendForbiddenCode() {
HttpServletRequest request = (HttpServletRequest) Executions
.getCurrent().getNativeRequest();
Map<String, String> matrixParams = MatrixParameters.extract(request);
// If it doesn't come from a entry point
if (matrixParams.isEmpty()) {
if (!SecurityUtils.isSuperuserOrUserInRoles(UserRole.ROLE_EXPENSES)) {
Util.sendForbiddenStatusCodeInHttpServletResponse();
}
}
}
@Override
public void save() throws ValidationException {
expenseSheetModel.confirmSave();
}
@Override
protected void beforeSaving() throws ValidationException {
super.beforeSaving();
expenseSheetModel.generateExpenseSheetLineCodesIfIsNecessary();
}
private void loadComponentsEditWindow() {
tbConcept = (Textbox) editWindow.getFellowIfAny("tbConcept");
dateboxExpenseDate = (Datebox) editWindow.getFellowIfAny("dateboxExpenseDate");
dboxValue = (Decimalbox) editWindow.getFellowIfAny("dboxValue");
gridExpenseLines = (Grid) editWindow.getFellowIfAny("gridExpenseLines");
bandboxResource = (BandboxSearch) editWindow.getFellowIfAny("bandboxResource");
bandboxTasks = (BandboxSearch) editWindow.getFellowIfAny("bandboxTasks");
}
/*
* Operations in the list window
*/
public List<ExpenseSheet> getExpenseSheets() {
return expenseSheetModel.getExpenseSheets();
}
/*
* Operations in the create window
*/
public ExpenseSheet getExpenseSheet() {
return expenseSheetModel.getExpenseSheet();
}
public SortedSet<ExpenseSheetLine> getExpenseSheetLines() {
return expenseSheetModel.getExpenseSheetLines();
}
/**
* Adds a new {@link ExpenseSheetLine} to the list of rows
*
* @param rows
*/
public void addExpenseSheetLine() {
if (validateNewLine()) {
expenseSheetModel.addExpenseSheetLine();
reloadExpenseSheetLines();
reloadComponentsNewLine();
}
}
private void reloadComponentsNewLine() {
Util.reloadBindings(bandboxTasks);
Util.reloadBindings(bandboxResource);
Util.reloadBindings(tbConcept);
Util.reloadBindings(dboxValue);
}
private boolean validateNewLine() {
boolean result = true;
if (expenseSheetModel.getNewExpenseSheetLine().getDate() == null) {
result = false;
throw new WrongValueException(this.dateboxExpenseDate, _("cannot be empty"));
}
if (expenseSheetModel.getNewExpenseSheetLine().getOrderElement() == null) {
result = false;
throw new WrongValueException(this.bandboxTasks, _("cannot be empty"));
}
BigDecimal value = expenseSheetModel.getNewExpenseSheetLine().getValue();
if (value == null || value.compareTo(BigDecimal.ZERO) < 0) {
result = false;
throw new WrongValueException(this.dboxValue,
_("cannot be empty or less than zero"));
}
return result;
}
public void confirmRemove(ExpenseSheetLine expenseSheetLine) {
try {
int status = Messagebox.show(
_("Confirm deleting {0}. Are you sure?",
getExpenseSheetLineName(expenseSheetLine)), _("Delete"), Messagebox.OK
| Messagebox.CANCEL, Messagebox.QUESTION);
if (Messagebox.OK == status) {
removeExpenseSheetLine(expenseSheetLine);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private void removeExpenseSheetLine(ExpenseSheetLine expenseSheetLine) {
expenseSheetModel.removeExpenseSheetLine(expenseSheetLine);
reloadExpenseSheetLines();
}
private String getExpenseSheetLineName(ExpenseSheetLine expenseSheetLine) {
if (expenseSheetLine != null) {
LocalDate date = expenseSheetLine.getDate();
OrderElement task = expenseSheetLine.getOrderElement();
if (date != null && task != null) {
return _("expense line of the ") + task.getName() + " - " + date;
}
}
return _("item");
}
private void reloadExpenseSheetLines() {
if (gridExpenseLines != null) {
Util.reloadBindings(gridExpenseLines);
}
}
public void onCheckGenerateCode(Event e) {
CheckEvent ce = (CheckEvent) e;
if (ce.isChecked()) {
// we have to auto-generate the code for new objects
try {
expenseSheetModel.setCodeAutogenerated(ce.isChecked());
} catch (ConcurrentModificationException err) {
messagesForUser.showMessage(Level.ERROR, err.getMessage());
}
}
Util.reloadBindings(editWindow);
reloadExpenseSheetLines();
}
public ExpenseSheetLine getNewExpenseSheetLine() {
return expenseSheetModel.getNewExpenseSheetLine();
}
public List<Order> getOrders() {
return expenseSheetModel.getOrders();
}
public List<OrderElement> getTasks() {
return expenseSheetModel.getTasks();
}
public void setProject(Order project) {
expenseSheetModel.setSelectedProject(project);
Util.reloadBindings(bandboxTasks);
}
public Order getProject() {
return expenseSheetModel.getSelectedProject();
}
public ExpenseSheetLineRenderer getExpenseSheetLineRenderer() {
return expenseSheetLineRenderer;
}
/**
* RowRenderer for a @{ExpenseSheetLine} element
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
*/
public class ExpenseSheetLineRenderer implements RowRenderer {
@Override
public void render(Row row, Object data) {
ExpenseSheetLine expenseSheetLine = (ExpenseSheetLine) data;
row.setValue(expenseSheetLine);
appendOrderElementInLines(row);
appendValueInLines(row);
appendConceptInLines(row);
appendDateInLines(row);
appendResourceInLines(row);
appendCode(row);
appendDeleteButton(row);
}
private void appendConceptInLines(Row row) {
final ExpenseSheetLine expenseSheetLine = (ExpenseSheetLine) row.getValue();
final Textbox txtConcept = new Textbox();
txtConcept.setWidth("160px");
Util.bind(txtConcept, new Util.Getter<String>() {
@Override
public String get() {
if (expenseSheetLine != null) {
return expenseSheetLine.getConcept();
}
return "";
}
}, new Util.Setter<String>() {
@Override
public void set(String value) {
if (expenseSheetLine != null) {
expenseSheetLine.setConcept(value);
}
}
});
row.appendChild(txtConcept);
}
private void appendDateInLines(Row row) {
final ExpenseSheetLine expenseSheetLine = (ExpenseSheetLine) row.getValue();
final Datebox dateboxExpense = new Datebox();
Util.bind(dateboxExpense, new Util.Getter<Date>() {
@Override
public Date get() {
if (expenseSheetLine != null) {
if (expenseSheetLine.getDate() != null) {
return expenseSheetLine.getDate().toDateTimeAtStartOfDay().toDate();
}
}
return null;
}
}, new Util.Setter<Date>() {
@Override
public void set(Date value) {
if (expenseSheetLine != null) {
LocalDate newDate = null;
if (value != null) {
newDate = LocalDate.fromDateFields(value);
}
expenseSheetModel.keepSortedExpenseSheetLines(expenseSheetLine,
newDate);
reloadExpenseSheetLines();
}
}
});
dateboxExpense.setConstraint("no empty:" + _("cannot be empty"));
row.appendChild(dateboxExpense);
}
private void appendResourceInLines(Row row) {
final ExpenseSheetLine expenseSheetLine = (ExpenseSheetLine) row.getValue();
final BandboxSearch bandboxSearch = BandboxSearch.create(
"ResourceInExpenseSheetBandboxFinder");
bandboxSearch.setSelectedElement(expenseSheetLine.getResource());
bandboxSearch.setSclass("bandbox-workreport-task");
bandboxSearch.setListboxWidth("450px");
EventListener eventListenerUpdateResource = new EventListener() {
@Override
public void onEvent(Event event) {
Listitem selectedItem = bandboxSearch.getSelectedItem();
setResourceInESL(selectedItem, expenseSheetLine);
}
};
bandboxSearch.setListboxEventListener(Events.ON_SELECT, eventListenerUpdateResource);
bandboxSearch.setListboxEventListener(Events.ON_OK, eventListenerUpdateResource);
bandboxSearch.setBandboxEventListener(Events.ON_CHANGING, eventListenerUpdateResource);
row.appendChild(bandboxSearch);
}
private void appendCode(final Row row) {
final ExpenseSheetLine line = (ExpenseSheetLine) row.getValue();
final Textbox code = new Textbox();
code.setWidth("170px");
code.setDisabled(getExpenseSheet().isCodeAutogenerated());
code.applyProperties();
if (line.getCode() != null) {
code.setValue(line.getCode());
}
code.addEventListener("onChange", new EventListener() {
@Override
public void onEvent(Event event) {
final ExpenseSheetLine line = (ExpenseSheetLine) row.getValue();
line.setCode(code.getValue());
}
});
code.setConstraint(checkConstraintLineCodes(line));
row.appendChild(code);
}
private void appendDeleteButton(final Row row) {
Button delete = new Button("", "/common/img/ico_borrar1.png");
delete.setHoverImage("/common/img/ico_borrar.png");
delete.setSclass("icono");
delete.setTooltiptext(_("Delete"));
delete.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
public void onEvent(Event event) {
confirmRemove((ExpenseSheetLine) row.getValue());
}
});
row.appendChild(delete);
}
private void appendValueInLines(Row row) {
final ExpenseSheetLine expenseSheetLine = (ExpenseSheetLine) row.getValue();
final Decimalbox dbValue = new Decimalbox();
dbValue.setScale(2);
Util.bind(dbValue, new Util.Getter<BigDecimal>() {
@Override
public BigDecimal get() {
if (expenseSheetLine != null) {
return expenseSheetLine.getValue();
}
return BigDecimal.ZERO.setScale(2);
}
}, new Util.Setter<BigDecimal>() {
@Override
public void set(BigDecimal value) {
if (expenseSheetLine != null) {
expenseSheetLine.setValue(value);
}
}
});
dbValue.setConstraint(checkConstraintExpenseValue());
dbValue.setFormat(Util.getMoneyFormat());
row.appendChild(dbValue);
}
/**
* Append a Bandbox @{link OrderElement} to row
*
* @param row
*/
private void appendOrderElementInLines(Row row) {
final ExpenseSheetLine expenseSheetLine = (ExpenseSheetLine) row.getValue();
final BandboxSearch bandboxSearch = BandboxSearch
.create("OrderElementInExpenseSheetBandboxFinder");
bandboxSearch.setSelectedElement(expenseSheetLine.getOrderElement());
bandboxSearch.setSclass("bandbox-workreport-task");
bandboxSearch.setListboxWidth("450px");
EventListener eventListenerUpdateOrderElement = new EventListener() {
@Override
public void onEvent(Event event) {
Listitem selectedItem = bandboxSearch.getSelectedItem();
setOrderElementInESL(selectedItem, expenseSheetLine);
}
};
bandboxSearch
.setListboxEventListener(Events.ON_SELECT, eventListenerUpdateOrderElement);
bandboxSearch.setListboxEventListener(Events.ON_OK, eventListenerUpdateOrderElement);
bandboxSearch.setBandboxEventListener(Events.ON_CHANGING,
eventListenerUpdateOrderElement);
bandboxSearch.setBandboxConstraint("no empty:" + _("cannot be empty"));
row.appendChild(bandboxSearch);
}
private void setOrderElementInESL(Listitem selectedItem, ExpenseSheetLine line) {
OrderElement orderElement = (selectedItem == null ? null : (OrderElement) selectedItem
.getValue());
line.setOrderElement(orderElement);
}
private void setResourceInESL(Listitem selectedItem,
ExpenseSheetLine expenseSheetLine) {
Resource resource = (selectedItem == null ? null : (Resource) selectedItem.getValue());
expenseSheetLine.setResource(resource);
}
}
public Constraint checkConstraintExpenseValue() {
return new Constraint() {
@Override
public void validate(Component comp, Object value) throws WrongValueException {
BigDecimal expenseValue = (BigDecimal) value;
if (expenseValue == null || expenseValue.compareTo(BigDecimal.ZERO) < 0) {
throw new WrongValueException(comp, _("must be greater or equal than 0"));
}
}
};
}
public Constraint checkConstraintLineCodes(final ExpenseSheetLine line) {
return new Constraint() {
@Override
public void validate(Component comp, Object value) throws WrongValueException {
if (!getExpenseSheet().isCodeAutogenerated()) {
String code = (String) value;
if (code == null || code.isEmpty()) {
throw new WrongValueException(comp, _("Code cannot be empty."));
} else {
String oldCode = line.getCode();
line.setCode(code);
if (!getExpenseSheet()
.isNonRepeatedExpenseSheetLinesCodesConstraint()) {
line.setCode(oldCode);
throw new WrongValueException(comp, _("The code must be unique."));
}
}
}
}
};
}
public Constraint checkConstraintExpendeCode() {
return new Constraint() {
@Override
public void validate(Component comp, Object value) throws WrongValueException {
if (!getExpenseSheet().isCodeAutogenerated()) {
String code = (String) value;
if (code == null || code.isEmpty()) {
throw new WrongValueException(comp,
_("The code cannot be empty and it must be unique."));
} else if (!getExpenseSheet().isUniqueCodeConstraint()) {
throw new WrongValueException(comp,
_("it already exists another expense sheet with the same code."));
}
}
}
};
}
public Date getExpenseSheetLineDate() {
if (expenseSheetModel.getNewExpenseSheetLine() != null) {
return (expenseSheetModel.getNewExpenseSheetLine().getDate() != null) ? expenseSheetModel
.getNewExpenseSheetLine().getDate().toDateTimeAtStartOfDay().toDate()
: null;
}
return null;
}
public void setExpenseSheetLineDate(Date date) {
if (expenseSheetModel.getNewExpenseSheetLine() != null) {
LocalDate localDate = null;
if (date != null) {
localDate = LocalDate.fromDateFields(date);
}
expenseSheetModel.getNewExpenseSheetLine().setDate(localDate);
}
}
@Override
protected void initCreate() {
initCreate(false);
}
@Override
protected void initEdit(ExpenseSheet expenseSheet) {
expenseSheetModel.prepareToEdit(expenseSheet);
loadComponentsEditWindow();
}
@Override
protected ExpenseSheet getEntityBeingEdited() {
return expenseSheetModel.getExpenseSheet();
}
@Override
public void delete(ExpenseSheet expenseSheet)
throws InstanceNotFoundException {
expenseSheetModel.removeExpenseSheet(expenseSheet);
}
@Override
protected String getEntityType() {
return _("Expense Sheet");
}
@Override
protected String getPluralEntityType() {
return _("Expense Sheets");
}
public String getCurrencySymbol() {
return Util.getCurrencySymbol();
}
public String getMoneyFormat() {
return Util.getMoneyFormat();
}
@Override
public void goToCreatePersonalExpenseSheet() {
if (!SecurityUtils.isUserInRole(UserRole.ROLE_BOUND_USER)) {
Util.sendForbiddenStatusCodeInHttpServletResponse();
}
state = CRUDControllerState.CREATE;
initCreate(true);
showEditWindow();
fromUserDashboard = true;
}
private void initCreate(boolean personal) {
expenseSheetModel.initCreate(personal);
loadComponentsEditWindow();
}
public String getResource() {
Resource resource = expenseSheetModel.getResource();
return resource == null ? "" : resource.getShortDescription();
}
@Override
public void goToEditPersonalExpenseSheet(ExpenseSheet expenseSheet) {
if (!SecurityUtils.isUserInRole(UserRole.ROLE_BOUND_USER)
|| !expenseSheetModel
.isPersonalAndBelognsToCurrentUser(expenseSheet)) {
Util.sendForbiddenStatusCodeInHttpServletResponse();
}
goToEditForm(expenseSheet);
fromUserDashboard = true;
}
@Override
protected void showListWindow() {
if (fromUserDashboard) {
String url = IndexController.USER_DASHBOARD_URL;
if (!cancel) {
url += "?expense_sheet_saved="
+ expenseSheetModel.getExpenseSheet().getCode();
}
Executions.getCurrent().sendRedirect(url);
} else {
super.showListWindow();
}
}
@Override
protected void cancel() {
cancel = true;
}
public String getType() {
return getType(expenseSheetModel.getExpenseSheet());
}
private String getType(ExpenseSheet expenseSheet) {
if (expenseSheet != null && expenseSheet.isPersonal()) {
return _("Personal");
}
return _("Regular");
}
public RowRenderer getExpenseSheetsRenderer() {
return new RowRenderer() {
@Override
public void render(Row row, Object data) throws Exception {
final ExpenseSheet expenseSheet = (ExpenseSheet) data;
row.setValue(expenseSheet);
Util.appendLabel(row, expenseSheet.getFirstExpense().toString());
Util.appendLabel(row, expenseSheet.getLastExpense().toString());
Util.appendLabel(row,
Util.addCurrencySymbol(expenseSheet.getTotal()));
Util.appendLabel(row, expenseSheet.getCode());
Util.appendLabel(row, expenseSheet.getDescription());
Util.appendLabel(row, getType(expenseSheet));
Util.appendOperationsAndOnClickEvent(row, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
goToEditForm(expenseSheet);
}
}, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
confirmDelete(expenseSheet);
}
});
}
};
}
}