/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, 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.costcategories;
import static org.libreplan.web.I18nHelper._;
import java.math.BigDecimal;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.costcategories.entities.CostCategory;
import org.libreplan.business.costcategories.entities.HourCost;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.web.common.BaseCRUDController;
import org.libreplan.web.common.Level;
import org.libreplan.web.common.Util;
import org.zkoss.ganttz.util.ComponentsFinder;
import org.zkoss.zk.ui.Component;
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.zkplus.spring.SpringUtil;
import org.zkoss.zul.Button;
import org.zkoss.zul.Datebox;
import org.zkoss.zul.Decimalbox;
import org.zkoss.zul.Grid;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Row;
import org.zkoss.zul.RowRenderer;
import org.zkoss.zul.Rows;
import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.Textbox;
/**
* Controller for CRUD actions over a {@link CostCategory}.
*
* @author Jacobo Aragunde Perez <jaragunde@igalia.com>
* @author Diego Pino García <dpino@igalia.com>
*/
@SuppressWarnings("serial")
public class CostCategoryCRUDController extends BaseCRUDController<CostCategory> {
private ICostCategoryModel costCategoryModel;
private Grid listHourCosts;
private Grid listCostCategories;
private ListModel allHoursType;
private HourCostListRenderer hourCostListRenderer = new HourCostListRenderer();
public CostCategoryCRUDController() {
if ( costCategoryModel == null ) {
costCategoryModel = (ICostCategoryModel) SpringUtil.getBean("costCategoryModel");
}
}
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
listHourCosts = (Grid) editWindow.getFellowIfAny("listHourCosts");
listCostCategories = (Grid) listWindow.getFellowIfAny("listing");
// Renders grid and disables delete button in case it cannot be removed
listCostCategories.addEventListener("onInitRender", new EventListener() {
@Override
public void onEvent(Event event) {
listCostCategories.renderAll();
final Rows rows = listCostCategories.getRows();
for (Iterator i = rows.getChildren().iterator(); i.hasNext(); ) {
final Row row = (Row) i.next();
final CostCategory category = row.getValue();
Button btnDelete = (Button) row.getChildren().get(2).getChildren().get(1);
if (!canRemoveCostCategory(category)) {
btnDelete.setDisabled(true);
btnDelete.setImage("/common/img/ico_borrar_out.png");
btnDelete.setHoverImage("/common/img/ico_borrar_out.png");
}
}
}
});
initializeHoursType();
}
private void initializeHoursType() {
allHoursType = new SimpleListModel<>(costCategoryModel.getAllHoursType());
}
@Override
protected void save() throws ValidationException{
costCategoryModel.confirmSave();
}
@Override
protected void beforeSaving() throws ValidationException {
costCategoryModel.validateHourCostsOverlap();
}
private void showInvalidValues(ValidationException e) {
Object value = e.getInvalidValue().getRootBean();
if (value instanceof HourCost) {
showInvalidValue((HourCost) value);
}
messagesForUser.showInvalidValues(e);
}
private void showInvalidValue(HourCost hourCost) {
Row row = ComponentsFinder.findRowByValue(listHourCosts, hourCost);
if (row != null) {
if (hourCost.getType() == null) {
Listbox workHoursType = getWorkHoursType(row);
String message = workHoursType.getItems().isEmpty()
? _("Hours types are empty. Please, create some hours types before proceeding")
: _("cannot be empty");
throw new WrongValueException(getWorkHoursType(row), message);
}
if (hourCost.getPriceCost() == null) {
throw new WrongValueException(getPricePerHour(row), _("cannot be empty"));
}
}
}
private Listbox getWorkHoursType(Row row) {
return (Listbox) row.getChildren().get(1);
}
private Component getPricePerHour(Row row) {
return row.getChildren().get(2);
}
public CostCategory getCostCategory() {
return costCategoryModel.getCostCategory();
}
public List<CostCategory> getCostCategories() {
return costCategoryModel.getCostCategories();
}
public Set<HourCost> getHourCosts() {
return costCategoryModel.getHourCosts();
}
private TypeOfWorkHours getTypeOfWorkHours(Row listitem) {
HourCost hourCost = listitem.getValue();
return hourCost.getType();
}
/**
* Append a textbox code to row.
*
* @param row
*/
private void appendTextboxCode(final Row row) {
final HourCost hourCost = row.getValue();
final Textbox txtCode = new Textbox();
txtCode.setWidth("200px");
if (hourCost != null) {
CostCategory costCategory = hourCost.getCategory();
txtCode.setDisabled(costCategory.isCodeAutogenerated());
Util.bind(
txtCode,
new Util.Getter<String>() {
@Override
public String get() {
return hourCost.getCode();
}
},
new Util.Setter<String>() {
@Override
public void set(String value) {
hourCost.setCode(value);
}
});
if (!hourCost.getCategory().isCodeAutogenerated()) {
txtCode.setConstraint("no empty:"
+ _("cannot be empty"));
} else {
txtCode.setConstraint("");
}
}
row.appendChild(txtCode);
}
/**
* Append Selectbox of {@link TypeOfWorkHours} to row.
*
* @param row
*/
private void appendHoursType(final Row row) {
final HourCost hourCost = row.getValue();
final Listbox lbHoursType = new Listbox();
lbHoursType.setMold("select");
lbHoursType.setModel(allHoursType);
lbHoursType.renderAll();
lbHoursType.applyProperties();
if (lbHoursType.getItems().isEmpty()) {
row.appendChild(lbHoursType);
return;
}
// First time is rendered, select first item
TypeOfWorkHours type = hourCost.getType();
if ( hourCost.isNewObject() && type == null ) {
Listitem item = lbHoursType.getItemAtIndex(0);
item.setSelected(true);
setHoursType(hourCost, item);
} else {
// If hoursCost has a type, select item with that type
Listitem item = ComponentsFinder.findItemByValue(lbHoursType, type);
if (item != null) {
lbHoursType.selectItem(item);
}
}
lbHoursType.addEventListener(Events.ON_SELECT, new EventListener() {
@Override
public void onEvent(Event event) {
Listitem item = lbHoursType.getSelectedItem();
if (item != null) {
setHoursType(row.getValue(), item);
}
}
});
row.appendChild(lbHoursType);
}
private void setHoursType(HourCost hourCost, Listitem item) {
TypeOfWorkHours value = item != null ? (TypeOfWorkHours) item.getValue() : null;
hourCost.setType(value);
if (value != null) {
final BigDecimal defaultPrice = value.getDefaultPrice();
final Decimalbox dbPricePerHour = (Decimalbox) item.getParent().getNextSibling();
hourCost.setPriceCost(defaultPrice);
if (dbPricePerHour != null) {
dbPricePerHour.setValue(defaultPrice);
}
} else if (item != null) {
hourCost.setPriceCost(BigDecimal.ZERO);
throw new WrongValueException(item.getParent(), _("Please, select an item"));
}
}
/**
* Append a delete {@link Button} to {@link Row}.
*
* @param row
*/
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((HourCost) row.getValue());
}
});
row.appendChild(delete);
}
/**
* Append a Textbox "hour cost" to row.
*
* @param row
*/
private void appendDecimalboxCost(Row row) {
Decimalbox boxCost = new Decimalbox();
bindDecimalboxCost(boxCost, row.getValue());
boxCost.setConstraint("no empty:" + _("cannot be empty"));
boxCost.setFormat(Util.getMoneyFormat());
row.appendChild(boxCost);
}
/**
* Binds Decimalbox "hour cost" to the corresponding attribute of a {@link HourCost}.
*
* @param boxCost
* @param hourCost
*/
private void bindDecimalboxCost(final Decimalbox boxCost, final HourCost hourCost) {
Util.bind(
boxCost,
new Util.Getter<BigDecimal>() {
@Override
public BigDecimal get() {
if (hourCost.getPriceCost() != null) {
return hourCost.getPriceCost();
}
return new BigDecimal(0);
}
},
new Util.Setter<BigDecimal>() {
@Override
public void set(BigDecimal value) {
hourCost.setPriceCost(value);
}
});
}
/**
* Append a Datebox "init date" to row.
*
* @param row
*/
private void appendDateboxInitDate(final Row row) {
Datebox initDateBox = new Datebox();
bindDateboxInitDate(initDateBox, row.getValue());
initDateBox.setConstraint("no empty:" + _("Start date cannot be empty"));
row.appendChild(initDateBox);
initDateBox.addEventListener("onChange", new EventListener() {
@Override
public void onEvent(Event event) {
// Updates the constraint of the endDate box with the new date
LocalDate initDate = ((HourCost)row.getValue()).getInitDate();
Datebox endDateBox = (Datebox) row.getChildren().get(4);
endDateBox.setConstraint("after " +
String.format("%04d", initDate.getYear()) +
String.format("%02d", initDate.getMonthOfYear()) +
String.format("%02d", initDate.getDayOfMonth()));
}
});
}
/**
* Binds Datebox "init date" to the corresponding attribute of a {@link HourCost}.
*
* @param dateBoxInitDate
* @param hourCost
*/
private void bindDateboxInitDate(final Datebox dateBoxInitDate, final HourCost hourCost) {
Util.bind(
dateBoxInitDate,
new Util.Getter<Date>() {
@Override
public Date get() {
LocalDate dateTime = hourCost.getInitDate();
if (dateTime != null) {
/* TODO resolve deprecated */
return new Date(dateTime.getYear() - 1900, dateTime.getMonthOfYear() - 1, dateTime.getDayOfMonth());
} else {
Date now = new Date();
hourCost.setInitDate(new LocalDate(now));
return now;
}
}
},
new Util.Setter<Date>() {
@Override
public void set(Date value) {
if (value != null) {
/* TODO resolve deprecated */
hourCost.setInitDate(new LocalDate(value.getYear() + 1900, value.getMonth() + 1, value.getDate()));
}
else {
hourCost.setInitDate(null);
}
}
});
}
/**
* Append a Datebox "end date" to row.
*
* @param row
*/
private void appendDateboxEndDate(Row row) {
Datebox endDateBox = new Datebox();
bindDateboxEndDate(endDateBox, row.getValue());
LocalDate initDate = ((HourCost)row.getValue()).getInitDate();
if (initDate != null) {
endDateBox.setConstraint("after " +
String.format("%04d", initDate.getYear()) +
String.format("%02d", initDate.getMonthOfYear()) +
String.format("%02d", initDate.getDayOfMonth()));
}
row.appendChild(endDateBox);
}
/**
* Binds Datebox "init date" to the corresponding attribute of a {@link HourCost}.
*
* @param dateBoxEndDate
* @param hourCost
*/
private void bindDateboxEndDate(final Datebox dateBoxEndDate, final HourCost hourCost) {
Util.bind(
dateBoxEndDate,
new Util.Getter<Date>() {
@Override
public Date get() {
LocalDate dateTime = hourCost.getEndDate();
if (dateTime != null) {
/* TODO resolve deprecated */
return new Date(
dateTime.getYear() - 1900, dateTime.getMonthOfYear() - 1, dateTime.getDayOfMonth());
}
return null;
}
},
new Util.Setter<Date>() {
@Override
public void set(Date value) {
if (value != null) {
/* TODO resolve deprecated */
hourCost.setEndDate(
new LocalDate(value.getYear() + 1900, value.getMonth() + 1, value.getDate()));
}
else {
hourCost.setEndDate(null);
}
}
});
}
public void confirmRemove(HourCost hourCost) {
int status = Messagebox.show(
_("Confirm deleting this hour cost. Are you sure?"), _("Delete"),
Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION);
if (Messagebox.OK == status) {
removeHourCost(hourCost);
}
}
public HourCostListRenderer getRenderer() {
return hourCostListRenderer;
}
/**
* Adds a new {@link HourCost} to the list of rows.
*/
public void addHourCost() {
costCategoryModel.addHourCost();
Util.reloadBindings(listHourCosts);
}
private void removeHourCost(HourCost hourCost) {
costCategoryModel.removeHourCost(hourCost);
Util.reloadBindings(listHourCosts);
}
/**
* RowRenderer for a @{HourCost} element.
*/
private class HourCostListRenderer implements RowRenderer {
@Override
public void render(Row row, Object o, int i) throws Exception {
HourCost hourCost = (HourCost) o;
row.setValue(hourCost);
// Create boxes
appendTextboxCode(row);
appendHoursType(row);
appendDecimalboxCost(row);
appendDateboxInitDate(row);
appendDateboxEndDate(row);
appendDeleteButton(row);
}
}
public void onCheckGenerateCode(Event e) {
CheckEvent ce = (CheckEvent) e;
if (ce.isChecked()) {
// We have to auto-generate the code for new objects
try {
costCategoryModel.setCodeAutogenerated(ce.isChecked());
} catch (ConcurrentModificationException err) {
messagesForUser.showMessage(Level.ERROR, err.getMessage());
}
}
Util.reloadBindings(listCostCategories);
Util.reloadBindings(listHourCosts);
}
@Override
protected String getEntityType() {
return _("Cost Category");
}
@Override
protected String getPluralEntityType() {
return _("Cost Categories");
}
@Override
protected void initCreate() {
costCategoryModel.initCreate();
}
@Override
protected void initEdit(CostCategory costCategory) {
costCategoryModel.initEdit(costCategory);
}
@Override
protected CostCategory getEntityBeingEdited() {
return costCategoryModel.getCostCategory();
}
@Override
protected void delete(CostCategory category) throws InstanceNotFoundException{
costCategoryModel.confirmRemoveCostCategory(category);
Util.reloadBindings(listCostCategories);
}
protected boolean beforeDeleting(CostCategory category) {
return costCategoryModel.canRemoveCostCategory(category);
}
private boolean canRemoveCostCategory(CostCategory category) {
return costCategoryModel.canRemoveCostCategory(category);
}
}