Merge branch 'master' into subcontracting-merger-master

This commit is contained in:
Susana Montes Pedreira 2012-04-10 16:36:55 +01:00
commit 2008ea6744
120 changed files with 4895 additions and 545 deletions

108
INSTALL
View file

@ -24,6 +24,17 @@ Instructions::
$ sudo apt-get update
$ sudo apt-get install libreplan
.. TIP::
If you do not have ``add-apt-repository`` command, you will need to install
``python-software-properties`` package before running the previous commands.
You can do it with the following line::
sudo apt-get install python-software-properties
.. WARNING::
If you have memory problems review the section `Fix memory errors`_.
Debian packages
~~~~~~~~~~~~~~~
@ -47,9 +58,58 @@ Instructions:
.. WARNING::
If you have problems with printing support review the last section `Fix
If you have problems with printing support review the section `Fix
printing in Debian Squeeze`_.
.. WARNING::
If you have memory problems review the section `Fix memory errors`_.
Fedora and openSUSE OBS (openSUSE Build Service)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instructions depending on the distribution:
* Fedora 16::
# cd /etc/yum.repos.d
# wget download.opensuse.org/repositories/home:/jsuarezr:/LibrePlan/Fedora_16/home:jsuarezr:LibrePlan.repo
# yum install libreplan
* Fedora 15::
# cd /etc/yum.repos.d
# wget download.opensuse.org/repositories/home:/jsuarezr:/LibrePlan/Fedora_15/home:jsuarezr:LibrePlan.repo
# yum install libreplan
* openSUSE 12.1::
# cd /etc/zypp/repos.d
# wget download.opensuse.org/repositories/home:/jsuarezr:/LibrePlan/openSUSE_12.1/home:jsuarezr:LibrePlan.repo
# zypper ref
# zypper install libreplan
* openSUSE 11.4::
# cd /etc/zypp/repos.d
# wget download.opensuse.org/repositories/home:/jsuarezr:/LibrePlan/openSUSE_11.4/home:jsuarezr:LibrePlan.repo
# zypper ref
# zypper install libreplan
.. WARNING::
If you have memory problems review the section `Fix memory errors`_.
RPM Packages
~~~~~~~~~~~~
There are several LibrePlan RPM packages available in the following URL:
http://download.opensuse.org/repositories/home:/jsuarezr:/LibrePlan/
.. WARNING::
If you have memory problems review the section `Fix memory errors`_.
LibrePlan manual installation
-----------------------------
@ -73,7 +133,7 @@ Debian/Ubuntu
* Download database installation script::
$ wget -O install.sql http://downloads.sourceforge.net/project/librelplan/LibrePlan/install_1.2.0.sql
$ wget -O install.sql http://downloads.sourceforge.net/project/libreplan/LibrePlan/install_1.2.0.sql
* Create database structure::
@ -273,3 +333,47 @@ Instructions:
* Fetch and install ``cutycapt`` (and its dependencies) from testing::
# apt-get -t testing install cutycapt
Fix memory errors
-----------------
With the default parameters of Tomcat in the different distributions you could
have problems with Java memory.
After a while using LibrePlan you could see that some windows do not work and
the log shows a ``java.lang.OutOfMemoryError`` exception.
This exception could be caused because of two different issues:
* Heap space::
java.lang.OutOfMemoryError: Java heap space
* PermGemp space (Permanent Generation, reflective data for the JVM)::
java.lang.OutOfMemoryError: PermGen space
In order to avoid this problem you need to configure properly ``JAVA_OPTS``
variable in your server. This is configured in different files depending on the
distribution:
* Debian or Ubuntu: ``/etc/default/tomcat6``
* Fedora or openSUSE: ``/etc/tomcat6/tomcat6.conf``
The next lines show a possible configuration to fix the memory errors (the exact
values depends on the server features)::
JAVA_OPTS="-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m"
JAVA_OPTS="${JAVA_OPTS} -server -Djava.awt.headless=true"
Where the different parameters have the following meaning:
* ``-Xms``: Initial size of the Java heap
* ``-Xmx``: Maximum size of the Java heap
* ``-XX:PermSize``: Initial size of PermGen
* ``-XX:MaxPermSize``: Maximum size of PermGen
.. NOTE::
Take into account that size of PermGen is additional to heap size.

View file

@ -56,6 +56,8 @@ authentication method", at the end of this document.
LibrePlan should be running at http://localhost:8080/libreplan
Review INSTALL file for more information.
Upgrading LibrePlan a.b.c to LibrePlan x.y.z
============================================

View file

@ -61,6 +61,7 @@ authentication method", at the end of this document.
LibrePlan should be running at http://localhost:8080/libreplan
Review INSTALL file for more information.
Upgrading LibrePlan a.b.c to LibrePlan x.y.z

View file

@ -5,12 +5,24 @@ About
.. contents::
Licence
========
Copyright
=========
Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
Desenvolvemento Tecnolóxico de Galicia
Copyright (C) 2010 Igalia, S.L.; Wireless Galicia, S.L.
Copyright (C) 2011 Igalia, S.L.; Wireless Galicia, S.L.; ComtecSF, S.L.;
CafédeRed Solutions, S.L.
Copyright (C) 2012 Igalia, S.L.; Wireless Galicia, S.L.; CafédeRed Solutions,
S.L.
Licence
========
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

View file

@ -5,6 +5,21 @@ Acerca de
.. contents::
Copyright
=========
Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
Desenvolvemento Tecnolóxico de Galicia
Copyright (C) 2010 Igalia, S.L.; Wireless Galicia, S.L.
Copyright (C) 2011 Igalia, S.L.; Wireless Galicia, S.L.; ComtecSF, S.L.;
CafédeRed Solutions, S.L.
Copyright (C) 2012 Igalia, S.L.; Wireless Galicia, S.L.; CafédeRed Solutions,
S.L.
Licencia
================

View file

@ -5,6 +5,21 @@ Acerca de
.. contents::
Copyright
=========
Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
Desenvolvemento Tecnolóxico de Galicia
Copyright (C) 2010 Igalia, S.L.; Wireless Galicia, S.L.
Copyright (C) 2011 Igalia, S.L.; Wireless Galicia, S.L.; ComtecSF, S.L.;
CafédeRed Solutions, S.L.
Copyright (C) 2012 Igalia, S.L.; Wireless Galicia, S.L.; CafédeRed Solutions,
S.L.
Licenza
================

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -227,6 +227,7 @@ public class FunctionalityExposedForExtensions<T> implements IContext<T> {
}
result.setShowingReportedHours(planner.showReportedHoursRightNow());
result.setShowingMoneyCostBar(planner.showMoneyCostBarRightNow());
result.setShowingAdvances(planner.showAdvancesRightNow());
mapper.register(position, result, data);
@ -465,6 +466,20 @@ public class FunctionalityExposedForExtensions<T> implements IContext<T> {
}
}
@Override
public void showMoneyCostBar() {
for (Task task : diagramGraph.getTasks()) {
task.setShowingMoneyCostBar(true);
}
}
@Override
public void hideMoneyCostBar() {
for (Task task : diagramGraph.getTasks()) {
task.setShowingMoneyCostBar(false);
}
}
@Override
public void reloadCharts() {
configuration.reloadCharts();
@ -483,6 +498,8 @@ public class FunctionalityExposedForExtensions<T> implements IContext<T> {
Checkbox advances = (Checkbox) parent.getFellow("print_advances");
Checkbox reportedHours = (Checkbox) parent
.getFellow("print_reported_hours");
Checkbox moneyCostBar = (Checkbox) parent
.getFellow("print_money_cost_bar");
parameters.put("extension", ".png");
if (expanded.isChecked() == true) {
@ -497,6 +514,9 @@ public class FunctionalityExposedForExtensions<T> implements IContext<T> {
if (reportedHours.isChecked() == true) {
parameters.put("reportedHours", "all");
}
if (moneyCostBar.isChecked() == true) {
parameters.put("moneyCostBar", "all");
}
if (resources.isChecked() == true) {
parameters.put("resources", "all");
}

View file

@ -141,7 +141,9 @@ public class GanttPanel extends XulElement implements AfterCompose {
for (Task task : this.tasksLists.getAllTasks()) {
task.updateTooltipText();
}
invalidate();
for (TaskComponent taskComponent : this.tasksLists.getTaskComponents()) {
taskComponent.invalidate();
}
}
public TimeTrackerComponent getTimeTrackerComponent() {

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -120,6 +120,15 @@ public class Planner extends HtmlMacroComponent {
return toLowercaseSet(values).contains("all");
}
public static boolean guessShowMoneyCostBarByDefault(
Map<String, String[]> queryURLParameters) {
String[] values = queryURLParameters.get("moneyCostBar");
if (values == null) {
return false;
}
return toLowercaseSet(values).contains("all");
}
private static Set<String> toLowercaseSet(String[] values) {
Set<String> result = new HashSet<String>();
for (String each : values) {
@ -156,6 +165,8 @@ public class Planner extends HtmlMacroComponent {
private boolean isShowingReportedHours = false;
private boolean isShowingMoneyCostBar = false;
private boolean isShowingResources = false;
private boolean isExpandAll = false;
@ -372,6 +383,11 @@ public class Planner extends HtmlMacroComponent {
Button showAllResources = (Button) getFellow("showAllResources");
showAllResources.setVisible(false);
}
if (!configuration.isMoneyCostBarEnabled()) {
Button showMoneyCostBarButton = (Button) getFellow("showMoneyCostBar");
showMoneyCostBarButton.setVisible(false);
}
listZoomLevels.setSelectedIndex(getZoomLevel().ordinal());
this.visibleChart = configuration.isExpandPlanningViewCharts();
@ -571,12 +587,22 @@ public class Planner extends HtmlMacroComponent {
}
};
private IGraphChangeListener showMoneyCostBarOnChange = new IGraphChangeListener() {
@Override
public void execute() {
context.showMoneyCostBar();
}
};
private boolean containersExpandedByDefault = false;
private boolean shownAdvanceByDefault = false;
private boolean shownReportedHoursByDefault = false;
private boolean shownMoneyCostBarByDefault = false;
private FilterAndParentExpandedPredicates predicate;
private boolean visibleChart;
@ -645,6 +671,26 @@ public class Planner extends HtmlMacroComponent {
}
}
public void showMoneyCostBar() {
Button showMoneyCostBarButton = (Button) getFellow("showMoneyCostBar");
if (disabilityConfiguration.isMoneyCostBarEnabled()) {
if (isShowingMoneyCostBar) {
context.hideMoneyCostBar();
diagramGraph
.removePostGraphChangeListener(showMoneyCostBarOnChange);
showMoneyCostBarButton.setSclass("planner-command");
showMoneyCostBarButton.setTooltiptext(_("Show money cost bar"));
} else {
context.showMoneyCostBar();
diagramGraph
.addPostGraphChangeListener(showMoneyCostBarOnChange);
showMoneyCostBarButton.setSclass("planner-command clicked");
showMoneyCostBarButton.setTooltiptext(_("Hide money cost bar"));
}
isShowingMoneyCostBar = !isShowingMoneyCostBar;
}
}
public void showAllLabels() {
Button showAllLabelsButton = (Button) getFellow("showAllLabels");
if (isShowingLabels) {
@ -732,6 +778,19 @@ public class Planner extends HtmlMacroComponent {
return (areShownReportedHoursByDefault() || isShowingReportedHours);
}
public void setAreShownMoneyCostBarByDefault(
boolean shownMoneyCostBarByDefault) {
this.shownMoneyCostBarByDefault = shownMoneyCostBarByDefault;
}
public boolean areShownMoneyCostBarByDefault() {
return shownMoneyCostBarByDefault;
}
public boolean showMoneyCostBarRightNow() {
return (areShownMoneyCostBarByDefault() || isShowingMoneyCostBar);
}
public void expandAll() {
Button expandAllButton = (Button) getFellow("expandAll");
if (disabilityConfiguration.isExpandAllEnabled()) {

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -60,6 +60,7 @@ import org.zkoss.zul.Div;
* Graphical component which represents a {@link Task}.
*
* @author Javier Morán Rúa <jmoran@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class TaskComponent extends Div implements AfterCompose {
@ -78,6 +79,8 @@ public class TaskComponent extends Div implements AfterCompose {
private PropertyChangeListener showingReportedHoursPropertyListener;
private PropertyChangeListener showingMoneyCostBarPropertyListener;
public static TaskComponent asTaskComponent(Task task,
IDisabilityConfiguration disabilityConfiguration,
boolean isTopLevel) {
@ -278,6 +281,20 @@ public class TaskComponent extends Div implements AfterCompose {
this.task
.addReportedHoursPropertyChangeListener(showingReportedHoursPropertyListener);
if (showingMoneyCostBarPropertyListener == null) {
showingMoneyCostBarPropertyListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (isInPage() && !(task instanceof Milestone)) {
updateCompletionMoneyCostBar();
}
}
};
}
this.task
.addMoneyCostBarPropertyChangeListener(showingMoneyCostBarPropertyListener);
if (criticalPathPropertyListener == null) {
criticalPathPropertyListener = new PropertyChangeListener() {
@ -491,6 +508,7 @@ public class TaskComponent extends Div implements AfterCompose {
return;
}
updateCompletionReportedHours();
updateCompletionMoneyCostBar();
updateCompletionAdvance();
}
@ -507,6 +525,19 @@ public class TaskComponent extends Div implements AfterCompose {
}
}
public void updateCompletionMoneyCostBar() {
if (task.isShowingMoneyCostBar()) {
int startPixels = this.task.getBeginDate().toPixels(getMapper());
String widthMoneyCostBar = pixelsFromStartUntil(startPixels,
this.task.getMoneyCostBarEndDate()) + "px";
response(null, new AuInvoke(this, "resizeCompletionMoneyCostBar",
widthMoneyCostBar));
} else {
response(null, new AuInvoke(this, "resizeCompletionMoneyCostBar",
"0px"));
}
}
private void updateCompletionAdvance() {
if (task.isShowingAdvances()) {
int startPixels = this.task.getBeginDate().toPixels(getMapper());

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -22,7 +22,7 @@ package org.zkoss.ganttz.adapters;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface IDisabilityConfiguration {
@ -40,6 +40,8 @@ public interface IDisabilityConfiguration {
public boolean isReportedHoursEnabled();
public boolean isMoneyCostBarEnabled();
public boolean isExpandAllEnabled();
public boolean isFlattenTreeEnabled();

View file

@ -148,6 +148,8 @@ public class PlannerConfiguration<T> implements IDisabilityConfiguration {
private boolean reportedHoursEnabled = true;
private boolean moneyCostBarEnabled = true;
private boolean expandAllEnabled = true;
private boolean flattenTreeEnabled = true;
@ -345,6 +347,15 @@ public class PlannerConfiguration<T> implements IDisabilityConfiguration {
return reportedHoursEnabled;
}
public void setMoneyCostBarEnabled(boolean moneyCostBarEnabled) {
this.moneyCostBarEnabled = moneyCostBarEnabled;
}
@Override
public boolean isMoneyCostBarEnabled() {
return moneyCostBarEnabled;
}
public void setExpandAllEnabled(boolean expandAllEnabled) {
this.expandAllEnabled = expandAllEnabled;
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -34,6 +34,7 @@ import org.zkoss.ganttz.data.constraint.Constraint;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class DefaultFundamentalProperties implements ITaskFundamentalProperties {
@ -47,10 +48,14 @@ public class DefaultFundamentalProperties implements ITaskFundamentalProperties
private long hoursAdvanceEndDate;
private long moneyCostBarEndDate;
private Date advanceEndDate;
private BigDecimal hoursAdvancePercentage;
private BigDecimal moneyCostBarPercentage;
private BigDecimal advancePercentage;
private String tooltipText;
@ -92,15 +97,19 @@ public class DefaultFundamentalProperties implements ITaskFundamentalProperties
public DefaultFundamentalProperties(String name, Date beginDate,
long lengthMilliseconds, String notes,
Date hoursAdvanceEndDate,
Date moneyCostBarEndDate,
Date advanceEndDate,
BigDecimal hoursAdvancePercentage, BigDecimal advancePercentage) {
BigDecimal hoursAdvancePercentage,
BigDecimal moneyCostBarPercentage, BigDecimal advancePercentage) {
this.name = name;
this.beginDate = beginDate.getTime();
this.lengthMilliseconds = lengthMilliseconds;
this.notes = notes;
this.hoursAdvanceEndDate = hoursAdvanceEndDate.getTime();
this.moneyCostBarEndDate = moneyCostBarEndDate.getTime();
this.advanceEndDate = advanceEndDate;
this.hoursAdvancePercentage = hoursAdvancePercentage;
this.moneyCostBarPercentage = moneyCostBarPercentage;
this.advancePercentage = advancePercentage;
this.tooltipText = "Default tooltip";
this.labelsText = "";
@ -169,6 +178,11 @@ public class DefaultFundamentalProperties implements ITaskFundamentalProperties
return GanttDate.createFrom(new Date(hoursAdvanceEndDate));
}
@Override
public GanttDate getMoneyCostBarEndDate() {
return GanttDate.createFrom(new Date(moneyCostBarEndDate));
}
@Override
public GanttDate getAdvanceEndDate() {
return advanceEndDate != null ? GanttDate.createFrom(new Date(

View file

@ -2427,4 +2427,4 @@ public class GanttDiagramGraph<V, D extends IDependency<V>> implements
return adapter.getChildren(task);
}
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -29,6 +29,7 @@ import org.zkoss.ganttz.data.constraint.Constraint;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface ITaskFundamentalProperties {
@ -76,6 +77,8 @@ public interface ITaskFundamentalProperties {
public GanttDate getHoursAdvanceEndDate();
public GanttDate getMoneyCostBarEndDate();
public GanttDate getAdvanceEndDate();
public BigDecimal getHoursAdvancePercentage();

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -47,7 +47,9 @@ import org.zkoss.ganttz.util.WeakReferencedListeners.Mode;
/**
* This class contains the information of a task. It can be modified and
* notifies of the changes to the interested parties. <br/>
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public abstract class Task implements ITaskFundamentalProperties {
@ -72,6 +74,9 @@ public abstract class Task implements ITaskFundamentalProperties {
private PropertyChangeSupport reportedHoursProperty = new PropertyChangeSupport(
this);
private PropertyChangeSupport moneyCostBarProperty = new PropertyChangeSupport(
this);
private final ITaskFundamentalProperties fundamentalProperties;
private boolean visible = true;
@ -82,6 +87,8 @@ public abstract class Task implements ITaskFundamentalProperties {
private boolean showingReportedHours = false;
private boolean showingMoneyCostBar = false;
private ConstraintViolationNotificator<GanttDate> violationNotificator = ConstraintViolationNotificator
.create();
@ -241,6 +248,17 @@ public abstract class Task implements ITaskFundamentalProperties {
return showingReportedHours;
}
public void setShowingMoneyCostBar(boolean showingMoneyCostBar) {
boolean previousValue = this.showingMoneyCostBar;
this.showingMoneyCostBar = showingMoneyCostBar;
moneyCostBarProperty.firePropertyChange("showingMoneyCostBar",
previousValue, this.showingMoneyCostBar);
}
public boolean isShowingMoneyCostBar() {
return showingMoneyCostBar;
}
public String getName() {
return fundamentalProperties.getName();
}
@ -297,6 +315,11 @@ public abstract class Task implements ITaskFundamentalProperties {
this.reportedHoursProperty.addPropertyChangeListener(listener);
}
public void addMoneyCostBarPropertyChangeListener(
PropertyChangeListener listener) {
this.moneyCostBarProperty.addPropertyChangeListener(listener);
}
public void addFundamentalPropertiesChangeListener(
PropertyChangeListener listener) {
this.fundamentalPropertiesListeners.addPropertyChangeListener(listener);
@ -374,6 +397,11 @@ public abstract class Task implements ITaskFundamentalProperties {
return fundamentalProperties.getHoursAdvanceEndDate();
}
@Override
public GanttDate getMoneyCostBarEndDate() {
return fundamentalProperties.getMoneyCostBarEndDate();
}
@Override
public GanttDate getAdvanceEndDate() {
return fundamentalProperties.getAdvanceEndDate();

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -35,7 +35,9 @@ import org.zkoss.zk.ui.Component;
/**
* An implementation of {@link IContext} that delegates to another context and
* redefines its {@link IContext#getRelativeTo()}
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class ContextRelativeToOtherComponent<T> implements IContext<T> {
@ -147,4 +149,15 @@ public class ContextRelativeToOtherComponent<T> implements IContext<T> {
public void showReportedHours() {
context.showReportedHours();
}
@Override
public void hideMoneyCostBar() {
context.hideMoneyCostBar();
}
@Override
public void showMoneyCostBar() {
context.showMoneyCostBar();
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -36,7 +36,9 @@ import org.zkoss.zk.ui.Component;
* An implementation of {@link IContextWithPlannerTask} that wraps another
* context and specifies the task to be returned by
* {@link IContextWithPlannerTask#getTask()}
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class ContextWithPlannerTask<T> implements IContextWithPlannerTask<T> {
@ -149,4 +151,15 @@ public class ContextWithPlannerTask<T> implements IContextWithPlannerTask<T> {
public void showReportedHours() {
context.showReportedHours();
}
@Override
public void hideMoneyCostBar() {
context.hideMoneyCostBar();
}
@Override
public void showMoneyCostBar() {
context.showMoneyCostBar();
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -38,7 +38,9 @@ import org.zkoss.zk.ui.Component;
/**
* A facade for operations allowed to extensions <br />
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface IContext<T> {
@ -143,4 +145,9 @@ public interface IContext<T> {
void showReportedHours();
void hideReportedHours();
void showMoneyCostBar();
void hideMoneyCostBar();
}

View file

@ -3,7 +3,7 @@
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.
Copyright (C) 2010-2012 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
@ -86,6 +86,12 @@ planner = self;
sclass="planner-command"/>
<separator />
<button id="showMoneyCostBar" onClick="planner.showMoneyCostBar();"
image="/common/img/ico_money_cost_bar.png"
tooltiptext="${ganttzk_i18n:_('Show/Hide money cost bar')}"
sclass="planner-command"/>
<separator />
<!-- Filtering -->
<vbox id="orderFilter"/>
<vbox id="orderElementFilter"/>
@ -116,4 +122,4 @@ planner = self;
<div id="insertionPointChart" />
</south>
</borderlayout>
</zk>
</zk>

View file

@ -215,6 +215,9 @@ ganttz.TaskComponent = zk.$extends(zul.Widget, {
moveConsolidatedline : function(width){
jq('#consolidatedline' + this.parent.uuid).css('left', width);
},
resizeCompletionMoneyCostBar : function(width){
jq('#' + this.uuid + ' .completionMoneyCostBar:first').css('width', width);
},
resizeCompletionAdvance : function(width){
jq('#' + this.uuid + ' .completion:first').css('width', width);
},

View file

@ -5,6 +5,7 @@ function(out){
'z.autoz="true"',
'class="milestone"',
'>');
out.push('<div class="completionMoneyCostBar"></div>');
out.push('<div class="completion"></div>');
out.push('<div class="completion2"></div>');
out.push('<div class="milestone_end"></div>');

View file

@ -11,6 +11,7 @@ function(out){
out.push('<div class="task-resources-inner">', this.getResourcesText(),'</div>');
out.push('</div>');
out.push('<div class="completionMoneyCostBar"></div>');
out.push('<div class="completion"></div>');
out.push('<div class="completion2"></div>');

View file

@ -22,6 +22,7 @@ function(out){
'</div>');
out.push('<div class="taskcontainer_completion">',
'<div class="completionMoneyCostBar"></div>',
'<div class="completion"></div>',
'<div class="completion2"></div>',
'</div>');

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -21,20 +21,24 @@
package org.libreplan.business.costcategories.daos;
import java.util.Collection;
import java.math.BigDecimal;
import org.hibernate.criterion.Restrictions;
import org.hibernate.Query;
import org.joda.time.LocalDate;
import org.libreplan.business.common.daos.IntegrationEntityDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.costcategories.entities.HourCost;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.resources.entities.Resource;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Jacobo Aragunde Perez <jaragunde@igalia.com>
* @author Diego Pino García <dpino@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Repository
@Scope(BeanDefinition.SCOPE_SINGLETON)
@ -53,4 +57,25 @@ public class HourCostDAO extends IntegrationEntityDAO<HourCost> implements
super.remove(id);
}
@Override
@Transactional(readOnly = true)
public BigDecimal getPriceCostFromResourceDateAndType(Resource resource,
LocalDate date, TypeOfWorkHours type) {
String strQuery = "SELECT hc.priceCost "
+ "FROM ResourcesCostCategoryAssignment rcca, HourCost hc "
+ "WHERE rcca.costCategory = hc.category "
+ "AND rcca.resource = :resource " + "AND hc.type = :type "
+ "AND rcca.initDate <= :date "
+ "AND (rcca.endDate >= :date OR rcca.endDate IS NULL) "
+ "AND hc.initDate <= :date "
+ "AND (hc.endDate >= :date OR hc.endDate IS NULL)";
Query query = getSession().createQuery(strQuery);
query.setParameter("resource", resource);
query.setParameter("date", date);
query.setParameter("type", type);
return (BigDecimal) query.uniqueResult();
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -21,17 +21,43 @@
package org.libreplan.business.costcategories.daos;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
import org.libreplan.business.common.daos.IIntegrationEntityDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.costcategories.entities.CostCategory;
import org.libreplan.business.costcategories.entities.HourCost;
import org.libreplan.business.costcategories.entities.ResourcesCostCategoryAssignment;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.resources.entities.Resource;
/**
* @author Jacobo Aragunde Perez <jaragunde@igalia.com>
* @author Diego Pino García <dpino@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface IHourCostDAO extends IIntegrationEntityDAO<HourCost> {
@Override
public void remove(Long id) throws InstanceNotFoundException;
}
/**
* Returns the price cost of a {@link HourCost} associated with a
* {@link Resource} in a specific {@link LocalDate} and with a concrete
* {@link TypeOfWorkHours}<br />
*
* The association is done through {@link CostCategory} associated to the
* resource in the specific date (from class
* {@link ResourcesCostCategoryAssignment}).
*
* @param resource
* @param date
* @param type
* @return A {@link BigDecimal} with the price cost for a {@link Resource}
* in a {@link LocalDate} for this {@link TypeOfWorkHours}
*/
BigDecimal getPriceCostFromResourceDateAndType(Resource resource,
LocalDate date, TypeOfWorkHours type);
}

View file

@ -31,7 +31,6 @@ import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.templates.entities.OrderElementTemplate;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.entities.WorkReportLine;
/**
* Contract for {@link OrderElementDAO}
@ -111,12 +110,6 @@ public interface IOrderElementDAO extends IIntegrationEntityDAO<OrderElement> {
boolean isAlreadyInUseThisOrAnyOfItsChildren(OrderElement orderElement);
void updateRelatedSumChargedEffortWithWorkReportLineSet(
Set<WorkReportLine> workReportLineSet) throws InstanceNotFoundException;
void updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(
Set<WorkReportLine> workReportLineSet) throws InstanceNotFoundException;
/**
* Returns codes in DB searching in all order elements but excluding orderElements
*

View file

@ -0,0 +1,77 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 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.business.orders.daos;
import java.util.Set;
import org.libreplan.business.common.daos.IGenericDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.SumChargedEffort;
import org.libreplan.business.workreports.entities.WorkReportLine;
/**
* Contract for {@link SumChargedEffortDAO}
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface ISumChargedEffortDAO extends
IGenericDAO<SumChargedEffort, Long> {
/**
* Update the {@link SumChargedEffort} objects with the changes in the
* {@link WorkReportLine} set passed as argument. <br />
*
* If the {@link WorkReportLine} is new, the effort is added to the
* corresponding {@link SumChargedEffort}. Otherwise, the difference of
* effort is added or subtracted as required. <br />
*
* If there is not {@link SumChargedEffort} associated to the
* {@link OrderElement} yet, it is created on demand.
*
* @param workReportLineSet
*/
void updateRelatedSumChargedEffortWithWorkReportLineSet(
Set<WorkReportLine> workReportLineSet);
/**
* Update the {@link SumChargedEffort} objects removing the values from the
* {@link WorkReportLine} set passed as argument. <br />
*
* If the {@link WorkReportLine} is new, nothing is substracted. Otherwise,
* the actual value saved in the database is substracted and not the one
* coming in the objects passed.
*
* @param workReportLineSet
*/
void updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(
Set<WorkReportLine> workReportLineSet);
SumChargedEffort findByOrderElement(OrderElement orderElement);
/**
* Recalculates all the {@link SumChargedEffort} objets of an {@link Order}.
* This is needed when some elements are moved inside the {@link Order}.
*
* @param orderId
*/
void recalculateSumChargedEfforts(Long orderId);
}

View file

@ -27,7 +27,6 @@ import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -39,7 +38,6 @@ import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IntegrationEntityDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.orders.entities.Order;
@ -446,149 +444,6 @@ public class OrderElementDAO extends IntegrationEntityDAO<OrderElement>
return false;
}
private void updateRelatedSumChargedEffortWithWorkReportLine(
final WorkReportLine workReportLine,
Map<Long, EffortDuration> relationOrderElementIdAndIndirectChargedEffort)
throws InstanceNotFoundException {
OrderElement orderElement = find(workReportLine.getOrderElement().getId());
EffortDuration effort = workReportLine.getEffort();
EffortDuration differenceOfEffort;
boolean mustBeAdded = true;
if(workReportLine.isNewObject()) {
differenceOfEffort = effort;
}
else {
//the line already exists, we have to get the old value for numHours
EffortDuration oldEffort = transactionService
.runOnAnotherTransaction(new IOnTransaction<EffortDuration>() {
@Override
public EffortDuration execute() {
try {
return workReportLineDAO.find(
workReportLine.getId()).getEffort();
} catch (InstanceNotFoundException e) {
// this shouldn't happen, as workReportLine.isNewObject()
// returns false, indicating this object already exists
return workReportLine.getEffort();
}
}
});
BigDecimal differenceEffortNumeric = effort
.toHoursAsDecimalWithScale(2).subtract(
oldEffort.toHoursAsDecimalWithScale(2));
mustBeAdded = differenceEffortNumeric.compareTo(BigDecimal.ZERO) >= 0;
if (mustBeAdded)
differenceOfEffort = effort.minus(oldEffort);
else
differenceOfEffort = oldEffort.minus(effort);
}
if (mustBeAdded)
orderElement.getSumChargedEffort().addDirectChargedEffort(
differenceOfEffort);
else
orderElement.getSumChargedEffort().subtractDirectChargedEffort(
differenceOfEffort);
save(orderElement);
updateIndirectChargedEffortRecursively(orderElement.getParent(),
differenceOfEffort,
relationOrderElementIdAndIndirectChargedEffort);
workReportLineDAO.save(workReportLine);
}
private void updateRelatedSumChargedEffortWithDeletedWorkReportLine(
final WorkReportLine workReportLine,
Map<Long, EffortDuration> relationOrderElementIdAndIndirectChargedHours)
throws InstanceNotFoundException {
if(workReportLine.isNewObject()) {
//if the line hadn't been saved, we have nothing to update
return;
}
// Refresh data from database, because of changes not saved are not
// useful for the following operations
sessionFactory.getCurrentSession().refresh(workReportLine);
OrderElement orderElement = find(workReportLine.getOrderElement().getId());
EffortDuration effort = workReportLine.getEffort();
orderElement.getSumChargedEffort().subtractDirectChargedEffort(effort);
save(orderElement);
updateIndirectChargedEffortRecursively(orderElement.getParent(),
effort,
relationOrderElementIdAndIndirectChargedHours);
}
private void updateIndirectChargedEffortRecursively(
OrderElement orderElement,
EffortDuration effortDelta,
Map<Long, EffortDuration> relationOrderElementIdAndIndirectChargedEffort) {
if(orderElement != null) {
Long id = orderElement.getId();
if (relationOrderElementIdAndIndirectChargedEffort.containsKey(id)) {
EffortDuration previous = relationOrderElementIdAndIndirectChargedEffort
.get(id);
relationOrderElementIdAndIndirectChargedEffort.put(id,
previous.plus(effortDelta));
}
else {
relationOrderElementIdAndIndirectChargedEffort.put(id,
effortDelta);
}
updateIndirectChargedEffortRecursively(orderElement.getParent(),
effortDelta, relationOrderElementIdAndIndirectChargedEffort);
}
}
private void updateIndirectChargedEffortWithMap(
Map<Long, EffortDuration> relationOrderElementIdAndIndirectChargedEffort,
boolean sum)
throws InstanceNotFoundException {
for (Long id : relationOrderElementIdAndIndirectChargedEffort.keySet()) {
OrderElement orderElement = find(id);
if (sum) {
orderElement.getSumChargedEffort().addIndirectChargedEffort(
relationOrderElementIdAndIndirectChargedEffort.get(id));
} else {
orderElement.getSumChargedEffort()
.subtractIndirectChargedEffort(
relationOrderElementIdAndIndirectChargedEffort
.get(id));
}
save(orderElement);
}
}
@Override
@Transactional
public void updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(
Set<WorkReportLine> workReportLineSet) throws InstanceNotFoundException {
Map<Long, EffortDuration> relationOrderElementIdAndIndirectChargedEffort = new Hashtable<Long, EffortDuration>();
for(WorkReportLine line : workReportLineSet) {
updateRelatedSumChargedEffortWithDeletedWorkReportLine(line,
relationOrderElementIdAndIndirectChargedEffort);
}
updateIndirectChargedEffortWithMap(
relationOrderElementIdAndIndirectChargedEffort, false);
}
@Override
@Transactional
public void updateRelatedSumChargedEffortWithWorkReportLineSet(
Set<WorkReportLine> workReportLineSet) throws InstanceNotFoundException {
Map<Long, EffortDuration> relationOrderElementIdAndIndirectChargedEffort = new Hashtable<Long, EffortDuration>();
for(WorkReportLine line : workReportLineSet) {
updateRelatedSumChargedEffortWithWorkReportLine(line,
relationOrderElementIdAndIndirectChargedEffort);
}
updateIndirectChargedEffortWithMap(
relationOrderElementIdAndIndirectChargedEffort, true);
}
@SuppressWarnings("unchecked")
@Override
@Transactional(readOnly = true)

View file

@ -0,0 +1,251 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 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.business.orders.daos;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.GenericDAOHibernate;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.SumChargedEffort;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.entities.WorkReportLine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* DAO for {@link SumChargedEffort}.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Repository
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class SumChargedEffortDAO extends
GenericDAOHibernate<SumChargedEffort, Long> implements
ISumChargedEffortDAO {
@Autowired
private SessionFactory sessionFactory;
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired
private IAdHocTransactionService transactionService;
@Autowired
private IOrderDAO orderDAO;
private Map<OrderElement, SumChargedEffort> mapSumChargedEfforts;
@Override
public void updateRelatedSumChargedEffortWithWorkReportLineSet(
Set<WorkReportLine> workReportLineSet) {
resetMapSumChargedEfforts();
for (WorkReportLine workReportLine : workReportLineSet) {
updateRelatedSumChargedEffortWithAddedOrModifiedWorkReportLine(workReportLine);
}
}
private void updateRelatedSumChargedEffortWithAddedOrModifiedWorkReportLine(
final WorkReportLine workReportLine) {
boolean increase = true;
EffortDuration effort = workReportLine.getEffort();
if (!workReportLine.isNewObject()) {
EffortDuration previousEffort = transactionService
.runOnAnotherTransaction(new IOnTransaction<EffortDuration>() {
@Override
public EffortDuration execute() {
try {
return workReportLineDAO.find(
workReportLine.getId()).getEffort();
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
}
});
if (effort.compareTo(previousEffort) >= 0) {
effort = effort.minus(previousEffort);
} else {
increase = false;
effort = previousEffort.minus(effort);
}
}
if (!effort.isZero()) {
if (increase) {
addDirectChargedEffort(workReportLine.getOrderElement(), effort);
} else {
substractDirectChargedEffort(workReportLine.getOrderElement(),
effort);
}
}
}
private void addDirectChargedEffort(OrderElement orderElement,
EffortDuration effort) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
sumChargedEffort.addDirectChargedEffort(effort);
save(sumChargedEffort);
addInirectChargedEffortRecursively(orderElement.getParent(), effort);
}
private void addInirectChargedEffortRecursively(OrderElement orderElement,
EffortDuration effort) {
if (orderElement != null) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
sumChargedEffort.addIndirectChargedEffort(effort);
save(sumChargedEffort);
addInirectChargedEffortRecursively(orderElement.getParent(), effort);
}
}
@Override
public void updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(
Set<WorkReportLine> workReportLineSet) {
resetMapSumChargedEfforts();
for (WorkReportLine workReportLine : workReportLineSet) {
updateRelatedSumChargedEffortWithDeletedWorkReportLine(workReportLine);
}
}
private void resetMapSumChargedEfforts() {
mapSumChargedEfforts = new HashMap<OrderElement, SumChargedEffort>();
}
private void updateRelatedSumChargedEffortWithDeletedWorkReportLine(
WorkReportLine workReportLine) {
if (workReportLine.isNewObject()) {
// If the line hasn't been saved, we have nothing to update
return;
}
// Refresh data from database, because of changes not saved are not
// useful for the following operations
sessionFactory.getCurrentSession().refresh(workReportLine);
substractDirectChargedEffort(workReportLine.getOrderElement(),
workReportLine.getEffort());
}
private void substractDirectChargedEffort(OrderElement orderElement,
EffortDuration effort) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
sumChargedEffort.subtractDirectChargedEffort(effort);
save(sumChargedEffort);
substractInirectChargedEffortRecursively(orderElement.getParent(),
effort);
}
private void substractInirectChargedEffortRecursively(OrderElement orderElement,
EffortDuration effort) {
if (orderElement != null) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
sumChargedEffort.subtractIndirectChargedEffort(effort);
save(sumChargedEffort);
substractInirectChargedEffortRecursively(orderElement.getParent(), effort);
}
}
private SumChargedEffort getByOrderElement(OrderElement orderElement) {
SumChargedEffort sumChargedEffort = mapSumChargedEfforts
.get(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = findByOrderElement(orderElement);
mapSumChargedEfforts.put(orderElement, sumChargedEffort);
}
return sumChargedEffort;
}
@Override
public SumChargedEffort findByOrderElement(OrderElement orderElement) {
return (SumChargedEffort) getSession().createCriteria(getEntityClass())
.add(Restrictions.eq("orderElement", orderElement))
.uniqueResult();
}
@Override
@Transactional
public void recalculateSumChargedEfforts(Long orderId) {
try {
Order order = orderDAO.find(orderId);
resetMapSumChargedEfforts();
resetSumChargedEffort(order);
calculateDirectChargedEffort(order);
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
}
private void resetSumChargedEffort(OrderElement orderElement) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
sumChargedEffort.reset();
for (OrderElement each : orderElement.getChildren()) {
resetSumChargedEffort(each);
}
}
private void calculateDirectChargedEffort(OrderElement orderElement) {
for (OrderElement each : orderElement.getChildren()) {
calculateDirectChargedEffort(each);
}
EffortDuration effort = EffortDuration.zero();
for (WorkReportLine line : workReportLineDAO
.findByOrderElement(orderElement)) {
effort = effort.plus(line.getEffort());
}
addDirectChargedEffort(orderElement, effort);
}
}

View file

@ -0,0 +1,40 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 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.business.orders.entities;
/**
* Interface to recalculate {@link SumChargedEffort} for an {@link Order}.<br />
*
* This is needed to be called when some elements are moved in the {@link Order}
* .
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface ISumChargedEffortRecalculator {
/**
* Mark {@link Order} to recalculate {@link SumChargedEffort}.<br />
*
* @param orderId
*/
void recalculate(Long orderId);
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -66,7 +66,11 @@ import org.libreplan.business.util.deepcopy.DeepCopy;
/**
* It represents an {@link Order} with its related information. <br />
*
* Every project in the application is an {@link Order}.
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class Order extends OrderLineGroup implements Comparable {
@ -130,6 +134,8 @@ public class Order extends OrderLineGroup implements Comparable {
private SchedulingMode schedulingMode = SchedulingMode.FORWARD;
private boolean neededToRecalculateSumChargedEfforts = false;
public static class CurrentVersionInfo {
private final OrderVersion orderVersion;
@ -440,32 +446,35 @@ public class Order extends OrderLineGroup implements Comparable {
}
public void generateOrderElementCodes(int numberOfDigits) {
for (OrderElement orderElement : this.getAllOrderElements()) {
if ((orderElement.getCode() == null)
|| (orderElement.getCode().isEmpty())
|| (!orderElement.getCode().startsWith(this.getCode()))) {
this.incrementLastOrderElementSequenceCode();
String orderElementCode = EntitySequence.formatValue(
numberOfDigits, this.getLastOrderElementSequenceCode());
orderElement.setCode(this.getCode()
+ EntitySequence.CODE_SEPARATOR_CHILDREN
+ orderElementCode);
}
if (isCodeAutogenerated()) {
for (OrderElement orderElement : this.getAllOrderElements()) {
if ((orderElement.getCode() == null)
|| (orderElement.getCode().isEmpty())
|| (!orderElement.getCode().startsWith(this.getCode()))) {
this.incrementLastOrderElementSequenceCode();
String orderElementCode = EntitySequence.formatValue(
numberOfDigits,
this.getLastOrderElementSequenceCode());
orderElement.setCode(this.getCode()
+ EntitySequence.CODE_SEPARATOR_CHILDREN
+ orderElementCode);
}
if (orderElement instanceof OrderLine) {
for (HoursGroup hoursGroup : orderElement.getHoursGroups()) {
if ((hoursGroup.getCode() == null)
|| (hoursGroup.getCode().isEmpty())
|| (!hoursGroup.getCode().startsWith(
orderElement.getCode()))) {
((OrderLine) orderElement)
.incrementLastHoursGroupSequenceCode();
String hoursGroupCode = EntitySequence.formatValue(
numberOfDigits, ((OrderLine) orderElement)
.getLastHoursGroupSequenceCode());
hoursGroup.setCode(orderElement.getCode()
+ EntitySequence.CODE_SEPARATOR_CHILDREN
+ hoursGroupCode);
if (orderElement instanceof OrderLine) {
for (HoursGroup hoursGroup : orderElement.getHoursGroups()) {
if ((hoursGroup.getCode() == null)
|| (hoursGroup.getCode().isEmpty())
|| (!hoursGroup.getCode().startsWith(
orderElement.getCode()))) {
((OrderLine) orderElement)
.incrementLastHoursGroupSequenceCode();
String hoursGroupCode = EntitySequence.formatValue(
numberOfDigits, ((OrderLine) orderElement)
.getLastHoursGroupSequenceCode());
hoursGroup.setCode(orderElement.getCode()
+ EntitySequence.CODE_SEPARATOR_CHILDREN
+ hoursGroupCode);
}
}
}
}
@ -642,4 +651,13 @@ public class Order extends OrderLineGroup implements Comparable {
public void addAskedEndDate(EndDateCommunication endDate) {
this.endDateCommunicationToCustomer.add(endDate);
}
public void markAsNeededToRecalculateSumChargedEfforts() {
neededToRecalculateSumChargedEfforts = true;
}
public boolean isNeededToRecalculateSumChargedEfforts() {
return neededToRecalculateSumChargedEfforts;
}
}

View file

@ -57,7 +57,6 @@ import org.libreplan.business.orders.entities.SchedulingState.Type;
import org.libreplan.business.orders.entities.TaskSource.TaskSourceSynchronization;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.planner.entities.TaskPositionConstraint;
import org.libreplan.business.qualityforms.entities.QualityForm;
import org.libreplan.business.qualityforms.entities.TaskQualityForm;
@ -70,6 +69,8 @@ import org.libreplan.business.templates.entities.OrderElementTemplate;
import org.libreplan.business.trees.ITreeNode;
import org.libreplan.business.util.deepcopy.DeepCopy;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.entities.WorkReportLine;
public abstract class OrderElement extends IntegrationEntity implements
ICriterionRequirable, ITreeNode<OrderElement> {
@ -109,7 +110,7 @@ public abstract class OrderElement extends IntegrationEntity implements
private Boolean dirtyLastAdvanceMeasurementForSpreading = true;
private SumChargedEffort sumChargedEffort = SumChargedEffort.create();
private SumChargedEffort sumChargedEffort;
public OrderElementTemplate getTemplate() {
return template;
@ -291,7 +292,7 @@ public abstract class OrderElement extends IntegrationEntity implements
SchedulingDataForVersion schedulingDataForVersion) {
List<TaskSourceSynchronization> result = new ArrayList<TaskSourceSynchronization>();
if (isSchedulingPoint()) {
if(isSchedulingPointButItWasNot()) {
if (!wasASchedulingPoint()) {
//this element was a container but now it's a scheduling point
//we have to remove the TaskSource which contains a TaskGroup instead of TaskElement
removeTaskSource(result);
@ -325,13 +326,9 @@ public abstract class OrderElement extends IntegrationEntity implements
}
private boolean wasASchedulingPoint() {
return getTaskSource() != null
&& getTaskSource().getTask() instanceof Task;
}
private boolean isSchedulingPointButItWasNot() {
return getTaskSource() != null
&& getTaskSource().getTask() instanceof TaskGroup;
SchedulingDataForVersion currentVersionOnDB = getCurrentVersionOnDB();
return SchedulingState.Type.SCHEDULING_POINT == currentVersionOnDB
.getSchedulingStateType();
}
private List<TaskSourceSynchronization> childrenSynchronizations() {
@ -416,11 +413,16 @@ public abstract class OrderElement extends IntegrationEntity implements
}
private TaskSource getOnDBTaskSource() {
SchedulingDataForVersion schedulingDataForVersion = getCurrentVersionOnDB();
return schedulingDataForVersion.getTaskSource();
}
SchedulingDataForVersion getCurrentVersionOnDB() {
OrderVersion version = getCurrentSchedulingData()
.getOriginOrderVersion();
SchedulingDataForVersion schedulingDataForVersion = schedulingDatasForVersion
.get(version);
return schedulingDataForVersion.getTaskSource();
return schedulingDataForVersion;
}
private TaskSourceSynchronization taskSourceRemoval() {
@ -1469,6 +1471,11 @@ public abstract class OrderElement extends IntegrationEntity implements
return super.toString() + " :: " + getName();
}
public List<WorkReportLine> getWorkReportLines(boolean sortedByDate) {
IWorkReportLineDAO workReportLineDAO = Registry.getWorkReportLineDAO();
return workReportLineDAO.findByOrderElementAndChildren(this, sortedByDate);
}
/**
* Checks if it has nay consolidated advance, if not checks if any parent
* has it
@ -1493,4 +1500,6 @@ public abstract class OrderElement extends IntegrationEntity implements
return false;
}
public abstract BigDecimal getBudget();
}

View file

@ -28,6 +28,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.Validate;
import org.hibernate.validator.AssertTrue;
import org.hibernate.validator.NotNull;
import org.hibernate.validator.Valid;
@ -72,6 +73,8 @@ public class OrderLine extends OrderElement {
return result;
}
private BigDecimal budget = BigDecimal.ZERO.setScale(2);
/**
* Constructor for hibernate. Do not use!
*/
@ -123,7 +126,8 @@ public class OrderLine extends OrderElement {
if (!getMaterialAssignments().isEmpty()) {
return false;
}
if (!getSumChargedEffort().getDirectChargedEffort().isZero()) {
if (getSumChargedEffort() != null
&& !getSumChargedEffort().getDirectChargedEffort().isZero()) {
return false;
}
if (!getTaskElements().isEmpty()) {
@ -370,4 +374,16 @@ public class OrderLine extends OrderElement {
}
}
public void setBudget(BigDecimal budget) {
Validate.isTrue(budget.compareTo(BigDecimal.ZERO) >= 0,
"budget cannot be negative");
this.budget = budget.setScale(2);
}
@Override
@NotNull(message = "budget not specified")
public BigDecimal getBudget() {
return budget;
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -58,6 +58,14 @@ import org.libreplan.business.templates.entities.OrderLineGroupTemplate;
import org.libreplan.business.trees.ITreeParentNode;
/**
* Represents every container in the WBS view. A task of the WBS that has some
* children.<br />
*
* The project itself is also an {@link OrderLineGroup}.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class OrderLineGroup extends OrderElement implements
ITreeParentNode<OrderElement> {
@ -93,6 +101,9 @@ public class OrderLineGroup extends OrderElement implements
protected void onChildAddedAdditionalActions(OrderElement newChild) {
updateCriterionRequirements();
newChild.updateLabels();
if (!newChild.isNewObject()) {
getOrder().markAsNeededToRecalculateSumChargedEfforts();
}
}
@Override
@ -101,6 +112,9 @@ public class OrderLineGroup extends OrderElement implements
removeChildTask(removedChild);
}
updateCriterionRequirements();
if (!removedChild.isNewObject()) {
getOrder().markAsNeededToRecalculateSumChargedEfforts();
}
}
private void removeChildTask(OrderElement removedChild) {
@ -1134,4 +1148,13 @@ public class OrderLineGroup extends OrderElement implements
return true;
}
@Override
public BigDecimal getBudget() {
BigDecimal budget = BigDecimal.ZERO.setScale(2);
for (OrderElement child : children) {
budget = budget.add(child.getBudget());
}
return budget;
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -33,14 +33,24 @@ import org.libreplan.business.workingday.EffortDuration;
*/
public class SumChargedEffort extends BaseEntity {
private OrderElement orderElement;
private EffortDuration directChargedEffort = EffortDuration.zero();
private EffortDuration indirectChargedEffort = EffortDuration.zero();
protected SumChargedEffort() {}
public static SumChargedEffort create() {
return create(new SumChargedEffort());
private SumChargedEffort(OrderElement orderElement) {
this.orderElement = orderElement;
}
public static SumChargedEffort create(OrderElement orderElement) {
return create(new SumChargedEffort(orderElement));
}
public OrderElement getOrderElement() {
return orderElement;
}
public void addDirectChargedEffort(EffortDuration directChargedEffort) {
@ -76,4 +86,13 @@ public class SumChargedEffort extends BaseEntity {
return directChargedEffort.plus(indirectChargedEffort);
}
public boolean isZero() {
return directChargedEffort.isZero() && indirectChargedEffort.isZero();
}
public void reset() {
directChargedEffort = EffortDuration.zero();
indirectChargedEffort = EffortDuration.zero();
}
}

View file

@ -0,0 +1,123 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 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.business.orders.entities;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.libreplan.business.orders.daos.ISumChargedEffortDAO;
import org.libreplan.business.workreports.entities.WorkReport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.stereotype.Component;
/**
* Class to recalculate {@link SumChargedEffort} for an {@link Order}.<br />
*
* This is needed to be called when some elements are moved in the {@link Order}
* .<br />
*
* This class uses a thread, in order to call one by one all the requests
* received. Moreover, if there's any concurrency issue (because of some reports
* were saving in the meanwhile) the recalculation is repeated again (with
* <code>MAX_ATTEMPS_BECAUSE_CONCURRENCY</code> as maximum) till it's performed
* without concurrency problems.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class SumChargedEffortRecalculator implements
ISumChargedEffortRecalculator {
private static final Log LOG = LogFactory
.getLog(SumChargedEffortRecalculator.class);
/**
* Number of times that an order is tried to be recalculated if there is any
* concurrency issue.<br />
*
* Concurrency problems could happen because while the recalculation is
* being done a {@link WorkReport} is saved with elements in the same
* {@link Order}.
*/
protected static final int MAX_ATTEMPS_BECAUSE_CONCURRENCY = 100;
@Autowired
private ISumChargedEffortDAO sumChargedEffortDAO;
/**
* Single thread executor in order to perform the recalculations one by one.
*/
private ExecutorService executor = Executors.newSingleThreadExecutor();
@Override
public void recalculate(Long orderId) {
LOG.info("Mark order (id=" + orderId + ") to be recalculated");
executor.execute(getRecalculationThread(orderId));
}
private Runnable getRecalculationThread(final Long orderId) {
return new Runnable() {
@Override
public void run() {
try {
recalculateSumChargedEfforts(orderId);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private void recalculateSumChargedEfforts(Long orderId)
throws InterruptedException {
recalculateSumChargedEfforts(orderId, 0);
}
private void recalculateSumChargedEfforts(Long orderId, int counter)
throws InterruptedException {
if (counter > MAX_ATTEMPS_BECAUSE_CONCURRENCY) {
LOG.error("Impossible to recalculate order (id=" + orderId
+ ") due to concurrency problems");
return;
}
try {
LOG.info("Recalculate order (id=" + orderId + ")");
sumChargedEffortDAO.recalculateSumChargedEfforts(orderId);
} catch (OptimisticLockingFailureException e) {
// Wait 1 second and try again
LOG.info("Concurrency problem recalculating order (id="
+ orderId + ") trying again in 1 second (attempt "
+ counter + ")");
Thread.sleep(1000);
counter++;
recalculateSumChargedEfforts(orderId, counter);
}
}
};
}
}

View file

@ -0,0 +1,75 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 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.business.planner.entities;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.Validate;
import org.joda.time.LocalDate;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.workingday.EffortDuration;
/**
* Computes aggregate values on a set{@link DayAssignment}.
* <p>
* @author Nacho Barrientos <nacho@igalia.com>
*/
public class AggregateOfDayAssignments {
public static AggregateOfDayAssignments createByDataRange(
List<DayAssignment> assignments,
Date start,
Date end) {
assignments = DayAssignment.orderedByDay(assignments);
return new AggregateOfDayAssignments(
DayAssignment.getAtInterval(assignments,
new LocalDate(start), new LocalDate(end)));
}
public static AggregateOfDayAssignments create(List<DayAssignment> assignments) {
return new AggregateOfDayAssignments(assignments);
}
private Set<DayAssignment> dayAssignments;
private AggregateOfDayAssignments(
Collection<DayAssignment> assignments) {
Validate.notNull(assignments);
Validate.noNullElements(assignments);
this.dayAssignments = new HashSet<DayAssignment>(
assignments);
}
public EffortDuration getTotalTime() {
EffortDuration sum = EffortDuration.zero();
for (DayAssignment dayAssignment : dayAssignments) {
sum = EffortDuration.sum(sum, dayAssignment.getDuration());
}
return sum;
}
}

View file

@ -0,0 +1,59 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 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.business.planner.entities;
import java.math.BigDecimal;
import org.libreplan.business.orders.entities.OrderElement;
/**
* Interface to calculate the money cost of a {@link TaskElement}.
*
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/
public interface IMoneyCostCalculator {
/**
* Returns the money cost of a {@link OrderElement} taking into account all
* its children.<br />
*
* It uses the {@link OrderElement} in order to calculate the cost using the
* following formula:<br />
* <tt>Sum of all the hours devoted to a task multiplied by the cost of
* each hour according to these parameters (type of hour, cost category of
* the resource, date of the work report)</tt><br />
*
* If there is not relationship between resource and type of hour through
* the cost categories, the price used is the default one for the type of
* hour.
*
* @param The
* {@link OrderElement} to calculate the money cost
* @return Money cost of the order element and all its children
*/
BigDecimal getMoneyCost(OrderElement orderElement);
/**
* Resets the map used to save cached values of money cost for each
* {@link OrderElement}
*/
void resetMoneyCostMap();
}

View file

@ -0,0 +1,126 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 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.business.planner.entities;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.libreplan.business.costcategories.daos.IHourCostDAO;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.entities.WorkReportLine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* Class to calculate the money cost of a {@link TaskElement}.<br />
*
* Money cost is calculated checking the hours reported for that task and using
* the cost category of each resource in the different dates.<br />
*
* Money cost is stored in a map that will be cached in memeroy. This map could
* be reseted when needed with method {@code resetMoneyCostMap}.
*
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class MoneyCostCalculator implements IMoneyCostCalculator {
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired
private IHourCostDAO hourCostDAO;
private Map<OrderElement, BigDecimal> moneyCostMap = new HashMap<OrderElement, BigDecimal>();
@Override
public void resetMoneyCostMap() {
moneyCostMap = new HashMap<OrderElement, BigDecimal>();
}
@Override
public BigDecimal getMoneyCost(OrderElement orderElement) {
BigDecimal result = moneyCostMap.get(orderElement);
if (result != null) {
return result;
}
result = BigDecimal.ZERO.setScale(2);
for (OrderElement each : orderElement.getChildren()) {
result = result.add(getMoneyCost(each));
}
result = result.add(getMoneyCostFromOwnWorkReportLines(orderElement))
.setScale(2, RoundingMode.HALF_UP);
moneyCostMap.put(orderElement, result);
return result;
}
private BigDecimal getMoneyCostFromOwnWorkReportLines(OrderElement orderElement) {
List<WorkReportLine> workReportLines = workReportLineDAO
.findByOrderElement(orderElement);
BigDecimal result = BigDecimal.ZERO.setScale(2);
for (WorkReportLine workReportLine : workReportLines) {
BigDecimal priceCost = hourCostDAO
.getPriceCostFromResourceDateAndType(
workReportLine.getResource(),
workReportLine.getLocalDate(),
workReportLine.getTypeOfWorkHours());
// If cost undefined via CostCategory get it from type
if (priceCost == null) {
priceCost = workReportLine.getTypeOfWorkHours()
.getDefaultPrice();
}
BigDecimal cost = priceCost.multiply(workReportLine.getEffort()
.toHoursAsDecimalWithScale(2));
result = result.add(cost);
}
return result;
}
/**
* Divides {@code moneyCost} by {@code budget} if {@code budget} is
* different from 0. Otherwise, returns 0.
*
* @param moneyCost
* @param budget
* @return A BigDecimal from 0 to 1 with the proportion
*/
public static BigDecimal getMoneyCostProportion(BigDecimal moneyCost,
BigDecimal budget) {
if (budget.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
return moneyCost.divide(budget, 2, RoundingMode.HALF_UP);
}
}

View file

@ -22,11 +22,15 @@
package org.libreplan.business.planner.entities;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.LocalDate;
import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.planner.entities.DayAssignment.FilterType;
import org.libreplan.business.workingday.EffortDuration;
/**
*
@ -53,6 +57,12 @@ public class PlanningData extends BaseEntity {
private BigDecimal progressByNumHours;
private BigDecimal theoreticalProgressByNumHoursForAllTasks;
private BigDecimal theoreticalProgressByDurationForCriticalPath;
private BigDecimal theoreticalProgressByNumHoursForCriticalPath;
public PlanningData() {
}
@ -69,6 +79,18 @@ public class PlanningData extends BaseEntity {
return progressByNumHours;
}
public BigDecimal getTheoreticalProgressByNumHoursForAllTasks() {
return theoreticalProgressByNumHoursForAllTasks;
}
public BigDecimal getTheoreticalProgressByDurationForCriticalPath() {
return theoreticalProgressByDurationForCriticalPath;
}
public BigDecimal getTheoreticalProgressByNumHoursForCriticalPath() {
return theoreticalProgressByNumHoursForCriticalPath;
}
private PlanningData(TaskGroup rootTask) {
this.rootTask = rootTask;
}
@ -82,6 +104,11 @@ public class PlanningData extends BaseEntity {
.getAdvancePercentageChildren();
progressByDuration = calculateByDuration(criticalPath);
progressByNumHours = calculateByNumHours(criticalPath);
Date now = new Date();
theoreticalProgressByNumHoursForAllTasks = rootTask.getTheoreticalAdvancePercentageUntilDate(now);
theoreticalProgressByDurationForCriticalPath = calculateTheoreticalAdvanceByDurationForCriticalPath(criticalPath, now);
theoreticalProgressByNumHoursForCriticalPath = calculateTheoreticalAdvanceByNumHoursForCriticalPath(criticalPath, now);
}
private BigDecimal calculateByDuration(List<Task> criticalPath) {
@ -132,4 +159,35 @@ public class PlanningData extends BaseEntity {
return divide(totalProgress, totalNumHours);
}
private BigDecimal calculateTheoreticalAdvanceByNumHoursForCriticalPath(
List<Task> criticalPath, Date limit) {
EffortDuration theoreticalCompletedTime = EffortDuration.zero();
EffortDuration totalAssignedTime = EffortDuration.zero();
for (Task each: criticalPath) {
theoreticalCompletedTime = EffortDuration.sum(
theoreticalCompletedTime,
each.getTheoreticalCompletedTimeUntilDate(limit));
totalAssignedTime = EffortDuration.sum(
totalAssignedTime,
AggregateOfDayAssignments.create(
each.getDayAssignments(FilterType.KEEP_ALL))
.getTotalTime());
}
return theoreticalCompletedTime.dividedByAndResultAsBigDecimal(totalAssignedTime);
}
private BigDecimal calculateTheoreticalAdvanceByDurationForCriticalPath(
List<Task> criticalPath, Date limit) {
int totalTheoreticalProgressDays = 0;
int totalDurationDays = 0;
LocalDate limitLocalDate = new LocalDate(limit);
for (Task each : criticalPath) {
totalTheoreticalProgressDays += each.getWorkableDaysFromLimitedByEndOfTheTask(limitLocalDate);
totalDurationDays += each.getWorkableDays();
}
return divide(new BigDecimal(totalTheoreticalProgressDays), totalDurationDays);
}
}

View file

@ -24,9 +24,11 @@ package org.libreplan.business.planner.entities;
import static java.util.Collections.emptyList;
import static org.libreplan.business.workingday.EffortDuration.min;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -42,10 +44,14 @@ import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
import org.libreplan.business.calendars.entities.ICalendar;
import org.libreplan.business.calendars.entities.SameWorkHoursEveryDay;
import org.libreplan.business.externalcompanies.entities.ExternalCompany;
import org.libreplan.business.orders.entities.AggregatedHoursGroup;
import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.SumChargedEffort;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.planner.entities.DayAssignment.FilterType;
import org.libreplan.business.planner.entities.Dependency.Type;
import org.libreplan.business.planner.entities.DerivedAllocationGenerator.IWorkerFinder;
import org.libreplan.business.planner.entities.ResourceAllocation.Direction;
import org.libreplan.business.planner.entities.allocationalgorithms.AllocationModification;
@ -58,6 +64,7 @@ import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.Worker;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.util.TaskElementVisitor;
import org.libreplan.business.util.deepcopy.AfterCopy;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
@ -121,6 +128,8 @@ public class Task extends TaskElement implements ITaskPositionConstrained {
private CalculatedValue calculatedValue = CalculatedValue.END_DATE;
private TaskStatusEnum currentStatus = null;
private Set<ResourceAllocation<?>> resourceAllocations = new HashSet<ResourceAllocation<?>>();
@Valid
@ -847,6 +856,10 @@ public class Task extends TaskElement implements ITaskPositionConstrained {
return subcontractedTaskData;
}
public ExternalCompany getSubcontractedCompany() {
return subcontractedTaskData.getExternalCompany();
}
public void removeAllSatisfiedResourceAllocations() {
Set<ResourceAllocation<?>> resourceAllocations = getSatisfiedResourceAllocations();
for (ResourceAllocation<?> resourceAllocation : resourceAllocations) {
@ -866,6 +879,10 @@ public class Task extends TaskElement implements ITaskPositionConstrained {
return (subcontractedTaskData != null);
}
public String getSubcontractionName() {
return subcontractedTaskData.getExternalCompany().getName();
}
public boolean isSubcontractedAndWasAlreadySent() {
return (subcontractedTaskData != null)
&& (!subcontractedTaskData.getState()
@ -984,6 +1001,29 @@ public class Task extends TaskElement implements ITaskPositionConstrained {
return result;
}
/* Older methods didn't consider until dates more recent than
* task end date
*/
public Integer getWorkableDaysFromLimitedByEndOfTheTask(LocalDate end) {
return getWorkableDaysFromLimitedByEndOfTheTask(getStartAsLocalDate(), end);
}
public Integer getWorkableDaysFromLimitedByEndOfTheTask(LocalDate startInclusive,
LocalDate endExclusive) {
int result = 0;
if(endExclusive.compareTo(this.getEndAsLocalDate()) > 0) {
endExclusive = getIntraDayEndDate().asExclusiveEnd();
}
for (LocalDate current = startInclusive; current
.compareTo(endExclusive) < 0; current = current
.plusDays(1)) {
if (isWorkable(current)) {
result++;
}
}
return result;
}
public LocalDate calculateEndGivenWorkableDays(int workableDays) {
return calculateEndGivenWorkableDays(getIntraDayStartDate().getDate(),
workableDays);
@ -1045,4 +1085,137 @@ public class Task extends TaskElement implements ITaskPositionConstrained {
return true;
}
@Override
public EffortDuration getTheoreticalCompletedTimeUntilDate(Date date) {
return AggregateOfDayAssignments.createByDataRange(
this.getDayAssignments(FilterType.KEEP_ALL),
this.getStartDate(),
date).getTotalTime();
}
public TaskStatusEnum getTaskStatus() {
if (this.isFinished()) {
return TaskStatusEnum.FINISHED;
} else if (this.isInProgress()) {
return TaskStatusEnum.IN_PROGRESS;
} else if (this.isReadyToStart()) {
return TaskStatusEnum.READY_TO_START;
} else if (this.isBlocked()){
return TaskStatusEnum.BLOCKED;
} else {
throw new RuntimeException("Unknown task status. You've found a bug :)");
}
}
public TaskDeadlineViolationStatusEnum getDeadlineViolationStatus() {
LocalDate deadline = this.getDeadline();
if (deadline == null) {
return TaskDeadlineViolationStatusEnum.NO_DEADLINE;
} else if (this.getEndAsLocalDate().isAfter(deadline)) {
return TaskDeadlineViolationStatusEnum.DEADLINE_VIOLATED;
} else {
return TaskDeadlineViolationStatusEnum.ON_SCHEDULE;
}
}
@Override
/* If the status of the task was needed in the past was because
* a TaskGroup needed to calculate children status, but only asked
* if this task was FINISHED or IN_PROGRESS. Thus, there is no need
* to cache other statutes because they only will be queried once.
*/
public boolean isFinished() {
if (this.currentStatus != null) {
return this.currentStatus == TaskStatusEnum.FINISHED;
} else {
boolean outcome = this.advancePercentageIsOne();
if (outcome == true) {
this.currentStatus = TaskStatusEnum.FINISHED;
}
return outcome;
}
}
@Override
public boolean isInProgress() {
if (this.currentStatus != null) {
return this.currentStatus == TaskStatusEnum.IN_PROGRESS;
} else {
boolean advanceBetweenZeroAndOne = this.advancePertentageIsGreaterThanZero() &&
!advancePercentageIsOne();
boolean outcome = advanceBetweenZeroAndOne || this.hasAttachedWorkReports();
if (outcome == true) {
this.currentStatus = TaskStatusEnum.IN_PROGRESS;
}
return outcome;
}
}
public boolean isReadyToStart() {
if (!this.advancePercentageIsZero() || this.hasAttachedWorkReports()) {
return false;
}
Set<Dependency> dependencies = this.getDependenciesWithThisDestination();
for (Dependency dependency: dependencies) {
Type dependencyType = dependency.getType();
if (dependencyType.equals(Type.END_START)) {
if (!dependency.getOrigin().isFinished()) {
return false;
}
} else if (dependencyType.equals(Type.START_START)) {
if (!dependency.getOrigin().isFinished() &&
!dependency.getOrigin().isInProgress()) {
return false;
}
}
}
return true;
}
public boolean isBlocked() {
if (!this.advancePercentageIsZero() || this.hasAttachedWorkReports()) {
return false;
}
Set<Dependency> dependencies = this.getDependenciesWithThisDestination();
for (Dependency dependency: dependencies) {
Type dependencyType = dependency.getType();
if (dependencyType.equals(Type.END_START)) {
if (!dependency.getOrigin().isFinished()) {
return true;
}
} else if (dependencyType.equals(Type.START_START)) {
if (!dependency.getOrigin().isFinished() &&
!dependency.getOrigin().isInProgress()) {
return true;
}
}
}
return false;
}
private boolean advancePertentageIsGreaterThanZero() {
return this.getAdvancePercentage().compareTo(BigDecimal.ZERO) > 0;
}
private boolean advancePercentageIsZero() {
return this.getAdvancePercentage().compareTo(BigDecimal.ZERO) == 0;
}
private boolean advancePercentageIsOne() {
return this.getAdvancePercentage().compareTo(BigDecimal.ONE) == 0;
}
private boolean hasAttachedWorkReports() {
SumChargedEffort sumChargedEffort = this.getOrderElement().getSumChargedEffort();
return sumChargedEffort != null && !sumChargedEffort.isZero();
}
public void acceptVisitor(TaskElementVisitor visitor) {
visitor.visit(this);
}
@Override
public void resetStatus() {
this.currentStatus = null;
}
}

View file

@ -1,8 +1,6 @@
/*
* 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
@ -19,30 +17,31 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.print;
package org.libreplan.business.planner.entities;
import static org.zkoss.ganttz.i18n.I18nHelper._;
import static org.libreplan.business.i18n.I18nHelper._;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Enumerate of {@Link Task} deadline violation statuses.
*
* NO_DEADLINE: Task has no deadline set.
* DEADLINE_VIOLATED: Task didn't finish on time.
* ON_SCHEDULE: Either Task is ahead schedule or finished just in time.
*
* @author Nacho Barrientos <nacho@igalia.com>
*/
public enum TaskDeadlineViolationStatusEnum {
NO_DEADLINE(_("No deadline")),
DEADLINE_VIOLATED(_("Deadline violated")),
ON_SCHEDULE(_("On schedule"));
private String value;
public class CutyCaptTimeout extends Thread {
long timeout;
private static final Log LOG = LogFactory.getLog(CutyPrint.class);
CutyCaptTimeout(int timeout) {
this.timeout = timeout;
private TaskDeadlineViolationStatusEnum(String value) {
this.value = value;
}
public void run() {
try {
sleep(timeout);
Runtime.getRuntime().exec("killall CutyCapt");
} catch (Exception e) {
LOG.error(_("CutycaptTimeout thread exception"), e);
}
public String toString() {
return value;
}
}
}

View file

@ -45,13 +45,16 @@ import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.common.entities.ProgressType;
import org.libreplan.business.externalcompanies.entities.ExternalCompany;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderStatusEnum;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.planner.entities.DayAssignment.FilterType;
import org.libreplan.business.planner.entities.Dependency.Type;
import org.libreplan.business.resources.daos.IResourcesSearcher;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.util.TaskElementVisitor;
import org.libreplan.business.util.deepcopy.OnCopy;
import org.libreplan.business.util.deepcopy.Strategy;
import org.libreplan.business.workingday.EffortDuration;
@ -401,7 +404,7 @@ public abstract class TaskElement extends BaseEntity {
}
}
void add(Dependency dependency) {
public void add(Dependency dependency) {
if (this.equals(dependency.getOrigin())) {
dependenciesWithThisOrigin.add(dependency);
}
@ -573,6 +576,10 @@ public abstract class TaskElement extends BaseEntity {
return false;
}
public String getSubcontractionName() {
return "";
}
public boolean isSubcontractedAndWasAlreadySent() {
// Just Task could be subcontracted
return false;
@ -679,6 +686,7 @@ public abstract class TaskElement extends BaseEntity {
public void setAdvancePercentage(BigDecimal advancePercentage) {
this.advancePercentage = advancePercentage;
this.resetStatus();
}
private EffortDuration sumOfAssignedEffort = EffortDuration.hours(0);
@ -721,6 +729,27 @@ public abstract class TaskElement extends BaseEntity {
return result;
}
public abstract EffortDuration getTheoreticalCompletedTimeUntilDate(Date date);
public BigDecimal getTheoreticalAdvancePercentageUntilDate(Date date) {
EffortDuration totalAllocatedTime = AggregateOfDayAssignments.create(
this.getDayAssignments(FilterType.KEEP_ALL)).getTotalTime();
EffortDuration totalTheoreticalCompletedTime = this.getTheoreticalCompletedTimeUntilDate(date);
if (totalAllocatedTime.isZero() || totalTheoreticalCompletedTime.isZero()) {
return BigDecimal.ZERO;
}
Validate.isTrue(totalTheoreticalCompletedTime.getSeconds() <= totalAllocatedTime.getSeconds());
return totalTheoreticalCompletedTime.dividedByAndResultAsBigDecimal(totalAllocatedTime);
}
public abstract boolean isFinished();
public abstract boolean isInProgress();
public abstract void acceptVisitor(TaskElementVisitor visitor);
public abstract void resetStatus();
public void updateAdvancePercentageFromOrderElement() {
setAdvancePercentage(getOrderElement().getAdvancePercentage());
}
@ -729,4 +758,15 @@ public abstract class TaskElement extends BaseEntity {
return (this.getParent() == null);
}
public BigDecimal getBudget() {
if ((taskSource != null) && (taskSource.getOrderElement() != null)) {
return taskSource.getOrderElement().getBudget();
}
return null;
}
public ExternalCompany getSubcontractedCompany() {
return null;
}
}

View file

@ -24,6 +24,7 @@ package org.libreplan.business.planner.entities;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
@ -36,6 +37,8 @@ import org.libreplan.business.common.entities.ProgressType;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.resources.daos.IResourcesSearcher;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.util.TaskElementVisitor;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
/**
@ -81,6 +84,27 @@ public class TaskGroup extends TaskElement {
return planningData.getProgressAllByNumHours();
}
public BigDecimal getTheoreticalProgressByNumHoursForAllTasksUntilNow() {
if (planningData == null) {
return BigDecimal.ZERO;
}
return planningData.getTheoreticalProgressByNumHoursForAllTasks();
}
public BigDecimal getTheoreticalProgressByDurationForCriticalPathUntilNow() {
if (planningData == null) {
return BigDecimal.ZERO;
}
return planningData.getTheoreticalProgressByDurationForCriticalPath();
}
public BigDecimal getTheoreticalProgressByNumHoursForCriticalPathUntilNow() {
if (planningData == null) {
return BigDecimal.ZERO;
}
return planningData.getTheoreticalProgressByNumHoursForCriticalPath();
}
@SuppressWarnings("unused")
@AssertTrue(message = "order element associated to a task group must be not null")
private boolean theOrderElementMustBeNotNull() {
@ -304,4 +328,52 @@ public class TaskGroup extends TaskElement {
return false;
}
}
@Override
public EffortDuration getTheoreticalCompletedTimeUntilDate(Date date) {
EffortDuration sum = EffortDuration.zero();
for (TaskElement each: taskElements) {
sum = EffortDuration.sum(sum, each.getTheoreticalCompletedTimeUntilDate(date));
}
return sum;
}
private Boolean isFinished = null;
private Boolean isInProgress = null;
@Override
public boolean isFinished() {
if (this.isFinished == null) {
this.isFinished = new Boolean(true);
for (TaskElement each: taskElements) {
if (!each.isFinished()) {
this.isFinished = new Boolean(false);
break;
}
}
}
return this.isFinished.booleanValue();
}
@Override
public boolean isInProgress() {
if (this.isInProgress == null) {
this.isInProgress = new Boolean(false);
for (TaskElement each: taskElements) {
if (each.isInProgress()) {
this.isInProgress = new Boolean(true);
break;
}
}
}
return this.isInProgress.booleanValue();
}
public void acceptVisitor(TaskElementVisitor visitor) {
visitor.visit(this);
}
@Override
public void resetStatus() {
this.isFinished = this.isInProgress = null;
}
}

View file

@ -25,6 +25,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.Validate;
@ -35,6 +36,8 @@ import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderStatusEnum;
import org.libreplan.business.resources.daos.IResourcesSearcher;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.util.TaskElementVisitor;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
/**
@ -185,6 +188,30 @@ public class TaskMilestone extends TaskElement implements ITaskPositionConstrain
return false;
}
@Override
public EffortDuration getTheoreticalCompletedTimeUntilDate(Date date) {
return EffortDuration.zero();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isInProgress() {
return false;
}
@Override
public void acceptVisitor(TaskElementVisitor visitor) {
throw new RuntimeException("No visitors should visit this type of TaskElement");
}
@Override
public void resetStatus() {
}
@Override
public Boolean belongsClosedProject() {
EnumSet<OrderStatusEnum> CLOSED = EnumSet.of(OrderStatusEnum.CANCELLED,
@ -198,4 +225,4 @@ public class TaskMilestone extends TaskElement implements ITaskPositionConstrain
return false;
}
}
}

View file

@ -28,7 +28,8 @@ public enum TaskStatusEnum {
FINISHED(_("Finished")),
IN_PROGRESS(_("In progress")),
PENDING(_("Pending")),
BLOCKED(_("Blocked"));
BLOCKED(_("Blocked")),
READY_TO_START(_("Ready to start"));
private String value;

View file

@ -0,0 +1,66 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 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.business.planner.entities.visitors;
/**
* Visits task graphs computing deadline violation statuses
* filling in a Map summarizing the status of all tasks.
*
* @author Nacho Barrientos <nacho@igalia.com>
*/
import java.util.EnumMap;
import java.util.Map;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskDeadlineViolationStatusEnum;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.util.TaskElementVisitor;
public class AccumulateTasksDeadlineStatusVisitor extends TaskElementVisitor {
private Map<TaskDeadlineViolationStatusEnum, Integer> taskDeadlineViolationStatusData;
public AccumulateTasksDeadlineStatusVisitor() {
this.taskDeadlineViolationStatusData = new EnumMap<TaskDeadlineViolationStatusEnum, Integer>(
TaskDeadlineViolationStatusEnum.class);
for (TaskDeadlineViolationStatusEnum status : TaskDeadlineViolationStatusEnum
.values()) {
this.taskDeadlineViolationStatusData.put(status, new Integer(0));
}
}
public Map<TaskDeadlineViolationStatusEnum, Integer> getTaskDeadlineViolationStatusData() {
return taskDeadlineViolationStatusData;
}
public void visit(Task task) {
TaskDeadlineViolationStatusEnum status = task.getDeadlineViolationStatus();
Integer currentValue = taskDeadlineViolationStatusData.get(status);
taskDeadlineViolationStatusData.put(status, Integer.valueOf(currentValue.intValue() + 1));
}
public void visit(TaskGroup taskGroup) {
for (TaskElement each: taskGroup.getChildren()) {
each.acceptVisitor(this);
}
}
}

View file

@ -0,0 +1,64 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 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.business.planner.entities.visitors;
/**
* Visits a task graph computing statuses and writing them
* down in a Map.
*
* @author Nacho Barrientos <nacho@igalia.com>
*/
import java.util.EnumMap;
import java.util.Map;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.planner.entities.TaskStatusEnum;
import org.libreplan.business.util.TaskElementVisitor;
public class AccumulateTasksStatusVisitor extends TaskElementVisitor {
private Map<TaskStatusEnum, Integer> taskStatusData;
public AccumulateTasksStatusVisitor() {
this.taskStatusData = new EnumMap<TaskStatusEnum, Integer>(TaskStatusEnum.class);
for (TaskStatusEnum status: TaskStatusEnum.values()) {
this.taskStatusData.put(status, new Integer(0));
}
}
public Map<TaskStatusEnum, Integer> getTaskStatusData() {
return taskStatusData;
}
public void visit(Task task) {
TaskStatusEnum status = task.getTaskStatus();
Integer currentValue = getTaskStatusData().get(status);
taskStatusData.put(status, Integer.valueOf(currentValue.intValue() + 1));
}
public void visit(TaskGroup taskGroup) {
for (TaskElement each: taskGroup.getChildren()) {
each.acceptVisitor(this);
}
}
}

View file

@ -0,0 +1,75 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 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.business.planner.entities.visitors;
/**
* FIXME
* This visitor calculates allocated/spent hours deviation
* for finished tasks.
* @author Nacho Barrientos <nacho@igalia.com>
*/
import java.util.ArrayList;
import java.util.List;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.util.TaskElementVisitor;
import org.libreplan.business.workingday.EffortDuration;
public class CalculateFinishedTasksEstimationDeviationVisitor extends TaskElementVisitor {
private List<Double> deviations;
public CalculateFinishedTasksEstimationDeviationVisitor() {
this.deviations = new ArrayList<Double>();
}
public List<Double> getDeviations() {
return this.deviations;
}
public int getNumberOfConsideredTasks(){
return deviations.size();
}
public void visit(Task task) {
if (task.isFinished()) {
int allocatedHours = task.getAssignedHours();
if (allocatedHours > 0) {
EffortDuration spentEffort = task.getOrderElement()
.getSumChargedEffort().getTotalChargedEffort();
deviations.add(new Double(
((1.0*spentEffort.getHours() -
allocatedHours)/allocatedHours)*100));
} else {
deviations.add(new Double(0.0));
}
}
}
public void visit(TaskGroup taskGroup) {
for (TaskElement each: taskGroup.getChildren()) {
each.acceptVisitor(this);
}
}
}

View file

@ -0,0 +1,70 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 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.business.planner.entities.visitors;
/**
* This visitor calculates lastWorkReportDate/EndDate deviation
* for all finished tasks with work report lines attached.
* @author Nacho Barrientos <nacho@igalia.com>
*/
import java.util.ArrayList;
import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.util.TaskElementVisitor;
import org.libreplan.business.workreports.entities.WorkReportLine;
public class CalculateFinishedTasksLagInCompletionVisitor extends TaskElementVisitor {
private List<Double> deviations;
public CalculateFinishedTasksLagInCompletionVisitor() {
this.deviations = new ArrayList<Double>();
}
public List<Double> getDeviations() {
return this.deviations;
}
public void visit(Task task) {
if (task.isFinished()) {
List<WorkReportLine> workReportLines = task.
getOrderElement().getWorkReportLines(true);
if (workReportLines.size() > 0) {
LocalDate lastRLDate = LocalDate.fromDateFields(
workReportLines.get(workReportLines.size()-1).getDate());
LocalDate endDate = task.getEndAsLocalDate();
deviations.add((double)Days.
daysBetween(endDate, lastRLDate).getDays());
}
}
}
public void visit(TaskGroup taskGroup) {
for (TaskElement each: taskGroup.getChildren()) {
each.acceptVisitor(this);
}
}
}

View file

@ -0,0 +1,48 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 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.business.planner.entities.visitors;
/**
* Visits a task graph resetting task statuses.
*
* @author Nacho Barrientos <nacho@igalia.com>
*/
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.util.TaskElementVisitor;
public class ResetTasksStatusVisitor extends TaskElementVisitor {
public ResetTasksStatusVisitor() {
}
public void visit(Task task) {
task.resetStatus();
}
public void visit(TaskGroup taskGroup) {
taskGroup.resetStatus();
for (TaskElement each: taskGroup.getChildren()) {
each.acceptVisitor(this);
}
}
}

View file

@ -594,4 +594,6 @@ public abstract class OrderElementTemplate extends BaseEntity implements
this.origin = origin;
}
public abstract BigDecimal getBudget();
}

View file

@ -20,6 +20,7 @@
*/
package org.libreplan.business.templates.entities;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -276,4 +277,13 @@ public class OrderLineGroupTemplate extends OrderElementTemplate implements
return result;
}
@Override
public BigDecimal getBudget() {
BigDecimal budget = BigDecimal.ZERO.setScale(2);
for (OrderElementTemplate child : children) {
budget = budget.add(child.getBudget());
}
return budget;
}
}

View file

@ -22,6 +22,7 @@ package org.libreplan.business.templates.entities;
import static org.libreplan.business.i18n.I18nHelper._;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -29,6 +30,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.Validate;
import org.hibernate.validator.NotNull;
import org.hibernate.validator.Valid;
import org.libreplan.business.orders.entities.HoursGroup;
@ -51,6 +53,7 @@ public class OrderLineTemplate extends OrderElementTemplate {
public static OrderLineTemplate create(OrderLine orderLine) {
OrderLineTemplate beingBuilt = new OrderLineTemplate();
copyHoursGroup(orderLine.getHoursGroups(), beingBuilt);
beingBuilt.setBudget(orderLine.getBudget());
return create(beingBuilt, orderLine);
}
@ -67,9 +70,12 @@ public class OrderLineTemplate extends OrderElementTemplate {
return createNew(new OrderLineTemplate());
}
private BigDecimal budget = BigDecimal.ZERO.setScale(2);
protected <T extends OrderElement> T setupElementParts(T orderElement) {
super.setupElementParts(orderElement);
setupHoursGroups((OrderLine) orderElement);
setupBudget((OrderLine) orderElement);
return orderElement;
}
@ -81,6 +87,10 @@ public class OrderLineTemplate extends OrderElementTemplate {
orderLine.setHoursGroups(result);
}
private void setupBudget(OrderLine orderLine) {
orderLine.setBudget(getBudget());
}
@Override
public List<OrderElementTemplate> getChildrenTemplates() {
return Collections.emptyList();
@ -215,4 +225,16 @@ public class OrderLineTemplate extends OrderElementTemplate {
hoursGroupOrderLineTemplateHandler.recalculateHoursGroups(this);
}
public void setBudget(BigDecimal budget) {
Validate.isTrue(budget.compareTo(BigDecimal.ZERO) >= 0,
"budget cannot be negative");
this.budget = budget.setScale(2);
}
@Override
@NotNull(message = "budget not specified")
public BigDecimal getBudget() {
return budget;
}
}

View file

@ -0,0 +1,37 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 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.business.util;
/**
* This class represents an abstract visitor to traverse
* task graphs.
*
* @author Nacho Barrientos <nacho@igalia.com>
*/
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskGroup;
public abstract class TaskElementVisitor {
public abstract void visit(Task task);
public abstract void visit(TaskGroup taskGroup);
}

View file

@ -266,6 +266,24 @@ public class EffortDuration implements Comparable<EffortDuration> {
return Fraction.getFraction(this.seconds, effortAssigned.seconds);
}
/**
* <p>
* Divides this duration by other (using total seconds) returning the
* quotient as BigDecimal.
* </p>
* @param other
* @return
*/
public BigDecimal dividedByAndResultAsBigDecimal(EffortDuration other) {
if (other.isZero()) {
return BigDecimal.ZERO;
}
else {
return new BigDecimal(this.getSeconds()).divide(
new BigDecimal(other.getSeconds()), 8, BigDecimal.ROUND_HALF_EVEN);
}
}
/**
* Calculates the remainder resulting of doing the integer division of both
* durations

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -70,7 +70,9 @@ import org.libreplan.business.calendars.entities.Capacity;
* @see PartialDay
* @see LocalDate
* @see EffortDuration
*
* @author Óscar González Fernández
* @author Manuel Rego Casasnovas <rego@igalia.com>
*
*/
public class IntraDayDate implements Comparable<IntraDayDate> {
@ -497,4 +499,57 @@ public class IntraDayDate implements Comparable<IntraDayDate> {
return startOfDay(date);
}
/**
* Returns the {@link EffortDuration} until {@code end} considering 8h per
* day of effort.
*
* @param end
* @return The {@link EffortDuration} until {@code end}
*/
public EffortDuration effortUntil(IntraDayDate end) {
Validate.isTrue(compareTo(end) <= 0);
int days = Days.daysBetween(getDate(), end.getDate()).getDays();
EffortDuration result = EffortDuration.hours(days * 8);
if (!getEffortDuration().isZero()) {
result = result.minus(EffortDuration.hours(8));
result = result.plus(EffortDuration.hours(8).minus(
getEffortDuration()));
}
if (!end.getEffortDuration().isZero()) {
result = result.plus(end.getEffortDuration());
}
return result;
}
/**
* Returns the {@link IntraDayDate} adding the {@code effort} considering 8h
* per day of effort.
*
* @param effort
* @return The {@link IntraDayDate} result of adding the {@code effort}
*/
public IntraDayDate addEffort(EffortDuration effort) {
int secondsPerDay = 3600 * 8;
int seconds = effort.getSeconds();
if (!getEffortDuration().isZero()) {
seconds += getEffortDuration().getSeconds();
}
int days = seconds / secondsPerDay;
EffortDuration extraEffort = EffortDuration.zero();
int extra = seconds % secondsPerDay;
if (extra != 0) {
extraEffort = extraEffort.plus(EffortDuration.seconds(extra));
}
return IntraDayDate.create(getDate().plusDays(days), extraEffort);
}
}

View file

@ -38,6 +38,7 @@ import org.libreplan.business.workreports.entities.WorkReportLine;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* Dao for {@link WorkReportLineDAO}
@ -89,6 +90,7 @@ public class WorkReportLineDAO extends IntegrationEntityDAO<WorkReportLine>
@SuppressWarnings("unchecked")
@Override
@Transactional(readOnly=true)
public List<WorkReportLine> findByOrderElementAndChildren(OrderElement orderElement, boolean sortByDate) {
// Create collection with current orderElement and all its children
Collection<OrderElement> orderElements = orderElement.getAllChildren();

View file

@ -222,4 +222,100 @@
<comment>Rename table to end_date_communication</comment>
<renameTable oldTableName="end_date_communication_to_customer" newTableName="end_date_communication" />
</changeSet>
<changeSet id="add-budget-column-to-order_line" author="mrego">
<comment>add budget column to order_line</comment>
<addColumn tableName="order_line">
<column name="budget" type="DECIMAL(19,2)" />
</addColumn>
<addNotNullConstraint tableName="order_line"
columnName="budget" defaultNullValue="0"
columnDataType="DECIMAL(19,2)" />
<addDefaultValue tableName="order_line"
columnName="budget" defaultValueNumeric="0" />
</changeSet>
<changeSet id="add-budget-column-to-order_line_template" author="mrego">
<comment>add budget column to order_line_template</comment>
<addColumn tableName="order_line_template">
<column name="budget" type="DECIMAL(19,2)" />
</addColumn>
<addNotNullConstraint tableName="order_line_template"
columnName="budget" defaultNullValue="0"
columnDataType="DECIMAL(19,2)" />
<addDefaultValue tableName="order_line_template"
columnName="budget" defaultValueNumeric="0" />
</changeSet>
<changeSet id="change-mapping-order-element-and-sum-charged-effort-postgresql"
author="mrego" dbms="postgresql">
<comment>Change mapping between OrderElement and SumChargedEffort in MySQL</comment>
<addColumn tableName="sum_charged_effort">
<column name="order_element" type="BIGINT" />
</addColumn>
<createProcedure>
CREATE OR REPLACE FUNCTION chageMappingBetweenOrderElementAndSumChargedEffort() RETURNS VOID AS $$
DECLARE
sce RECORD;
BEGIN
FOR sce IN SELECT sum_charged_effort_id AS id FROM order_element
LOOP
EXECUTE 'UPDATE sum_charged_effort '
|| 'SET order_element '
|| ' = (SELECT id FROM order_element '
|| 'WHERE sum_charged_effort_id '
|| ' = '
|| quote_literal(sce.id)
|| ') '
|| 'WHERE id '
|| ' = '
|| quote_literal(sce.id);
END LOOP;
END;
$$ LANGUAGE plpgsql;
</createProcedure>
<sql>SELECT chageMappingBetweenOrderElementAndSumChargedEffort()</sql>
<addForeignKeyConstraint constraintName="sum_charged_effort_order_element_fkey"
baseTableName="sum_charged_effort" baseColumnNames="order_element"
referencedTableName="order_element" referencedColumnNames="id" />
<dropColumn tableName="order_element" columnName="sum_charged_effort_id"/>
</changeSet>
<changeSet id="change-mapping-order-element-and-sum-charged-effort-mysql"
author="mrego" dbms="mysql">
<comment>Change mapping between OrderElement and SumChargedEffort in PostgreSQL</comment>
<addColumn tableName="sum_charged_effort">
<column name="order_element" type="BIGINT" />
</addColumn>
<sql>DROP PROCEDURE IF EXISTS chageMappingBetweenOrderElementAndSumChargedEffort</sql>
<sql splitStatements="false">
CREATE PROCEDURE chageMappingBetweenOrderElementAndSumChargedEffort()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE sce_id INT;
DECLARE cursor_sce_ids CURSOR FOR SELECT sum_charged_effort_id AS id FROM order_element;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor_sce_ids;
ids_loop: LOOP
FETCH cursor_sce_ids INTO sce_id;
IF done THEN
LEAVE ids_loop;
END IF;
UPDATE sum_charged_effort SET order_element = (SELECT id FROM order_element WHERE sum_charged_effort_id = sce_id) WHERE id = sce_id;
END LOOP;
CLOSE cursor_sce_ids;
END;
</sql>
<sql>CALL chageMappingBetweenOrderElementAndSumChargedEffort()</sql>
<addForeignKeyConstraint constraintName="sum_charged_effort_order_element_fkey"
baseTableName="sum_charged_effort" baseColumnNames="order_element"
referencedTableName="order_element" referencedColumnNames="id" />
<dropColumn tableName="order_element" columnName="sum_charged_effort_id"/>
</changeSet>
</databaseChangeLog>

View file

@ -74,12 +74,8 @@
column="scheduling_state_for_version_id" />
</map>
<many-to-one name="sumChargedEffort"
class="SumChargedEffort"
column="sum_charged_effort_id"
cascade="save-update, delete"
unique="true"
lazy="false" />
<one-to-one name="sumChargedEffort" class="SumChargedEffort"
cascade="delete" property-ref="orderElement" />
<joined-subclass name="OrderLineGroup" table="order_line_group">
<key column="order_element_id"></key>
@ -177,6 +173,8 @@
<property name="lastHoursGroupSequenceCode"
column="last_hours_group_sequence_code" access="field" />
<property name="budget" scale="2" access="field" />
</joined-subclass>
</class>
@ -261,6 +259,9 @@
</id>
<version name="version" access="property" type="long" />
<many-to-one name="orderElement" column="order_element"
class="OrderElement" cascade="none" unique="true" />
<property name="directChargedEffort" access="field"
column="direct_charged_effort"
type="org.libreplan.business.workingday.hibernate.EffortDurationType" />

View file

@ -87,6 +87,8 @@
<property name="lastHoursGroupSequenceCode" access="field"
column="last_hours_group_sequence_code" />
<property name="budget" scale="2" access="field" />
</joined-subclass>
</class>

View file

@ -59,6 +59,7 @@ import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLine;
import org.libreplan.business.orders.entities.OrderLineGroup;
import org.libreplan.business.orders.entities.SumChargedEffort;
import org.libreplan.business.qualityforms.daos.IQualityFormDAO;
import org.libreplan.business.qualityforms.entities.QualityForm;
import org.libreplan.business.qualityforms.entities.TaskQualityForm;
@ -420,6 +421,7 @@ public class OrderElementDAOTest {
@Test
public void testSumChargedHoursRelation() throws InstanceNotFoundException {
OrderLine orderLine = createValidOrderLine();
orderLine.setSumChargedEffort(SumChargedEffort.create(orderLine));
orderLine.getSumChargedEffort().addDirectChargedEffort(
EffortDuration.hours(8));

View file

@ -1175,4 +1175,25 @@ public class OrderElementTest {
.getAdvanceType(), equalTo(advanceType2));
}
@Test
public void checkPositiveBudgetInOrderLine() {
OrderLine line = givenOrderLine("task", "code", 10);
line.setBudget(new BigDecimal(100));
assertThat(line.getBudget(), equalTo(new BigDecimal(100).setScale(2)));
}
@Test(expected = IllegalArgumentException.class)
public void checkNonNegativeBudgetInOrderLine() {
OrderLine line = givenOrderLine("task", "code", 10);
line.setBudget(new BigDecimal(-100));
}
@Test
public void checkBudgetInOrderLineGroup() {
OrderLineGroup group = givenOrderLineGroupWithTwoOrderLines(20, 30);
((OrderLine) group.getChildren().get(0)).setBudget(new BigDecimal(50));
((OrderLine) group.getChildren().get(1)).setBudget(new BigDecimal(70));
assertThat(group.getBudget(), equalTo(new BigDecimal(120).setScale(2)));
}
}

View file

@ -0,0 +1,386 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 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.business.test.planner.entities;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.libreplan.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE;
import static org.libreplan.business.test.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_TEST_FILE;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.joda.time.LocalDate;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.libreplan.business.IDataBootstrap;
import org.libreplan.business.costcategories.daos.ICostCategoryDAO;
import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO;
import org.libreplan.business.costcategories.entities.CostCategory;
import org.libreplan.business.costcategories.entities.HourCost;
import org.libreplan.business.costcategories.entities.ResourcesCostCategoryAssignment;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.orders.daos.IOrderElementDAO;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLine;
import org.libreplan.business.orders.entities.OrderLineGroup;
import org.libreplan.business.planner.entities.IMoneyCostCalculator;
import org.libreplan.business.planner.entities.MoneyCostCalculator;
import org.libreplan.business.resources.daos.IResourceDAO;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.Worker;
import org.libreplan.business.scenarios.IScenarioManager;
import org.libreplan.business.scenarios.daos.IOrderVersionDAO;
import org.libreplan.business.scenarios.entities.OrderVersion;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.daos.IWorkReportDAO;
import org.libreplan.business.workreports.daos.IWorkReportTypeDAO;
import org.libreplan.business.workreports.entities.WorkReport;
import org.libreplan.business.workreports.entities.WorkReportLine;
import org.libreplan.business.workreports.entities.WorkReportType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Test for {@link MoneyCostCalculator}
*
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE,
BUSINESS_SPRING_CONFIG_TEST_FILE })
@Transactional
public class MoneyCostCalculatorTest {
@javax.annotation.Resource
private IDataBootstrap scenariosBootstrap;
@Before
public void loadRequiredaData() {
scenariosBootstrap.loadRequiredData();
}
@Autowired
private IMoneyCostCalculator moneyCostCalculator;
@Autowired
private IOrderElementDAO orderElementDAO;
@Autowired
private IResourceDAO resourceDAO;
@Autowired
private IWorkReportDAO workReportDAO;
@Autowired
private IWorkReportTypeDAO workReportTypeDAO;
@Autowired
private ICostCategoryDAO costCategoryDAO;
@Autowired
private ITypeOfWorkHoursDAO typeOfWorkHoursDAO;
@Autowired
private IOrderVersionDAO orderVersionDAO;
@Autowired
private IScenarioManager scenarioManager;
private List<TypeOfWorkHours> typesOfWorkHours = new ArrayList<TypeOfWorkHours>();
private CostCategory costCategory;
private Resource resource;
private List<OrderElement> orderElements = new ArrayList<OrderElement>();
private WorkReportType workReportType;
private WorkReport workReport;
private void givenTypeOfWorkHours(BigDecimal defaultPrice) {
TypeOfWorkHours typeOfWorkHours = TypeOfWorkHours.createUnvalidated(
"default-type-of-work-hours-" + UUID.randomUUID(),
"default-type-of-work-hours-" + UUID.randomUUID(), true,
defaultPrice);
typeOfWorkHoursDAO.save(typeOfWorkHours);
typesOfWorkHours.add(typeOfWorkHours);
}
private void givenCostCategory() {
costCategory = CostCategory.createUnvalidated("default-cost-category",
"default-cost-category", true);
HourCost hourCost = HourCost.createUnvalidated(
"default-hour-cost", new BigDecimal(50), new LocalDate());
hourCost.setType(typesOfWorkHours.get(0));
costCategory.addHourCost(hourCost);
costCategoryDAO.save(costCategory);
}
private void givenResource(boolean relatedWithCostCategory) {
resource = Worker.createUnvalidated("default-resource",
"default-resource", "default-resource", "default-resource");
if (relatedWithCostCategory) {
ResourcesCostCategoryAssignment resourcesCostCategoryAssignment = ResourcesCostCategoryAssignment
.create();
resourcesCostCategoryAssignment
.setCode("resources-cost-category-assignment");
resourcesCostCategoryAssignment.setCostCategory(costCategory);
resourcesCostCategoryAssignment.setInitDate(new LocalDate());
resource.addResourcesCostCategoryAssignment(resourcesCostCategoryAssignment);
}
resourceDAO.save(resource);
}
private void givenOrderElement() {
OrderElement orderElement = OrderLine.createOrderLineWithUnfixedPercentage(100);
orderElement.setCode("default-order-element-" + UUID.randomUUID());
orderElement.setName("default-order-element-" + UUID.randomUUID());
orderElement.getHoursGroups().get(0)
.setCode("default-hours-group-" + UUID.randomUUID());
orderElementDAO.save(orderElement);
orderElements.add(orderElement);
}
private void givenOrderLineGroupWithTwoLines() {
OrderLineGroup orderLineGroup = OrderLineGroup.create();
orderLineGroup.setCode("default-order-line-group-" + UUID.randomUUID());
orderLineGroup.setName("default-order-line-group-" + UUID.randomUUID());
OrderVersion orderVersion = OrderVersion
.createInitialVersion(scenarioManager.getCurrent());
orderVersionDAO.save(orderVersion);
orderLineGroup.useSchedulingDataFor(orderVersion);
OrderLine orderLine1 = OrderLine
.createOrderLineWithUnfixedPercentage(100);
orderLine1.setCode("order-line-1-" + UUID.randomUUID());
orderLine1.setName("order-line-1-" + UUID.randomUUID());
orderLine1.getHoursGroups().get(0)
.setCode("hours-group-1-" + UUID.randomUUID());
orderLineGroup.add(orderLine1);
OrderLine orderLine2 = OrderLine
.createOrderLineWithUnfixedPercentage(100);
orderLine2.setCode("order-line-2-" + UUID.randomUUID());
orderLine2.setName("order-line-2-" + UUID.randomUUID());
orderLine2.getHoursGroups().get(0)
.setCode("hours-group-2-" + UUID.randomUUID());
orderLineGroup.add(orderLine2);
orderElementDAO.save(orderLineGroup);
orderElements.add(orderLineGroup);
orderElements.add(orderLine1);
orderElements.add(orderLine2);
}
private void giveWorkReportType() {
workReportType = WorkReportType.create("default-work-report-type",
"default-work-report-type");
workReportTypeDAO.save(workReportType);
}
private void givenWorkReport() {
givenWorkReport(null);
}
private void givenWorkReport(List<Integer> hoursList) {
workReport = WorkReport.create(workReportType);
workReport.setCode("default-work-report");
for (OrderElement each : orderElements) {
int hours = 10;
if (hoursList != null) {
hours = hoursList.get(orderElements.indexOf(each));
}
workReport.addWorkReportLine(createWorkReportLine(each, hours));
}
workReportDAO.save(workReport);
}
private void givenWorkReportWithSeveralLines(List<Integer> hoursList,
List<TypeOfWorkHours> types) {
workReport = WorkReport.create(workReportType);
workReport.setCode("default-work-report");
for (Integer hour : hoursList) {
workReport.addWorkReportLine(createWorkReportLine(hour,
types.get(hoursList.indexOf(hour))));
}
workReportDAO.save(workReport);
}
private WorkReportLine createWorkReportLine(OrderElement orderElement,
Integer hours) {
WorkReportLine workReportLine = WorkReportLine.create(workReport);
workReportLine.setCode("default-work-report-line-" + UUID.randomUUID());
workReportLine.setDate(new Date());
workReportLine.setResource(resource);
workReportLine.setOrderElement(orderElement);
workReportLine.setTypeOfWorkHours(typesOfWorkHours.get(0));
workReportLine.setEffort(EffortDuration.hours(hours));
return workReportLine;
}
private WorkReportLine createWorkReportLine(Integer hours,
TypeOfWorkHours type) {
WorkReportLine workReportLine = WorkReportLine.create(workReport);
workReportLine.setCode("default-work-report-line-" + UUID.randomUUID());
workReportLine.setDate(new Date());
workReportLine.setResource(resource);
workReportLine.setOrderElement(orderElements.get(0));
workReportLine.setTypeOfWorkHours(type);
workReportLine.setEffort(EffortDuration.hours(hours));
return workReportLine;
}
private void givenBasicExample() {
givenTypeOfWorkHours(new BigDecimal(30));
givenCostCategory();
givenResource(true);
givenOrderElement();
giveWorkReportType();
givenWorkReport();
}
private void givenBasicExampleWithoutCostCategoryRelationship() {
givenTypeOfWorkHours(new BigDecimal(30));
givenResource(false);
givenOrderElement();
giveWorkReportType();
givenWorkReport();
}
private void givenExampleOrderLineGroup() {
givenTypeOfWorkHours(new BigDecimal(30));
givenCostCategory();
givenResource(true);
givenOrderLineGroupWithTwoLines();
giveWorkReportType();
givenWorkReport();
}
private void givenExampleOrderLineGroupWithDifferentHours(
List<Integer> hoursList) {
givenTypeOfWorkHours(new BigDecimal(30));
givenCostCategory();
givenResource(true);
givenOrderLineGroupWithTwoLines();
giveWorkReportType();
givenWorkReport(hoursList);
}
private void givenExampleWithoutCostCategoryRelationshipButDifferentTypeOfHours(
List<Integer> hoursList, List<BigDecimal> pricesList) {
for (BigDecimal price : pricesList) {
givenTypeOfWorkHours(price);
}
givenResource(false);
givenOrderElement();
giveWorkReportType();
givenWorkReportWithSeveralLines(hoursList, typesOfWorkHours);
}
@Test
public void basicTest() {
givenBasicExample();
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
equalTo(new BigDecimal(500).setScale(2)));
}
@Test
public void basicTestWithoutCostCategoryRelationship() {
givenBasicExampleWithoutCostCategoryRelationship();
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
equalTo(new BigDecimal(300).setScale(2)));
}
@Test
public void exampleOrderLineGroup() {
givenExampleOrderLineGroup();
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
equalTo(new BigDecimal(1500).setScale(2)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(1)),
equalTo(new BigDecimal(500).setScale(2)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(2)),
equalTo(new BigDecimal(500).setScale(2)));
}
@Test
public void exampleOrderLineGroupWithDifferentHours1() {
givenExampleOrderLineGroupWithDifferentHours(Arrays.asList(0, 10, 5));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
equalTo(new BigDecimal(750).setScale(2)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(1)),
equalTo(new BigDecimal(500).setScale(2)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(2)),
equalTo(new BigDecimal(250).setScale(2)));
}
@Test
public void exampleOrderLineGroupWithDifferentHours2() {
givenExampleOrderLineGroupWithDifferentHours(Arrays.asList(6, 0, 0));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
equalTo(new BigDecimal(300).setScale(2)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(1)),
equalTo(new BigDecimal(0).setScale(2)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(2)),
equalTo(new BigDecimal(0).setScale(2)));
}
@Test
public void exampleOrderLineGroupWithDifferentHours3() {
givenExampleOrderLineGroupWithDifferentHours(Arrays.asList(6, 5, 10));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
equalTo(new BigDecimal(1050).setScale(2)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(1)),
equalTo(new BigDecimal(250).setScale(2)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(2)),
equalTo(new BigDecimal(500).setScale(2)));
}
@Test
public void exampleWithoutCostCategoryRelationshipButDifferentTypeOfHours1() {
givenExampleWithoutCostCategoryRelationshipButDifferentTypeOfHours(
Arrays.asList(10, 5),
Arrays.asList(new BigDecimal(30), new BigDecimal(50)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
equalTo(new BigDecimal(550).setScale(2)));
}
@Test
public void exampleWithoutCostCategoryRelationshipButDifferentTypeOfHours2() {
givenExampleWithoutCostCategoryRelationshipButDifferentTypeOfHours(
Arrays.asList(10, 5, 8), Arrays.asList(new BigDecimal(30),
new BigDecimal(50), new BigDecimal(40)));
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
equalTo(new BigDecimal(870).setScale(2)));
}
}

View file

@ -79,24 +79,30 @@ public class SpecificResourceAllocationTest {
private ResourceCalendar calendar;
private void givenResourceCalendarAlwaysReturning(final int hours) {
this.calendar = createNiceMock(ResourceCalendar.class);
expect(this.calendar.getCapacityOn(isA(PartialDay.class)))
.andReturn(EffortDuration.hours(hours)).anyTimes();
IAnswer<? extends EffortDuration> asDurationAnswer = asDurationOnAnswer(hours(hours));
expect(
this.calendar.asDurationOn(isA(PartialDay.class),
isA(ResourcesPerDay.class)))
.andAnswer(asDurationAnswer).anyTimes();
expect(this.calendar.getCapacityWithOvertime(isA(LocalDate.class)))
.andReturn(
Capacity.create(hours(hours))
.overAssignableWithoutLimit()).anyTimes();
expect(this.calendar.getAvailability()).andReturn(
AvailabilityTimeLine.allValid()).anyTimes();
replay(this.calendar);
this.calendar = createResourceCalendarAlwaysReturning(hours);
}
private IAnswer<? extends EffortDuration> asDurationOnAnswer(
public static ResourceCalendar createResourceCalendarAlwaysReturning(final int hours) {
ResourceCalendar workerCalendar;
workerCalendar = createNiceMock(ResourceCalendar.class);
expect(workerCalendar.getCapacityOn(isA(PartialDay.class)))
.andReturn(EffortDuration.hours(hours)).anyTimes();
IAnswer<? extends EffortDuration> asDurationAnswer = asDurationOnAnswer(hours(hours));
expect(
workerCalendar.asDurationOn(isA(PartialDay.class),
isA(ResourcesPerDay.class)))
.andAnswer(asDurationAnswer).anyTimes();
expect(workerCalendar.getCapacityWithOvertime(isA(LocalDate.class)))
.andReturn(
Capacity.create(hours(hours))
.overAssignableWithoutLimit()).anyTimes();
expect(workerCalendar.getAvailability()).andReturn(
AvailabilityTimeLine.allValid()).anyTimes();
replay(workerCalendar);
return workerCalendar;
}
private static IAnswer<? extends EffortDuration> asDurationOnAnswer(
final EffortDuration duration) {
return new IAnswer<EffortDuration>() {

View file

@ -22,9 +22,12 @@
package org.libreplan.business.test.planner.entities;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@ -89,6 +92,34 @@ public class TaskGroupTest {
taskGroup.getChildren().set(0, null);
}
@Test
public void taskGroupIsFinishedIifAllTasksAreFinished() {
Task task1 = TaskTest.createValidTaskWithFullProgress();
Task task2 = TaskTest.createValidTaskWithFullProgress();
TaskGroup taskGroup = new TaskGroup();
taskGroup.addTaskElement(task1);
taskGroup.addTaskElement(task2);
assertTrue(taskGroup.isFinished());
task2.setAdvancePercentage(new BigDecimal("0.0001", new MathContext(4)));
task2.resetStatus();
taskGroup.resetStatus();
assertFalse(taskGroup.isFinished());
}
@Test
public void taskGroupIsInProgressIifAnyTaskisInProgress() {
Task task1 = TaskTest.createValidTaskWithFullProgress();
Task task2 = TaskTest.createValidTaskWithFullProgress();
TaskGroup taskGroup = new TaskGroup();
taskGroup.addTaskElement(task1);
taskGroup.addTaskElement(task2);
assertFalse(taskGroup.isInProgress());
task2.setAdvancePercentage(new BigDecimal("0.0001", new MathContext(4)));
task2.resetStatus();
taskGroup.resetStatus();
assertTrue(taskGroup.isInProgress());
}
public static TaskGroup createValidTaskGroup() {
HoursGroup hoursGroup = new HoursGroup();
hoursGroup.setWorkingHours(3);

View file

@ -29,13 +29,17 @@ import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.resetToNice;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.libreplan.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE;
import static org.libreplan.business.test.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_TEST_FILE;
import static org.libreplan.business.test.planner.entities.DayAssignmentMatchers.haveHours;
import static org.libreplan.business.workingday.EffortDuration.hours;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Arrays;
import java.util.Date;
@ -47,20 +51,32 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.libreplan.business.IDataBootstrap;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.calendars.entities.ResourceCalendar;
import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderLine;
import org.libreplan.business.orders.entities.SchedulingDataForVersion;
import org.libreplan.business.orders.entities.SumChargedEffort;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.planner.entities.AggregateOfDayAssignments;
import org.libreplan.business.planner.entities.DayAssignment.FilterType;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.Dependency.Type;
import org.libreplan.business.planner.entities.SpecificResourceAllocation;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskDeadlineViolationStatusEnum;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.planner.entities.TaskStatusEnum;
import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement;
import org.libreplan.business.resources.entities.Worker;
import org.libreplan.business.scenarios.entities.OrderVersion;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
import org.libreplan.business.workingday.ResourcesPerDay;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
@ -82,11 +98,15 @@ public class TaskTest {
return result;
}
private BaseCalendar taskCalendar;
private Task task;
private HoursGroup hoursGroup;
private BaseCalendar calendar;
private ResourceCalendar workerCalendar;
private Worker worker;
@Resource
private IDataBootstrap defaultAdvanceTypesBootstrapListener;
@ -113,11 +133,13 @@ public class TaskTest {
}
private BaseCalendar stubCalendar() {
calendar = createNiceMock(BaseCalendar.class);
expect(calendar.getCapacityOn(isA(PartialDay.class)))
taskCalendar = createNiceMock(BaseCalendar.class);
expect(taskCalendar.getCapacityOn(isA(PartialDay.class)))
.andReturn(hours(8)).anyTimes();
replay(calendar);
return calendar;
expect(taskCalendar.getAvailability()).andReturn(
AvailabilityTimeLine.allValid()).anyTimes();
replay(taskCalendar);
return taskCalendar;
}
@Test
@ -151,6 +173,12 @@ public class TaskTest {
return Task.createTask(taskSource);
}
public static Task createValidTaskWithFullProgress(){
Task task = createValidTask();
task.setAdvancePercentage(BigDecimal.ONE);
return task;
}
@Test
public void getResourceAllocationsDoesntRetrieveUnsatisfiedAllocations() {
assertThat(task.getSatisfiedResourceAllocations().size(), equalTo(0));
@ -236,8 +264,8 @@ public class TaskTest {
public void ifSomeDayIsNotWorkableIsNotCounted() {
final LocalDate start = new LocalDate(2010, 1, 13);
resetToNice(calendar);
expect(calendar.getCapacityOn(isA(PartialDay.class))).andAnswer(
resetToNice(taskCalendar);
expect(taskCalendar.getCapacityOn(isA(PartialDay.class))).andAnswer(
new IAnswer<EffortDuration>() {
@Override
public EffortDuration answer() throws Throwable {
@ -247,7 +275,7 @@ public class TaskTest {
: hours(8);
}
}).anyTimes();
replay(calendar);
replay(taskCalendar);
task.setIntraDayStartDate(IntraDayDate.create(start,
EffortDuration.hours(3)));
@ -294,4 +322,278 @@ public class TaskTest {
assertTrue(task.getNonLimitingResourceAllocations().size() == 1);
}
@Test
public void theoreticalHoursIsZeroIfNoResourcesAreAllocated() {
assertThat(task.getTheoreticalCompletedTimeUntilDate(new Date()), equalTo(EffortDuration.zero()));
}
@Test
public void theoreticalHoursIsTotalIfDateIsLaterThanEndDate() {
prepareTaskForTheoreticalAdvanceTesting();
EffortDuration totalAllocatedTime = AggregateOfDayAssignments.create(
task.getDayAssignments(FilterType.KEEP_ALL)).getTotalTime();
assertThat(task.getTheoreticalCompletedTimeUntilDate(task.getEndDate()), equalTo(totalAllocatedTime));
}
@Test
public void theoreticalHoursIsZeroIfDateIsEarlierThanStartDate() {
prepareTaskForTheoreticalAdvanceTesting();
assertThat(task.getTheoreticalCompletedTimeUntilDate(task.getStartDate()), equalTo(EffortDuration.zero()));
}
@Test
public void theoreticalHoursWithADateWithinStartAndEndDateHead() {
prepareTaskForTheoreticalAdvanceTesting();
LocalDate limit = task.getStartAsLocalDate().plusDays(1);
EffortDuration expected = EffortDuration.hours(8);
assertThat(task.getTheoreticalCompletedTimeUntilDate(limit.toDateTimeAtStartOfDay().toDate()),
equalTo(expected));
}
@Test
public void theoreticalHoursWithADateWithinStartAndEndDateTail() {
prepareTaskForTheoreticalAdvanceTesting();
LocalDate limit = task.getEndAsLocalDate().minusDays(1);
EffortDuration expected = EffortDuration.hours(32);
assertThat(task.getTheoreticalCompletedTimeUntilDate(limit.toDateTimeAtStartOfDay().toDate()),
equalTo(expected));
}
@Test
public void theoreticalAdvancePercentageIsZeroIfNoResourcesAreAllocated() {
assertThat(task.getTheoreticalAdvancePercentageUntilDate(new Date()), equalTo(new BigDecimal(0)));
}
@Test
public void theoreticalPercentageIsOneIfDateIsLaterThanEndDate() {
prepareTaskForTheoreticalAdvanceTesting();
assertThat(task.getTheoreticalAdvancePercentageUntilDate(task.getEndDate()),
equalTo(new BigDecimal("1.00000000")));
}
@Test
public void theoreticalPercentageWithADateWithinStartAndEndDateHead() {
prepareTaskForTheoreticalAdvanceTesting();
LocalDate limit = task.getStartAsLocalDate().plusDays(1);
assertThat(task.getTheoreticalAdvancePercentageUntilDate(limit.toDateTimeAtStartOfDay().toDate()),
equalTo(new BigDecimal("0.20000000")));
}
@Test
public void taskIsFinishedIfAdvancePertentageIsOne() {
task.setAdvancePercentage(BigDecimal.ONE);
assertTrue(task.isFinished());
assertFalse(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.FINISHED);
}
@Test
public void taskIsProgressIfAdvancePercentageIsLessThanOne() {
task.setAdvancePercentage(new BigDecimal("0.9999", new MathContext(4)));
assertFalse(task.isFinished());
assertTrue(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.IN_PROGRESS);
}
@Test
public void taskIsProgressIfAdvancePercentageIsGreaterThanZero() {
task.setAdvancePercentage(new BigDecimal("0.0001", new MathContext(4)));
assertFalse(task.isFinished());
assertTrue(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.IN_PROGRESS);
}
@Test
public void taskIsNotInProgressIfAdvancePercentageIsZeroAndNoWorkReportsAttached() {
task.setAdvancePercentage(BigDecimal.ZERO);
SumChargedEffort sumChargedEffort = task.getOrderElement().getSumChargedEffort();
assertTrue(sumChargedEffort == null || sumChargedEffort.isZero());
assertFalse(task.isFinished());
assertFalse(task.isInProgress());
}
@Test
public void taskIsInProgressIfAdvancePercentageIsZeroButWorkReportsAttached() {
SumChargedEffort sumChargedEffort = SumChargedEffort.create(task
.getOrderElement());
sumChargedEffort.addDirectChargedEffort(EffortDuration.hours(1));
task.getOrderElement().setSumChargedEffort(sumChargedEffort);
assertFalse(task.isFinished());
assertTrue(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.IN_PROGRESS);
}
@Test
public void taskIsReadyToStartIfAllEndStartDepsAreFinished() {
Dependency dependency = mockDependency(Type.END_START);
dependency.getOrigin().setAdvancePercentage(BigDecimal.ONE);
assertFalse(task.isFinished());
assertFalse(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.READY_TO_START);
}
@Test
public void taskIsReadyToStartIfAllStartStartDepsAreInProgressOrFinished() {
Dependency dependency1 = mockDependency(Type.START_START);
dependency1.getOrigin().setAdvancePercentage(BigDecimal.ONE);
Dependency dependency2 = mockDependency(Type.START_START);
dependency2.getOrigin().setAdvancePercentage(new BigDecimal("0.0001", new MathContext(4)));
assertFalse(task.isFinished());
assertFalse(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.READY_TO_START);
}
@Test
public void taskIsBlockedIfHasAnUnfinishedEndStartDependency() {
Dependency dependency = mockDependency(Type.END_START);
dependency.getOrigin().setAdvancePercentage(new BigDecimal("0.0001", new MathContext(4)));
assertFalse(task.isFinished());
assertFalse(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.BLOCKED);
}
@Test
public void taskIsBlockedIfHasANotStartedStartStartDependency() {
Dependency dependency = mockDependency(Type.START_START);
dependency.getOrigin().setAdvancePercentage(BigDecimal.ZERO);
assertFalse(task.isFinished());
assertFalse(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.BLOCKED);
}
@Test
public void taskStatusCalculationTakesIntoAccountDifferentDepType() {
Dependency dependency1 = mockDependency(Type.END_START);
dependency1.getOrigin().setAdvancePercentage(BigDecimal.ONE);
Dependency dependency2 = mockDependency(Type.START_START);
dependency2.getOrigin().setAdvancePercentage(new BigDecimal("0.0001", new MathContext(4)));
assertFalse(task.isFinished());
assertFalse(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.READY_TO_START);
dependency2.getOrigin().setAdvancePercentage(BigDecimal.ZERO);
assertTrue(task.getTaskStatus() == TaskStatusEnum.BLOCKED);
}
@Test
public void taskIsBlockedIfHasAnUnfinishedEndStartDependencyUsingGroup() {
Task task1 = createValidTaskWithFullProgress();
Task task2 = createValidTask();
task2.setAdvancePercentage(new BigDecimal("0.0001", new MathContext(4)));
TaskGroup taskGroup = new TaskGroup();
taskGroup.addTaskElement(task1);
taskGroup.addTaskElement(task2);
mockDependency(taskGroup, this.task, Type.END_START);
assertFalse(task.isFinished());
assertFalse(task.isInProgress());
assertTrue(task.getTaskStatus() == TaskStatusEnum.BLOCKED);
}
@Test
public void taskDependenciesDontMatterIfProgressIsNotZero() {
Task task1 = createValidTaskWithFullProgress();
Task task2 = createValidTask();
task2.setAdvancePercentage(new BigDecimal("0.0001", new MathContext(4)));
TaskGroup taskGroup = new TaskGroup();
taskGroup.addTaskElement(task1);
taskGroup.addTaskElement(task2);
mockDependency(taskGroup, this.task, Type.END_START);
task.setAdvancePercentage(new BigDecimal("0.0001", new MathContext(4)));
assertFalse(task.isFinished());
assertTrue(task.getTaskStatus() == TaskStatusEnum.IN_PROGRESS);
task.setAdvancePercentage(BigDecimal.ONE);
assertTrue(task.getTaskStatus() == TaskStatusEnum.FINISHED);
}
@Test
public void taskStatusNotAffectedByEndEndDeps() {
Dependency dependency = mockDependency(Type.END_END);
dependency.getOrigin().setAdvancePercentage(BigDecimal.ZERO);
assertTrue(task.getTaskStatus() == TaskStatusEnum.READY_TO_START);
}
@Test
public void taskWithNoDeadlineHasCorrectDeadlineViolationStatus() {
task.setDeadline(null);
assertTrue(task.getDeadlineViolationStatus() ==
TaskDeadlineViolationStatusEnum.NO_DEADLINE);
}
@Test
public void taskWithViolatedDeadlineHasCorrectDeadlineViolationStatus() {
task.setDeadline(new LocalDate());
LocalDate tomorrow = new LocalDate().plusDays(1);
task.setEndDate(tomorrow.toDateTimeAtStartOfDay().toDate());
assertTrue(task.getDeadlineViolationStatus() ==
TaskDeadlineViolationStatusEnum.DEADLINE_VIOLATED);
}
@Test
public void taskWithUnviolatedDeadlineHasCorrectDeadlineViolationStatusJustInTime() {
LocalDate now = new LocalDate();
task.setDeadline(now);
task.setEndDate(now.toDateTimeAtStartOfDay().toDate());
assertTrue(task.getDeadlineViolationStatus() ==
TaskDeadlineViolationStatusEnum.ON_SCHEDULE);
}
@Test
public void taskWithUnviolatedDeadlineHasCorrectDeadlineViolationStatusMargin() {
LocalDate now = new LocalDate();
task.setDeadline(now);
task.setEndDate(now.minusDays(1).toDateTimeAtStartOfDay().toDate());
assertTrue(task.getDeadlineViolationStatus() ==
TaskDeadlineViolationStatusEnum.ON_SCHEDULE);
}
private void prepareTaskForTheoreticalAdvanceTesting() {
task.getHoursGroup().setWorkingHours(40);
assertThat(task.getTotalHours(), equalTo(40));
task.setEndDate(task.getStartAsLocalDate().plusDays(5).toDateTimeAtStartOfDay().toDate());
SpecificResourceAllocation resourceAllocation = SpecificResourceAllocation.create(task);
givenWorker(8);
resourceAllocation.setResource(this.worker);
assertTrue(resourceAllocation.getResource() != null);
resourceAllocation.allocate(ResourcesPerDay.amount(1));
assertThat(resourceAllocation.getAssignments().size(), equalTo(5));
assertThat(resourceAllocation.getAssignments(), haveHours(8, 8, 8, 8, 8));
assertThat(task.getAssignedHours(), equalTo(0));
task.addResourceAllocation(resourceAllocation);
assertTrue(task.getNonLimitingResourceAllocations().size() == 1);
assertThat(task.getAssignedHours(), equalTo(40));
assertTrue(task.getDayAssignments(FilterType.KEEP_ALL).size() == 5);
}
private void givenWorker(int hoursPerDay) {
this.worker = createNiceMock(Worker.class);
givenResourceCalendarAlwaysReturning(hoursPerDay);
expect(this.worker.getCalendar()).andReturn(this.workerCalendar).anyTimes();
replay(this.worker);
}
private void givenResourceCalendarAlwaysReturning(final int hours) {
this.workerCalendar = SpecificResourceAllocationTest.
createResourceCalendarAlwaysReturning(hours);
}
private Dependency mockDependency(Type type){
return mockDependency(createValidTask(), this.task, type);
}
private Dependency mockDependency(TaskElement origin, TaskElement destination, Type type) {
Dependency dependency = createNiceMock(Dependency.class);
expect(dependency.getOrigin()).andReturn(origin).anyTimes();
expect(dependency.getDestination()).andReturn(destination).anyTimes();
expect(dependency.getType()).andReturn(type).anyTimes();
replay(dependency);
origin.add(dependency);
destination.add(dependency);
return dependency;
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -51,7 +51,7 @@ import org.libreplan.business.workingday.ResourcesPerDay;
/**
* @author Óscar González Fernández
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class IntraDayDateTest {
@ -367,4 +367,79 @@ public class IntraDayDateTest {
.getDate(), equalTo(today));
}
@Test
public void calculateEffortBetweenTwoDates() {
IntraDayDate start = IntraDayDate.create(today, zero());
IntraDayDate end = IntraDayDate.create(today.plusDays(3), hours(0));
assertThat(start.effortUntil(end), equalTo(hours(8 * 3)));
end = IntraDayDate.create(today.plusDays(3), hours(8));
assertThat(start.effortUntil(end), equalTo(hours(8 * 4)));
start = IntraDayDate.create(today, hours(8));
end = IntraDayDate.create(today.plusDays(3), hours(0));
assertThat(start.effortUntil(end), equalTo(hours(8 * 2)));
start = IntraDayDate.create(today, hours(3));
assertThat(start.effortUntil(end), equalTo(hours(8 * 2 + 5)));
end = IntraDayDate.create(today.plusDays(3), hours(6));
assertThat(start.effortUntil(end), equalTo(hours(8 * 2 + 5 + 6)));
end = IntraDayDate.create(today.plusDays(3), minutes(30));
assertThat(start.effortUntil(end),
equalTo(hours(8 * 2 + 5).plus(minutes(30))));
start = IntraDayDate.create(today, hours(5).plus(minutes(40)));
assertThat(start.effortUntil(end),
equalTo(hours(8 * 2 + 2).plus(minutes(50))));
}
@Test
public void calculateAddEffort() {
IntraDayDate start = IntraDayDate.create(today, zero());
assertThat(start.addEffort(hours(24)),
equalTo(IntraDayDate.create(today.plusDays(3), hours(0))));
assertThat(start.addEffort(hours(20)),
equalTo(IntraDayDate.create(today.plusDays(2), hours(4))));
assertThat(
start.addEffort(hours(20).plus(minutes(20))),
equalTo(IntraDayDate.create(today.plusDays(2),
hours(4).plus(minutes(20)))));
start = IntraDayDate.create(today, hours(3));
assertThat(start.addEffort(hours(24)),
equalTo(IntraDayDate.create(today.plusDays(3), hours(3))));
assertThat(start.addEffort(hours(20)),
equalTo(IntraDayDate.create(today.plusDays(2), hours(7))));
assertThat(start.addEffort(hours(20).plus(minutes(20))),
equalTo(IntraDayDate.create(today.plusDays(2),
hours(7).plus(minutes(20)))));
start = IntraDayDate.create(today, hours(3).plus(minutes(40)));
assertThat(
start.addEffort(hours(24)),
equalTo(IntraDayDate.create(today.plusDays(3),
hours(3).plus(minutes(40)))));
assertThat(
start.addEffort(hours(20)),
equalTo(IntraDayDate.create(today.plusDays(2),
hours(7).plus(minutes(40)))));
assertThat(start.addEffort(hours(20).plus(minutes(20))),
equalTo(IntraDayDate.create(today.plusDays(3), hours(0))));
assertThat(
start.addEffort(hours(20).plus(minutes(10))),
equalTo(IntraDayDate.create(today.plusDays(2),
hours(7).plus(minutes(50)))));
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -299,8 +299,8 @@ public abstract class BaseCalendarEditionController extends
if (baseCalendarModel.isDerived()) {
String currentStartDate = this.getCurrentStartDateLabel();
String currentExpiringDate = this.getCurrentExpiringDateLabel();
return _("Derived of Calendar " + getNameParentCalendar()
+ currentStartDate + currentExpiringDate);
return _("Derived of calendar {0}", getNameParentCalendar())
+ currentStartDate + currentExpiringDate;
}
return _("Root calendar");
}
@ -309,7 +309,7 @@ public abstract class BaseCalendarEditionController extends
Date date = baseCalendarModel.getCurrentExpiringDate();
String label = "";
if (date != null) {
label = " to " + new SimpleDateFormat("dd/MM/yyyy").format(date);
label = " " + _("to {0}", new SimpleDateFormat("dd/MM/yyyy").format(date));
}
return label;
}
@ -318,7 +318,7 @@ public abstract class BaseCalendarEditionController extends
Date date = baseCalendarModel.getCurrentStartDate();
String label = "";
if (date != null) {
label = " from " + new SimpleDateFormat("dd/MM/yyyy").format(date);
label = " " + _("from {0}", new SimpleDateFormat("dd/MM/yyyy").format(date));
}
return label;
}

View file

@ -0,0 +1,239 @@
/*
* This file is part of LibrePlan
*
* 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.dashboard;
import static org.libreplan.web.I18nHelper._;
import java.awt.Color;
import java.awt.Font;
import java.math.BigDecimal;
import java.util.Iterator;
import java.util.List;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.web.common.Util;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.CategoryModel;
import org.zkoss.zul.Chart;
import org.zkoss.zul.Div;
import org.zkoss.zul.PieModel;
import org.zkoss.zul.SimpleCategoryModel;
import org.zkoss.zul.SimplePieModel;
import org.zkoss.zul.Window;
/**
* Controller for dashboardfororder view
* @author Nacho Barrientos <nacho@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class DashboardController extends GenericForwardComposer {
private IDashboardModel dashboardModel;
private Window dashboardWindow;
private Chart progressKPIglobalProgressChart;
private Chart progressKPItaskStatusChart;
private Chart progressKPItaskDeadlineViolationStatusChart;
private Chart timeKPImarginWithDeadlineChart;
private Chart timeKPIEstimationAccuracyChart;
private Chart timeKPILagInTaskCompletionChart;
private Div projectDashboardChartsDiv;
private Div projectDashboardNoTasksWarningDiv;
public DashboardController() {
}
@Override
public void doAfterCompose(org.zkoss.zk.ui.Component comp) throws Exception {
super.doAfterCompose(comp);
this.dashboardWindow = (Window)comp;
Util.createBindingsFor(this.dashboardWindow);
}
public void setCurrentOrder(Order order) {
dashboardModel.setCurrentOrder(order);
if(dashboardModel.tasksAvailable()) {
this.reloadCharts();
} else {
this.hideChartsAndShowWarningMessage();
}
if (this.dashboardWindow != null) {
Util.reloadBindings(this.dashboardWindow);
}
}
private void reloadCharts() {
generateProgressKPIglobalProgressChart();
generateProgressKPItaskStatusChart();
generateProgressKPItaskDeadlineViolationStatusChart();
generateTimeKPImarginWithDeadlineChart();
generateTimeKPIEstimationAccuracyChart();
generateTimeKPILagInTaskCompletionChart();
}
private void hideChartsAndShowWarningMessage() {
projectDashboardChartsDiv.setVisible(false);
projectDashboardNoTasksWarningDiv.setVisible(true);
}
private void generateTimeKPILagInTaskCompletionChart() {
CategoryModel categoryModel;
categoryModel = refreshTimeKPILagInTaskCompletionCategoryModel();
Font labelFont = new Font("serif", Font.PLAIN, 10);
timeKPILagInTaskCompletionChart.setXAxisTickFont(labelFont);
Color[] seriesColorMappings = {Color.BLUE};
timeKPILagInTaskCompletionChart.setAttribute("series-color-mappings",
seriesColorMappings);
timeKPILagInTaskCompletionChart.setModel(categoryModel);
}
private void generateTimeKPIEstimationAccuracyChart() {
CategoryModel categoryModel;
categoryModel = refreshTimeKPIEstimationAccuracyCategoryModel();
Font labelFont = new Font("serif", Font.PLAIN, 10);
timeKPIEstimationAccuracyChart.setXAxisTickFont(labelFont);
Color[] seriesColorMappings = {Color.BLUE};
timeKPIEstimationAccuracyChart.setAttribute("series-color-mappings",
seriesColorMappings);
timeKPIEstimationAccuracyChart.setModel(categoryModel);
}
private void generateTimeKPImarginWithDeadlineChart() {
CategoryModel categoryModel;
categoryModel = refreshTimeKPImarginWithDeadlineCategoryModel();
if (categoryModel == null) { // Project has no deadline set.
timeKPImarginWithDeadlineChart.setVisible(false);
return;
}
timeKPImarginWithDeadlineChart.setAttribute("range-axis-lower-bound",
new Double(-3.0));
timeKPImarginWithDeadlineChart.setAttribute("range-axis-upper-bound",
new Double(3.0));
Color[] seriesColorMappings = new Color[1];
if(dashboardModel.getMarginWithDeadLine().compareTo(BigDecimal.ZERO) >= 0) {
seriesColorMappings[0] = Color.GREEN;
} else {
seriesColorMappings[0] = Color.RED;
}
timeKPImarginWithDeadlineChart.setAttribute("series-color-mappings",
seriesColorMappings);
timeKPImarginWithDeadlineChart.setModel(categoryModel);
}
private void generateProgressKPItaskStatusChart() {
PieModel model = refreshProgressKPItaskStatusPieModel();
progressKPItaskStatusChart.setModel(model);
}
private void generateProgressKPItaskDeadlineViolationStatusChart() {
PieModel model = refreshProgressKPItaskDeadlieViolationStatusPieModel();
progressKPItaskDeadlineViolationStatusChart.setModel(model);
}
private void generateProgressKPIglobalProgressChart() {
CategoryModel categoryModel;
categoryModel = refreshProgressKPIglobalProgressCategoryModel();
progressKPIglobalProgressChart.setAttribute("range-axis-lower-bound",
new Double(0.0));
progressKPIglobalProgressChart.setAttribute("range-axis-upper-bound",
new Double(100.0));
progressKPIglobalProgressChart.setModel(categoryModel);
}
private PieModel refreshProgressKPItaskStatusPieModel() {
PieModel model = new SimplePieModel();
model.setValue(_("Finished"), dashboardModel.getPercentageOfFinishedTasks());
model.setValue(_("In progress"), dashboardModel.getPercentageOfInProgressTasks());
model.setValue(_("Ready to start"), dashboardModel.getPercentageOfReadyToStartTasks());
model.setValue(_("Blocked"), dashboardModel.getPercentageOfBlockedTasks());
return model;
}
private PieModel refreshProgressKPItaskDeadlieViolationStatusPieModel() {
PieModel model = new SimplePieModel();
model.setValue(_("On schedule"), dashboardModel.getPercentageOfOnScheduleTasks());
model.setValue(_("Violated deadline"), dashboardModel.getPercentageOfTasksWithViolatedDeadline());
model.setValue(_("No deadline"), dashboardModel.getPercentageOfTasksWithNoDeadline());
return model;
}
private CategoryModel refreshProgressKPIglobalProgressCategoryModel() {
CategoryModel result = new SimpleCategoryModel();
result.setValue(_("Current"), _("All tasks (hours)"),
dashboardModel.getAdvancePercentageByHours());
result.setValue(_("Expected"), _("All tasks (hours)"),
dashboardModel.getTheoreticalAdvancePercentageByHoursUntilNow());
result.setValue(_("Current"), _("Critical path (hours)"),
dashboardModel.getCriticalPathProgressByNumHours());
result.setValue(_("Expected"), _("Critical path (hours)"), dashboardModel
.getTheoreticalProgressByNumHoursForCriticalPathUntilNow());
result.setValue(_("Current"), _("Critical path (duration)"),
dashboardModel.getCriticalPathProgressByDuration());
result.setValue(_("Expected"), _("Critical path (duration)"),
dashboardModel.getTheoreticalProgressByDurationForCriticalPathUntilNow());
return result;
}
private CategoryModel refreshTimeKPImarginWithDeadlineCategoryModel() {
CategoryModel result = null;
BigDecimal marginWithDeadLine = dashboardModel.getMarginWithDeadLine();
if (marginWithDeadLine != null) {
result = new SimpleCategoryModel();
result.setValue(_("None"), _("Deviation"), marginWithDeadLine);
}
return result;
}
private CategoryModel refreshTimeKPIEstimationAccuracyCategoryModel() {
CategoryModel result = new SimpleCategoryModel();
List<Double> values = dashboardModel.getFinishedTasksEstimationAccuracyHistogram();
Iterator<Double> it = values.iterator();
for(int ii= DashboardModel.EA_STRETCHES_MIN_VALUE;
ii < DashboardModel.EA_STRETCHES_MAX_VALUE;
ii += DashboardModel.EA_STRETCHES_PERCENTAGE_STEP) {
result.setValue(_("None"), _(String.valueOf(ii)), it.next());
}
result.setValue(_("None"),
_(">"+DashboardModel.EA_STRETCHES_MAX_VALUE),
it.next());
return result;
}
private CategoryModel refreshTimeKPILagInTaskCompletionCategoryModel() {
CategoryModel result = new SimpleCategoryModel();
List<Double> values = dashboardModel.getLagInTaskCompletionHistogram();
Iterator<Double> it = values.iterator();
for(double ii= DashboardModel.LTC_STRETCHES_MIN_VALUE;
ii < DashboardModel.LTC_STRETCHES_MAX_VALUE;
ii += DashboardModel.LTC_STRETCHES_STEP) {
result.setValue(_("None"), _(String.valueOf(ii)), it.next());
}
result.setValue(_("None"),
_(">"+DashboardModel.LTC_STRETCHES_MAX_VALUE),
it.next());
return result;
}
}

View file

@ -0,0 +1,363 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2010-2012 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.dashboard;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.planner.entities.TaskDeadlineViolationStatusEnum;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.planner.entities.TaskStatusEnum;
import org.libreplan.business.planner.entities.visitors.AccumulateTasksDeadlineStatusVisitor;
import org.libreplan.business.planner.entities.visitors.AccumulateTasksStatusVisitor;
import org.libreplan.business.planner.entities.visitors.CalculateFinishedTasksEstimationDeviationVisitor;
import org.libreplan.business.planner.entities.visitors.CalculateFinishedTasksLagInCompletionVisitor;
import org.libreplan.business.planner.entities.visitors.ResetTasksStatusVisitor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* Model for UI operations related to Order Dashboard View
*
* @author Nacho Barrientos <nacho@igalia.com>
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class DashboardModel implements IDashboardModel {
/* Parameters */
public final static int EA_STRETCHES_PERCENTAGE_STEP = 10;
public final static int EA_STRETCHES_MIN_VALUE = -100;
public final static int EA_STRETCHES_MAX_VALUE = 150;
public final static int LTC_NUMBER_OF_INTERVALS = 10;
/* To be calculated */
public static double LTC_STRETCHES_STEP = 0;
public static double LTC_STRETCHES_MIN_VALUE = 0;
public static double LTC_STRETCHES_MAX_VALUE = 0;
private Order currentOrder;
private Integer taskCount = null;
private Map<TaskStatusEnum, BigDecimal> taskStatusStats;
private Map<TaskDeadlineViolationStatusEnum, BigDecimal> taskDeadlineViolationStatusStats;
private List<Double> taskEstimationAccuracyHistogram;
private BigDecimal marginWithDeadLine;
private List<Double> lagInTaskCompletionHistogram;
public DashboardModel() {
taskStatusStats = new EnumMap<TaskStatusEnum, BigDecimal>(
TaskStatusEnum.class);
taskDeadlineViolationStatusStats = new EnumMap<TaskDeadlineViolationStatusEnum, BigDecimal>(
TaskDeadlineViolationStatusEnum.class);
}
public void setCurrentOrder(Order order) {
this.currentOrder = order;
this.taskCount = null;
if(tasksAvailable()) {
this.calculateTaskStatusStatistics();
this.calculateTaskViolationStatusStatistics();
this.calculateMarginWithDeadLine();
this.calculateFinishedTasksEstimationAccuracyHistogram();
this.calculateLagInTaskCompletionHistogram();
}
}
/* Progress KPI: "Number of tasks by status" */
public BigDecimal getPercentageOfFinishedTasks() {
return taskStatusStats.get(TaskStatusEnum.FINISHED);
}
public BigDecimal getPercentageOfInProgressTasks() {
return taskStatusStats.get(TaskStatusEnum.IN_PROGRESS);
}
public BigDecimal getPercentageOfReadyToStartTasks() {
return taskStatusStats.get(TaskStatusEnum.READY_TO_START);
}
public BigDecimal getPercentageOfBlockedTasks() {
return taskStatusStats.get(TaskStatusEnum.BLOCKED);
}
/* Progress KPI: "Deadline violation" */
public BigDecimal getPercentageOfOnScheduleTasks() {
return taskDeadlineViolationStatusStats.get(TaskDeadlineViolationStatusEnum.ON_SCHEDULE);
}
public BigDecimal getPercentageOfTasksWithViolatedDeadline() {
return taskDeadlineViolationStatusStats.get(TaskDeadlineViolationStatusEnum.DEADLINE_VIOLATED);
}
public BigDecimal getPercentageOfTasksWithNoDeadline() {
return taskDeadlineViolationStatusStats.get(TaskDeadlineViolationStatusEnum.NO_DEADLINE);
}
/* Progress KPI: "Global Progress of the Project" */
public BigDecimal getAdvancePercentageByHours(){
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
BigDecimal ratio = rootAsTaskGroup.getProgressAllByNumHours();
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
public BigDecimal getTheoreticalAdvancePercentageByHoursUntilNow(){
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
BigDecimal ratio = rootAsTaskGroup.getTheoreticalProgressByNumHoursForAllTasksUntilNow();
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
public BigDecimal getCriticalPathProgressByNumHours() {
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
BigDecimal ratio = rootAsTaskGroup.getCriticalPathProgressByNumHours();
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
public BigDecimal getTheoreticalProgressByNumHoursForCriticalPathUntilNow() {
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
BigDecimal ratio = rootAsTaskGroup.getTheoreticalProgressByNumHoursForCriticalPathUntilNow();
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
public BigDecimal getCriticalPathProgressByDuration() {
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
BigDecimal ratio = rootAsTaskGroup.getCriticalPathProgressByDuration();
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
public BigDecimal getTheoreticalProgressByDurationForCriticalPathUntilNow() {
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
BigDecimal ratio = rootAsTaskGroup.getTheoreticalProgressByDurationForCriticalPathUntilNow();
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
/* Time KPI: Margin with deadline */
public BigDecimal getMarginWithDeadLine() {
return this.marginWithDeadLine;
}
private void calculateMarginWithDeadLine() {
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
if (this.currentOrder.getDeadline() == null) {
this.marginWithDeadLine = null;
return;
}
TaskElement rootTask = getRootTask();
Days orderDuration = Days.daysBetween(rootTask.getStartAsLocalDate(),
rootTask.getEndAsLocalDate());
LocalDate deadLineAsLocalDate = LocalDate.fromDateFields(currentOrder
.getDeadline());
Days deadlineOffset = Days.daysBetween(rootTask.getEndAsLocalDate(),
deadLineAsLocalDate);
BigDecimal outcome = new BigDecimal(deadlineOffset.getDays(),
MathContext.DECIMAL32);
this.marginWithDeadLine = outcome.divide(
new BigDecimal(orderDuration.getDays()), 8,
BigDecimal.ROUND_HALF_EVEN);
}
/* Time KPI: Estimation accuracy */
public List<Double> getFinishedTasksEstimationAccuracyHistogram() {
return this.taskEstimationAccuracyHistogram;
}
private void calculateFinishedTasksEstimationAccuracyHistogram() {
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
CalculateFinishedTasksEstimationDeviationVisitor visitor =
new CalculateFinishedTasksEstimationDeviationVisitor();
TaskElement rootTask = getRootTask();
rootTask.acceptVisitor(visitor);
List<Double> deviations = visitor.getDeviations();
// [-100, -90), [-90, -80), ..., [190, 200), [200, inf)
this.taskEstimationAccuracyHistogram = createHistogram(
EA_STRETCHES_MIN_VALUE,
EA_STRETCHES_MAX_VALUE,
EA_STRETCHES_PERCENTAGE_STEP,
deviations);
}
/* Time KPI: Lead/Lag in task completion */
public List<Double> getLagInTaskCompletionHistogram() {
return this.lagInTaskCompletionHistogram;
}
private void calculateLagInTaskCompletionHistogram() {
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
CalculateFinishedTasksLagInCompletionVisitor visitor =
new CalculateFinishedTasksLagInCompletionVisitor();
TaskElement rootTask = getRootTask();
rootTask.acceptVisitor(visitor);
List<Double> deviations = visitor.getDeviations();
if (deviations.isEmpty()) {
LTC_STRETCHES_MIN_VALUE = 0;
LTC_STRETCHES_MAX_VALUE = 0;
} else {
LTC_STRETCHES_MIN_VALUE = Collections.min(deviations);
LTC_STRETCHES_MAX_VALUE = Collections.max(deviations);
}
LTC_STRETCHES_STEP = (LTC_STRETCHES_MAX_VALUE - LTC_STRETCHES_MIN_VALUE)
/LTC_NUMBER_OF_INTERVALS;
this.lagInTaskCompletionHistogram = createHistogram(
LTC_STRETCHES_MIN_VALUE,
LTC_STRETCHES_MAX_VALUE,
LTC_STRETCHES_STEP,
deviations);
}
private List<Double> createHistogram(double lowBound, double highBound,
double intervalStep, List<Double> values) {
double variableRange = highBound - lowBound;
/* TODO: What if highBound == lowBound? */
int numberOfClasses = (int)(variableRange/intervalStep);
int[] classes = new int[numberOfClasses+1];
for(Double value: values) {
int index;
if (value >= highBound) {
index = numberOfClasses;
} else {
index = (int)(numberOfClasses *
(((value.doubleValue() - lowBound))/variableRange));
}
classes[index]++;
}
List<Double> histogram = new ArrayList<Double>();
int numberOfConsideredTasks = values.size();
for (int numberOfElementsInClass: classes) {
Double relativeCount = new Double(0.0);
if (numberOfConsideredTasks > 0) {
relativeCount = new Double(1.0*numberOfElementsInClass/
numberOfConsideredTasks);
}
histogram.add(relativeCount);
}
return histogram;
}
private void calculateTaskStatusStatistics() {
AccumulateTasksStatusVisitor visitor = new AccumulateTasksStatusVisitor();
TaskElement rootTask = getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
resetTasksStatusInGraph();
rootTask.acceptVisitor(visitor);
Map<TaskStatusEnum, Integer> count = visitor.getTaskStatusData();
mapAbsoluteValuesToPercentages(count, taskStatusStats);
}
private void calculateTaskViolationStatusStatistics() {
AccumulateTasksDeadlineStatusVisitor visitor = new AccumulateTasksDeadlineStatusVisitor();
TaskElement rootTask = getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
rootTask.acceptVisitor(visitor);
Map<TaskDeadlineViolationStatusEnum, Integer> count = visitor.getTaskDeadlineViolationStatusData();
mapAbsoluteValuesToPercentages(count, taskDeadlineViolationStatusStats);
}
private <T> void mapAbsoluteValuesToPercentages(Map<T, Integer> source,
Map<T, BigDecimal> dest) {
int totalTasks = countTasksInAResultMap(source);
for (Map.Entry<T, Integer> entry : source.entrySet()) {
BigDecimal percentage;
if (totalTasks == 0) {
percentage = BigDecimal.ZERO;
} else {
percentage = new BigDecimal(
100 * (entry.getValue() / (1.0 * totalTasks)),
MathContext.DECIMAL32);
}
dest.put(entry.getKey(), percentage);
}
}
private TaskElement getRootTask() {
return currentOrder.getAssociatedTaskElement();
}
private void resetTasksStatusInGraph() {
ResetTasksStatusVisitor visitor = new ResetTasksStatusVisitor();
getRootTask().acceptVisitor(visitor);
}
private int countTasksInAResultMap(Map<? extends Object, Integer> map) {
/* It's only needed to count the number of tasks once
* each time setOrder is called.
*/
if (this.taskCount != null) {
return this.taskCount.intValue();
}
int sum = 0;
for (Object count : map.values()) {
sum += (Integer) count;
}
this.taskCount = new Integer(sum);
return sum;
}
public boolean tasksAvailable() {
return getRootTask() != null;
}
}

View file

@ -0,0 +1,70 @@
/*
* This file is part of LibrePlan
*
* 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.dashboard;
import java.math.BigDecimal;
import java.util.List;
import org.libreplan.business.orders.entities.Order;
interface IDashboardModel {
void setCurrentOrder(Order order);
boolean tasksAvailable();
/* Progress KPI: "Number of tasks by status" */
BigDecimal getPercentageOfFinishedTasks();
BigDecimal getPercentageOfInProgressTasks();
BigDecimal getPercentageOfReadyToStartTasks();
BigDecimal getPercentageOfBlockedTasks();
/* Progress KPI: "Deadline violation" */
BigDecimal getPercentageOfOnScheduleTasks();
BigDecimal getPercentageOfTasksWithViolatedDeadline();
BigDecimal getPercentageOfTasksWithNoDeadline();
/* Progress KPI: "Global Progress of the Project" */
BigDecimal getAdvancePercentageByHours();
BigDecimal getTheoreticalAdvancePercentageByHoursUntilNow();
BigDecimal getCriticalPathProgressByNumHours();
BigDecimal getTheoreticalProgressByNumHoursForCriticalPathUntilNow();
BigDecimal getCriticalPathProgressByDuration();
BigDecimal getTheoreticalProgressByDurationForCriticalPathUntilNow();
/* Time KPI: "Margin with deadline" */
BigDecimal getMarginWithDeadLine();
/* Time KPI: "Estimation accuracy" */
List<Double> getFinishedTasksEstimationAccuracyHistogram();
/* Time KPI: "Lead/Lag in task completion" */
List<Double> getLagInTaskCompletionHistogram();
}

View file

@ -1,7 +1,7 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2010-2011 Igalia, S.L.
* Copyright (C) 2010-2012 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
@ -244,12 +244,12 @@ public class CalendarExceptionTypeCRUDController extends
@Override
protected String getEntityType() {
return "Exception Day Type";
return _("Exception Day Type");
}
@Override
protected String getPluralEntityType() {
return "Exception Day Types";
return _("Exception Day Types");
}
@Override

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -21,6 +21,7 @@
package org.libreplan.web.orders;
import java.math.BigDecimal;
import java.util.List;
import org.libreplan.business.orders.entities.OrderElement;
@ -33,7 +34,9 @@ import org.zkoss.zul.Vbox;
/**
* Controller for show the asigned hours of the selected order element<br />
*
* @author Susana Montes Pedreria <smontes@wirelessgalicia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class AssignedHoursToOrderElementController extends
GenericForwardComposer {
@ -42,6 +45,14 @@ public class AssignedHoursToOrderElementController extends
private Vbox orderElementHours;
private Progressmeter hoursProgressBar;
private Progressmeter exceedHoursProgressBar;
private Progressmeter moneyCostProgressBar;
private Progressmeter exceedMoneyCostProgressBar;
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
@ -76,6 +87,18 @@ public class AssignedHoursToOrderElementController extends
return assignedHoursToOrderElementModel.getProgressWork();
}
public BigDecimal getBudget() {
return assignedHoursToOrderElementModel.getBudget();
}
public BigDecimal getMoneyCost() {
return assignedHoursToOrderElementModel.getMoneyCost();
}
public BigDecimal getMoneyCostPercentage() {
return assignedHoursToOrderElementModel.getMoneyCostPercentage();
}
private IOrderElementModel orderElementModel;
public void openWindow(IOrderElementModel orderElementModel) {
@ -87,7 +110,12 @@ public class AssignedHoursToOrderElementController extends
Util.reloadBindings(orderElementHours);
}
paintProgressBars();
}
public void paintProgressBars() {
viewPercentage();
showMoneyCostPercentageBars();
}
public void setOrderElementModel(IOrderElementModel orderElementModel) {
@ -98,10 +126,6 @@ public class AssignedHoursToOrderElementController extends
return orderElementModel.getOrderElement();
}
private Progressmeter hoursProgressBar;
private Progressmeter exceedHoursProgressBar;
/**
* This method shows the percentage of the imputed hours with respect to the
* estimated hours.If the hours imputed is greater that the hours estimated
@ -110,17 +134,29 @@ public class AssignedHoursToOrderElementController extends
private void viewPercentage() {
if (this.getProgressWork() > 100) {
hoursProgressBar.setValue(100);
exceedHoursProgressBar.setVisible(true);
exceedHoursProgressBar.setValue(0);
String exceedValue = String.valueOf(getProgressWork() - 100);
exceedHoursProgressBar.setWidth(exceedValue + "px");
exceedHoursProgressBar.setLeft("left");
exceedHoursProgressBar
.setStyle("background:red ; border:1px solid red");
} else {
hoursProgressBar.setValue(getProgressWork());
exceedHoursProgressBar.setVisible(false);
}
}
private void showMoneyCostPercentageBars() {
BigDecimal moneyCostPercentage = getMoneyCostPercentage();
if (moneyCostPercentage.compareTo(new BigDecimal(100)) > 0) {
moneyCostProgressBar.setValue(100);
exceedMoneyCostProgressBar.setVisible(true);
exceedMoneyCostProgressBar.setWidth(moneyCostPercentage.subtract(
new BigDecimal(100)).intValue()
+ "px");
} else {
moneyCostProgressBar.setValue(moneyCostPercentage.intValue());
exceedMoneyCostProgressBar.setVisible(false);
}
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -32,6 +32,7 @@ import org.apache.commons.lang.Validate;
import org.joda.time.LocalDate;
import org.libreplan.business.orders.daos.IOrderElementDAO;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.planner.entities.MoneyCostCalculator;
import org.libreplan.business.reports.dtos.WorkReportLineDTO;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
@ -46,6 +47,7 @@ import org.springframework.transaction.annotation.Transactional;
*
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
* @author Ignacio Díaz Teijido <ignacio.diaz@comtecsf.es>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@ -58,6 +60,9 @@ public class AssignedHoursToOrderElementModel implements
@Autowired
private IOrderElementDAO orderElementDAO;
@Autowired
private MoneyCostCalculator moneyCostCalculator;
private EffortDuration assignedDirectEffort;
private OrderElement orderElement;
@ -151,7 +156,7 @@ public class AssignedHoursToOrderElementModel implements
@Override
public EffortDuration getTotalAssignedEffort() {
if (orderElement == null) {
if (orderElement == null || orderElement.getSumChargedEffort() == null) {
return EffortDuration.zero();
}
return this.orderElement.getSumChargedEffort().getTotalChargedEffort();
@ -197,4 +202,32 @@ public class AssignedHoursToOrderElementModel implements
.multiply(new BigDecimal(100)).intValue();
}
@Override
public BigDecimal getBudget() {
if (orderElement == null) {
return BigDecimal.ZERO;
}
return orderElement.getBudget();
}
@Override
@Transactional(readOnly = true)
public BigDecimal getMoneyCost() {
if (orderElement == null) {
return BigDecimal.ZERO;
}
return moneyCostCalculator.getMoneyCost(orderElement);
}
@Override
@Transactional(readOnly = true)
public BigDecimal getMoneyCostPercentage() {
if (orderElement == null) {
return BigDecimal.ZERO;
}
return MoneyCostCalculator.getMoneyCostProportion(
moneyCostCalculator.getMoneyCost(orderElement),
orderElement.getBudget()).multiply(new BigDecimal(100));
}
}

View file

@ -60,4 +60,11 @@ public class DetailsOrderElementController extends
return orderElementModel.isCodeAutogenerated();
}
public boolean isContainer() {
if (orderElementModel.getOrderElement() == null) {
return false;
}
return !orderElementModel.getOrderElement().isLeaf();
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -21,6 +21,7 @@
package org.libreplan.web.orders;
import java.math.BigDecimal;
import java.util.List;
import org.libreplan.business.orders.entities.OrderElement;
@ -29,6 +30,7 @@ import org.libreplan.business.workingday.EffortDuration;
/**
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface IAssignedHoursToOrderElementModel{
public List<WorkReportLineDTO> getWorkReportLines();
@ -42,4 +44,11 @@ public interface IAssignedHoursToOrderElementModel{
public int getProgressWork();
public EffortDuration getAssignedDirectEffort();
BigDecimal getBudget();
BigDecimal getMoneyCost();
BigDecimal getMoneyCostPercentage();
}

View file

@ -502,14 +502,18 @@ public class OrderCRUDController extends GenericForwardComposer {
}
setCurrentTab();
Component orderElementHours = editWindow
.getFellowIfAny("orderElementHours");
if (assignedHoursController == null) {
Component orderElementHours = editWindow
.getFellowIfAny("orderElementHours");
assignedHoursController = (AssignedHoursToOrderElementController) orderElementHours
.getVariable("assignedHoursToOrderElementController", true);
final IOrderElementModel orderElementModel = getOrderElementModel();
assignedHoursController.openWindow(orderElementModel);
} else {
Util.createBindingsFor(orderElementHours);
Util.reloadBindings(orderElementHours);
assignedHoursController.paintProgressBars();
}
}

View file

@ -109,6 +109,7 @@ public class OrderElementController extends GenericForwardComposer {
assignedHoursToOrderElementController.openWindow(orderElementModel);
} else {
redraw(orderElementHours);
assignedHoursToOrderElementController.paintProgressBars();
}
}

View file

@ -23,6 +23,7 @@ package org.libreplan.web.orders;
import static org.libreplan.web.I18nHelper._;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
@ -393,6 +394,7 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
.getOrderElementModel(currentOrderElement);
orderElementController.openWindow(model);
updateHoursFor(currentOrderElement);
updateBudgetFor(currentOrderElement);
}
protected void addCodeCell(final OrderElement orderElement) {
@ -618,6 +620,7 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
public void refreshRow(Treeitem item) {
try {
getRenderer().updateHoursFor((OrderElement) item.getValue());
getRenderer().updateBudgetFor((OrderElement) item.getValue());
getRenderer().render(item, item.getValue());
} catch (Exception e) {
e.printStackTrace();
@ -718,4 +721,25 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
};
}
@Override
protected IBudgetHandler<OrderElement> getBudgetHandler() {
return new IBudgetHandler<OrderElement>() {
@Override
public BigDecimal getBudgetFor(OrderElement element) {
return element.getBudget();
}
@Override
public void setBudgetHours(OrderElement element,
BigDecimal budget) {
if (element instanceof OrderLine) {
OrderLine line = (OrderLine) element;
line.setBudget(budget);
}
}
};
}
}

View file

@ -3,7 +3,7 @@
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2011 Igalia, S.L.
* Copyright (C) 2012 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
@ -58,6 +58,7 @@ import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLineGroup;
import org.libreplan.business.planner.entities.IMoneyCostCalculator;
import org.libreplan.business.qualityforms.daos.IQualityFormDAO;
import org.libreplan.business.qualityforms.entities.QualityForm;
import org.libreplan.business.requirements.entities.DirectCriterionRequirement;
@ -98,9 +99,11 @@ import org.zkoss.zk.ui.Desktop;
/**
* Model for UI operations related to {@link Order}. <br />
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Diego Pino García <dpino@igalia.com>
* @author Jacobo Aragunde Pérez <jaragunde@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@ -168,6 +171,9 @@ public class OrderModel extends IntegrationEntityModel implements IOrderModel {
@Autowired
private IOrderVersionDAO orderVersionDAO;
@Autowired
private IMoneyCostCalculator moneyCostCalculator;
@Override
@Transactional(readOnly = true)
public List<Label> getLabels() {
@ -691,8 +697,8 @@ public class OrderModel extends IntegrationEntityModel implements IOrderModel {
orderDAO.reattachUnmodifiedEntity(order);
StringBuilder result = new StringBuilder();
result.append(_("Progress") + ": ").append(getEstimatedAdvance(order)).append("% , ");
result.append(_("Hours invested") + ": ").append(
getHoursAdvancePercentage(order)).append("% \n");
result.append(_("Hours invested") + ": ")
.append(getHoursAdvancePercentage(order)).append("%\n");
if (!getDescription(order).equals("")) {
result.append(" , " + _("Description") + ": "

View file

@ -76,6 +76,16 @@ public class OrdersTreeComponent extends TreeComponent {
treeRenderer.addHoursCell(currentElement);
}
});
columns.add(new OrdersTreeColumn(_("Budget"), "budget",
_("Total task budget")) {
@Override
protected void doCell(OrderElementTreeitemRenderer treeRenderer,
OrderElement currentElement) {
treeRenderer.addBudgetCell(currentElement);
}
});
columns.add(new OrdersTreeColumn(_("Must start after"),
"estimated_init",

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -22,7 +22,6 @@ package org.libreplan.web.orders.assigntemplates;
import static org.libreplan.web.I18nHelper._;
import org.apache.commons.lang.Validate;
import org.libreplan.business.templates.entities.OrderElementTemplate;
import org.libreplan.business.templates.entities.OrderTemplate;
import org.libreplan.web.common.components.bandboxsearch.BandboxSearch;
@ -36,8 +35,10 @@ import org.zkoss.zul.Caption;
import org.zkoss.zul.Popup;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
* Pop-up to choose the template to create a task from.
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class TemplateFinderPopup extends
HtmlMacroComponent {
@ -48,10 +49,7 @@ public class TemplateFinderPopup extends
private BandboxSearch bandboxSearch;
private Button acceptButton;
private Button cancelButton;
private String acceptButtonLabel = _("Accept");
private String cancelButtonLabel = _("Cancel");
private Caption caption;
private String captionLabel;
public interface IOnResult<T extends OrderElementTemplate> {
public void found(T template);
@ -112,36 +110,11 @@ public class TemplateFinderPopup extends
popup.close();
}
public void setAcceptButtonLabel(String label) {
Validate.notNull(label);
this.acceptButtonLabel = label;
if (acceptButton != null) {
acceptButton.setLabel(label);
}
}
public void setCancelButtonLabel(String label) {
Validate.notNull(label);
this.cancelButtonLabel = label;
if (cancelButton != null) {
cancelButton.setLabel(label);
}
}
public void setCaption(String label) {
Validate.notNull(label);
this.captionLabel = label;
if (caption != null) {
caption.setLabel(label);
}
}
@Override
public void afterCompose() {
super.afterCompose();
acceptButton = (Button) getFellow("acceptButton");
acceptButton.setLabel(acceptButtonLabel);
acceptButton.setLabel(_("Create task"));
acceptButton.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
@ -150,7 +123,7 @@ public class TemplateFinderPopup extends
}
});
cancelButton = (Button) getFellow("cancelButton");
cancelButton.setLabel(cancelButtonLabel);
cancelButton.setLabel(_("Cancel"));
cancelButton.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
@ -161,7 +134,7 @@ public class TemplateFinderPopup extends
finderPlaceholder = getFellow("finderPlaceholder");
popup = (Popup) getFellow("finderPopup");
caption = (Caption) getFellow("finderCaption");
caption.setLabel(captionLabel);
caption.setLabel(_("Choosing Template"));
}
}

View file

@ -3,7 +3,7 @@
*
* 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.
* Copyright (C) 2010-2012 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
@ -57,8 +57,10 @@ import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.entities.ProgressType;
import org.libreplan.business.externalcompanies.daos.IExternalCompanyDAO;
import org.libreplan.business.labels.entities.Label;
import org.libreplan.business.orders.daos.IOrderElementDAO;
import org.libreplan.business.orders.daos.OrderElementDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderStatusEnum;
@ -67,7 +69,9 @@ import org.libreplan.business.planner.daos.ITaskElementDAO;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.Dependency.Type;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.IMoneyCostCalculator;
import org.libreplan.business.planner.entities.ITaskPositionConstrained;
import org.libreplan.business.planner.entities.MoneyCostCalculator;
import org.libreplan.business.planner.entities.PositionConstraintType;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation.Direction;
@ -106,6 +110,7 @@ import org.zkoss.ganttz.util.ReentranceGuard.IReentranceCases;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
@ -274,12 +279,18 @@ public class TaskElementAdapter {
@Autowired
private IResourceAllocationDAO resourceAllocationDAO;
@Autowired
private IExternalCompanyDAO externalCompanyDAO;
@Autowired
private IResourcesSearcher searcher;
@Autowired
private IConfigurationDAO configurationDAO;
@Autowired
private IMoneyCostCalculator moneyCostCalculator;
static class GanttDateAdapter extends CustomDate {
private static final int DAY_MILLISECONDS = (int) Days.days(1)
@ -634,6 +645,58 @@ public class TaskElementAdapter {
RoundingMode.HALF_UP);
}
@Override
public GanttDate getMoneyCostBarEndDate() {
return calculateLimitDateProportionalToTaskElementSize(getMoneyCostBarPercentage());
}
private GanttDate calculateLimitDateProportionalToTaskElementSize(
BigDecimal proportion) {
if (proportion.compareTo(BigDecimal.ZERO) == 0) {
return getBeginDate();
}
IntraDayDate start = taskElement.getIntraDayStartDate();
IntraDayDate end = taskElement.getIntraDayEndDate();
EffortDuration effortBetween = start.effortUntil(end);
int seconds = new BigDecimal(effortBetween.getSeconds())
.multiply(proportion).toBigInteger().intValue();
return TaskElementAdapter.toGantt(
start.addEffort(EffortDuration.seconds(seconds)),
EffortDuration.hours(8));
}
private BigDecimal getMoneyCostBarPercentage() {
return MoneyCostCalculator.getMoneyCostProportion(
getMoneyCost(), getBudget());
}
private BigDecimal getBudget() {
if ((taskElement == null)
|| (taskElement.getOrderElement() == null)) {
return BigDecimal.ZERO;
}
return taskElement.getOrderElement().getBudget();
}
private BigDecimal getMoneyCost() {
if ((taskElement == null)
|| (taskElement.getOrderElement() == null)) {
return BigDecimal.ZERO;
}
return transactionService
.runOnReadOnlyTransaction(new IOnTransaction<BigDecimal>() {
@Override
public BigDecimal execute() {
return moneyCostCalculator
.getMoneyCost(taskElement
.getOrderElement());
}
});
}
@Override
public GanttDate getAdvanceEndDate(String progressType) {
return getAdvanceEndDate(ProgressType.asEnum(progressType));
@ -863,6 +926,10 @@ public class TaskElementAdapter {
public String execute() {
orderElementDAO.reattach(taskElement
.getOrderElement());
if (taskElement.isSubcontracted()) {
externalCompanyDAO.reattach(taskElement
.getSubcontractedCompany());
}
return buildResourcesText();
}
});
@ -925,6 +992,9 @@ public class TaskElementAdapter {
}
}
}
if (taskElement.isSubcontracted()) {
result.add(taskElement.getSubcontractionName());
}
Collections.sort(result);
return StringUtils.join(result, ", ");
}
@ -993,9 +1063,19 @@ public class TaskElementAdapter {
result.append(_("Hours invested") + ": ")
.append(getHoursAdvancePercentage().multiply(
new BigDecimal(100))).append("% <br/>");
if (taskElement.getOrderElement() instanceof Order) {
result.append(_("State") + ": ").append(getOrderState());
} else {
result.append(
_("Budget: {0}€, Consumed: {1}€ ({2}%)",
getBudget(),
getMoneyCost(),
getMoneyCostBarPercentage().multiply(
new BigDecimal(100)))).append(
"<br/>");
}
String labels = buildLabelsText();
if (!labels.equals("")) {
result.append("<div class='tooltip-labels'>" + _("Labels")

View file

@ -120,6 +120,8 @@ public class CompanyPlanningController implements Composer {
planner.setAreShownReportedHoursByDefault(Planner
.guessShowReportedHoursByDefault(parameters));
planner.setAreShownMoneyCostBarByDefault(Planner
.guessShowMoneyCostBarByDefault(parameters));
orderFilter = (Vbox) planner.getFellow("orderFilter");
// Configuration of the order filter

View file

@ -21,7 +21,6 @@
package org.libreplan.web.planner.company;
import static java.util.Arrays.asList;
import static org.libreplan.web.I18nHelper._;
import java.io.IOException;
@ -96,13 +95,11 @@ import org.zkoss.ganttz.timetracker.zoom.ZoomLevel;
import org.zkoss.ganttz.util.Emitter;
import org.zkoss.ganttz.util.Emitter.IEmissionListener;
import org.zkoss.ganttz.util.Interval;
import org.zkoss.zk.au.out.AuInsertAfter;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Datebox;
import org.zkoss.zul.Div;
@ -569,6 +566,7 @@ public class CompanyPlanningModel implements ICompanyPlanningModel {
configuration.setRenamingTasksEnabled(false);
configuration.setTreeEditable(false);
configuration.setShowAllResourcesEnabled(false);
configuration.setMoneyCostBarEnabled(false);
}
private void addAdditionalCommands(

View file

@ -38,4 +38,6 @@ public interface IOrderPlanningGate {
void goToOrderDetails(Order order);
void goToDashboard(Order order);
}

View file

@ -183,6 +183,8 @@ public class OrderPlanningController implements Composer {
planner.setAreShownReportedHoursByDefault(Planner
.guessShowReportedHoursByDefault(parameters));
planner.setAreShownMoneyCostBarByDefault(Planner
.guessShowMoneyCostBarByDefault(parameters));
orderElementFilter = (Vbox) planner.getFellow("orderElementFilter");
// Configuration of the order filter

View file

@ -56,6 +56,7 @@ import org.libreplan.business.planner.entities.DayAssignment.FilterType;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.DerivedAllocation;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.IMoneyCostCalculator;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation.IVisitor;
import org.libreplan.business.planner.entities.SpecificResourceAllocation;
@ -173,6 +174,9 @@ public class PlanningStateCreator {
@Autowired
private IOrderAuthorizationDAO orderAuthorizationDAO;
@Autowired
private IMoneyCostCalculator moneyCostCalculator;
void synchronizeWithSchedule(Order order, IOptionalPersistence persistence) {
List<TaskSourceSynchronization> synchronizationsNeeded = order
.calculateSynchronizationsNeeded();
@ -277,6 +281,9 @@ public class PlanningStateCreator {
currentScenario);
forceLoadOfWorkingHours(result.getInitial());
moneyCostCalculator.resetMoneyCostMap();
return result;
}

View file

@ -52,6 +52,7 @@ import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.daos.IOrderElementDAO;
import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.ISumChargedEffortRecalculator;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLineGroup;
@ -206,6 +207,9 @@ public class SaveCommandBuilder {
@Autowired
private IDependencyDAO dependencyDAO;
@Autowired
private ISumChargedEffortRecalculator sumChargedEffortRecalculator;
private class SaveCommand implements ISaveCommand {
private PlanningState state;
@ -289,6 +293,13 @@ public class SaveCommandBuilder {
dontPoseAsTransientObjectAnymore(state.getOrder()
.getEndDateCommunicationToCustomer());
state.getScenarioInfo().afterCommit();
if (state.getOrder()
.isNeededToRecalculateSumChargedEfforts()) {
sumChargedEffortRecalculator.recalculate(state
.getOrder().getId());
}
fireAfterSave();
if (afterSaveActions != null) {
afterSaveActions.doActions();
@ -337,9 +348,7 @@ public class SaveCommandBuilder {
private void doTheSaving() {
Order order = state.getOrder();
if (order.isCodeAutogenerated()) {
generateOrderElementCodes(order);
}
generateOrderElementCodes(order);
createAdvancePercentagesIfRequired(order);
calculateAndSetTotalHours(order);
checkConstraintOrderUniqueCode(order);

View file

@ -0,0 +1,126 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2010-2012 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.planner.tabs;
import static org.libreplan.web.I18nHelper._;
import static org.libreplan.web.planner.tabs.MultipleTabsPlannerController.BREADCRUMBS_SEPARATOR;
import static org.libreplan.web.planner.tabs.MultipleTabsPlannerController.getSchedulingLabel;
import java.util.HashMap;
import java.util.Map;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.Registry;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.web.dashboard.DashboardController;
import org.libreplan.web.planner.order.PlanningStateCreator;
import org.libreplan.web.planner.order.PlanningStateCreator.IActionsOnRetrieval;
import org.libreplan.web.planner.order.PlanningStateCreator.PlanningState;
import org.libreplan.web.planner.tabs.CreatedOnDemandTab.IComponentCreator;
import org.zkoss.ganttz.extensions.ITab;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zul.Image;
import org.zkoss.zul.Label;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Nacho Barrientos <nacho@igalia.com>
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
*
*/
public class DashboardTabCreator {
public static ITab create(Mode mode,
PlanningStateCreator planningStateCreator,
DashboardController dashboardController,
Component breadcrumbs) {
return new DashboardTabCreator(mode, planningStateCreator,
dashboardController, breadcrumbs).build();
}
private final PlanningStateCreator planningStateCreator;
private final Mode mode;
private final DashboardController dashboardController;
private final Component breadcrumbs;
private DashboardTabCreator(Mode mode,
PlanningStateCreator planningStateCreator,
DashboardController dashboardController,
Component breadcrumbs) {
this.mode = mode;
this.planningStateCreator = planningStateCreator;
this.dashboardController = dashboardController;
this.breadcrumbs = breadcrumbs;
}
private ITab build() {
return TabOnModeType.forMode(mode)
.forType(ModeType.GLOBAL, createDashboardTab())
.forType(ModeType.ORDER, createDashboardTab())
.create();
}
private ITab createDashboardTab() {
IComponentCreator componentCreator = new IComponentCreator() {
@Override
public org.zkoss.zk.ui.Component create(
org.zkoss.zk.ui.Component parent) {
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.put("dashboardController", dashboardController);
return Executions.createComponents(
"/dashboard/_dashboardfororder.zul", parent,
arguments);
}
};
return new CreatedOnDemandTab(_("Dashboard"), "order-dashboard",
componentCreator) {
@Override
protected void afterShowAction() {
PlanningState planningState = getPlanningState(mode.getOrder(), getDesktop());
Order currentOrder = planningState.getOrder();
dashboardController.setCurrentOrder(currentOrder);
breadcrumbs.getChildren().clear();
breadcrumbs.appendChild(new Image(BREADCRUMBS_SEPARATOR));
breadcrumbs.appendChild(new Label(getSchedulingLabel()));
breadcrumbs.appendChild(new Image(BREADCRUMBS_SEPARATOR));
breadcrumbs.appendChild(new Label(_("Dashboard")));
breadcrumbs.appendChild(new Image(BREADCRUMBS_SEPARATOR));
breadcrumbs.appendChild(new Label(currentOrder.getName()));
}
};
}
PlanningState getPlanningState(final Order order, final Desktop desktop) {
IAdHocTransactionService transactionService = Registry
.getTransactionService();
return transactionService
.runOnTransaction(new IOnTransaction<PlanningState>() {
public PlanningState execute() {
return planningStateCreator.retrieveOrCreate(desktop,
order);
}
});
}
}

View file

@ -38,6 +38,7 @@ import org.libreplan.business.templates.entities.OrderTemplate;
import org.libreplan.business.users.entities.UserRole;
import org.libreplan.web.common.entrypoints.EntryPointsHandler;
import org.libreplan.web.common.entrypoints.URLHandlerRegistry;
import org.libreplan.web.dashboard.DashboardController;
import org.libreplan.web.limitingresources.LimitingResourcesController;
import org.libreplan.web.montecarlo.MonteCarloController;
import org.libreplan.web.orders.OrderCRUDController;
@ -157,6 +158,8 @@ public class MultipleTabsPlannerController implements Composer,
private ITab advancedAllocationTab;
private ITab dashboardTab;
private TabSwitcher tabsSwitcher;
@Autowired
@ -177,6 +180,9 @@ public class MultipleTabsPlannerController implements Composer,
@Autowired
private LimitingResourcesController limitingResourcesControllerGlobal;
@Autowired
private DashboardController dashboardController;
private org.zkoss.zk.ui.Component breadcrumbs;
@Autowired
@ -226,6 +232,11 @@ public class MultipleTabsPlannerController implements Composer,
.show(planningTab, changeModeTo(order));
}
@Override
public void goToDashboard(Order order) {
// do nothing
}
}, breadcrumbs);
limitingResourcesTab = LimitingResourcesTabCreator.create(mode,
@ -252,8 +263,16 @@ public class MultipleTabsPlannerController implements Composer,
// do nothing
}
@Override
public void goToDashboard(Order order) {
// do nothing
}
}, parameters);
dashboardTab = DashboardTabCreator.create(mode, planningStateCreator,
dashboardController, breadcrumbs);
final boolean isMontecarloVisible = isMonteCarloVisible();
if (isMontecarloVisible) {
monteCarloTab = MonteCarloTabCreator.create(mode,
@ -272,7 +291,8 @@ public class MultipleTabsPlannerController implements Composer,
.add(tabWithNameReloading(ordersTab, typeChanged))
.add(tabWithNameReloading(resourceLoadTab, typeChanged))
.add(tabWithNameReloading(limitingResourcesTab, typeChanged))
.add(visibleOnlyAtOrderMode(advancedAllocationTab));
.add(visibleOnlyAtOrderMode(advancedAllocationTab))
.add(visibleOnlyAtOrderMode(dashboardTab));
if (isMontecarloVisible) {
tabsConfiguration.add(visibleOnlyAtOrderMode(monteCarloTab));

View file

@ -58,6 +58,7 @@ import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Comboitem;
import org.zkoss.zul.Decimalbox;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
@ -92,6 +93,8 @@ public class TaskPropertiesController extends GenericForwardComposer {
private Intbox duration;
private Decimalbox budget;
private Datebox startDateBox;
private Datebox endDateBox;
@ -222,6 +225,7 @@ public class TaskPropertiesController extends GenericForwardComposer {
hideResourceAllocationTypeRow();
}
hours.setValue(currentTaskElement.getWorkHours());
budget.setValue(currentTaskElement.getBudget());
Util.reloadBindings(tabpanel);
}

View file

@ -198,9 +198,7 @@ public class CutyPrint {
String generatedCSSFile = createCSSFile(
absolutePath + "/planner/css/print.css",
plannerWidth,
planner, parameters
.get("advances"),
parameters.get("reportedHours"),
planner,
parameters.get("labels"),
parameters.get("resources"),
expanded,
@ -236,10 +234,6 @@ public class CutyPrint {
printProcess = Runtime.getRuntime().exec(captureString);
}
try {
// Ensure CutyCapt process finalization
CutyCaptTimeout timeoutThread = new CutyCaptTimeout( CUTYCAPT_TIMEOUT );
new Thread(timeoutThread).start();
printProcess.waitFor();
printProcess.destroy();
@ -312,9 +306,7 @@ public class CutyPrint {
}
private static String createCSSFile(String srFile, int width,
Planner planner, String advances, String reportedHours,
String labels, String resources,
boolean expanded,
Planner planner, String labels, String resources, boolean expanded,
int minimumWidthForTaskNameColumn) {
File generatedCSS = null;
try {

View file

@ -28,15 +28,18 @@ import java.util.List;
import javax.annotation.Resource;
import org.apache.commons.logging.LogFactory;
import org.hibernate.validator.InvalidValue;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.templates.entities.OrderElementTemplate;
import org.libreplan.web.common.ConstraintChecker;
import org.libreplan.web.common.IMessagesForUser;
import org.libreplan.web.common.Level;
import org.libreplan.web.common.MessagesForUser;
import org.libreplan.web.common.OnlyOneVisible;
import org.libreplan.web.common.Util;
import org.libreplan.web.common.entrypoints.IURLHandlerRegistry;
import org.libreplan.web.common.entrypoints.EntryPointsHandler;
import org.libreplan.web.common.entrypoints.IURLHandlerRegistry;
import org.libreplan.web.planner.tabs.IGlobalViewEntryPoints;
import org.libreplan.web.templates.advances.AdvancesAssignmentComponent;
import org.libreplan.web.templates.criterionrequirements.CriterionRequirementTemplateComponent;
@ -61,6 +64,7 @@ import org.zkoss.zul.Image;
import org.zkoss.zul.Label;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Tab;
import org.zkoss.zul.Tabpanel;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.Tree;
import org.zkoss.zul.Window;
@ -214,9 +218,16 @@ public class OrderTemplatesController extends GenericForwardComposer implements
public void saveAndExit() {
if (isAllValid()) {
model.confirmSave();
messagesForUser.showMessage(Level.INFO, _("Template saved"));
show(listWindow);
try {
model.confirmSave();
messagesForUser.showMessage(Level.INFO, _("Template saved"));
show(listWindow);
} catch (ValidationException e) {
for (InvalidValue invalidValue : e.getInvalidValues()) {
messagesForUser.showMessage(Level.ERROR,
invalidValue.getMessage());
}
}
}
}
@ -226,15 +237,24 @@ public class OrderTemplatesController extends GenericForwardComposer implements
public void saveAndContinue() {
if (isAllValid()) {
model.confirmSave();
model.initEdit(getTemplate());
bindTemplatesTreeWithModel();
messagesForUser.showMessage(Level.INFO, _("Template saved"));
try {
model.confirmSave();
model.initEdit(getTemplate());
bindTemplatesTreeWithModel();
messagesForUser.showMessage(Level.INFO, _("Template saved"));
} catch (ValidationException e) {
for (InvalidValue invalidValue : e.getInvalidValues()) {
messagesForUser.showMessage(Level.ERROR,
invalidValue.getMessage());
}
}
}
}
private boolean isAllValid() {
// validate template name
ConstraintChecker.isValid(editWindow);
name = (Textbox) editWindow.getFellowIfAny("name");
if ((name != null) && (!name.isValid())) {
selectTab("tabGeneralData");
@ -374,4 +394,18 @@ public class OrderTemplatesController extends GenericForwardComposer implements
}
}
public boolean isContainer() {
if (model.getTemplate() == null) {
return false;
}
return !model.getTemplate().isLeaf();
}
public void reloadBudget() {
Tabpanel tabPanel = (Tabpanel) editWindow
.getFellow("tabPanelGeneralData");
Util.reloadBindings(tabPanel);
}
}

View file

@ -87,6 +87,15 @@ public class TemplatesTreeComponent extends TreeComponent {
renderer.addHoursCell(currentElement);
}
});
result.add(new TemplatesTreeColumn(_("Budget"), "budget") {
@Override
protected void doCell(TemplatesTreeRenderer renderer,
Treeitem item, OrderElementTemplate currentElement) {
renderer.addBudgetCell(currentElement);
}
});
result.add(new TemplatesTreeColumn(
_("Must start after (days since beginning project)"),

View file

@ -22,6 +22,8 @@ package org.libreplan.web.templates;
import static org.libreplan.web.I18nHelper._;
import java.math.BigDecimal;
import org.apache.commons.lang.StringUtils;
import org.hibernate.validator.ClassValidator;
import org.libreplan.business.orders.entities.SchedulingState;
@ -105,7 +107,7 @@ public class TemplatesTreeController extends
element.setName(value);
}
});
textBox.setConstraint("no empty:" + _("cannot be null or empty"));
addCell(textBox);
putNameTextbox(element, textBox);
}
@ -125,6 +127,8 @@ public class TemplatesTreeController extends
element.setCode(value);
}
});
textBoxCode.setConstraint("no empty:"
+ _("cannot be null or empty"));
addCell(textBoxCode);
}
@ -272,11 +276,33 @@ public class TemplatesTreeController extends
};
}
@Override
protected IBudgetHandler<OrderElementTemplate> getBudgetHandler() {
return new IBudgetHandler<OrderElementTemplate>() {
@Override
public BigDecimal getBudgetFor(OrderElementTemplate element) {
return element.getBudget();
}
@Override
public void setBudgetHours(OrderElementTemplate element,
BigDecimal budget) {
if (element instanceof OrderLineTemplate) {
OrderLineTemplate line = (OrderLineTemplate) element;
line.setBudget(budget);
}
}
};
}
public void refreshRow(Treeitem item) {
try {
OrderElementTemplate orderElement = (OrderElementTemplate) item
.getValue();
getRenderer().updateHoursFor(orderElement);
getRenderer().updateBudgetFor(orderElement);
getRenderer().render(item, orderElement);
} catch (Exception e) {
e.printStackTrace();

View file

@ -22,6 +22,7 @@ package org.libreplan.web.tree;
import static org.libreplan.web.I18nHelper._;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -62,6 +63,7 @@ import org.zkoss.zk.ui.event.KeyEvent;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Constraint;
import org.zkoss.zul.Decimalbox;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.RendererCtrl;
import org.zkoss.zul.Textbox;
@ -234,6 +236,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
T newNode = getModel().addElementAt(node, name.getValue(),
hours.getValue());
getRenderer().refreshHoursValueForThisNodeAndParents(newNode);
getRenderer().refreshBudgetValueForThisNodeAndParents(newNode);
if (node.isLeaf() && !node.isEmptyLeaf()) {
// Then a new container will be created
@ -325,6 +328,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
List<T> parentNodes = getModel().getParents(element);
getModel().removeNode(element);
getRenderer().refreshHoursValueForNodes(parentNodes);
getRenderer().refreshBudgetValueForNodes(parentNodes);
}
@Override
@ -578,6 +582,8 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
private Map<T, Intbox> hoursIntBoxByElement = new HashMap<T, Intbox>();
private Map<T, Decimalbox> budgetDecimalboxByElement = new HashMap<T, Decimalbox>();
private KeyboardNavigationHandler navigationHandler = new KeyboardNavigationHandler();
private Treerow currentTreeRow;
@ -771,6 +777,117 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
protected abstract void addDescriptionCell(final T element);
public void addBudgetCell(final T currentElement) {
Decimalbox decimalboxBudget = buildBudgetDecimalboxFor(currentElement);
budgetDecimalboxByElement.put(currentElement, decimalboxBudget);
if (readOnly) {
decimalboxBudget.setDisabled(true);
}
addCell(decimalboxBudget);
}
private Decimalbox buildBudgetDecimalboxFor(final T element) {
Decimalbox result = new DecimalboxDirectValue();
if (element.isLeaf()) {
Util.bind(result, getBudgetGetterFor(element),
getBudgetSetterFor(element));
result.setConstraint(getBudgetConstraintFor(element));
} else {
// If it's a container budget cell is not editable
Util.bind(result, getBudgetGetterFor(element));
}
return result;
}
private Getter<BigDecimal> getBudgetGetterFor(final T element) {
return new Util.Getter<BigDecimal>() {
@Override
public BigDecimal get() {
return getBudgetHandler().getBudgetFor(element);
}
};
}
private Setter<BigDecimal> getBudgetSetterFor(final T element) {
return new Util.Setter<BigDecimal>() {
@Override
public void set(BigDecimal value) {
getBudgetHandler().setBudgetHours(element, value);
List<T> parentNodes = getModel().getParents(element);
// Remove the last element because it's an
// Order node, not an OrderElement
parentNodes.remove(parentNodes.size() - 1);
for (T node : parentNodes) {
DecimalboxDirectValue decimalbox = (DecimalboxDirectValue) budgetDecimalboxByElement
.get(node);
BigDecimal budget = getBudgetHandler().getBudgetFor(
node);
if (isInCurrentPage(decimalbox)) {
decimalbox.setValue(budget);
} else {
decimalbox.setValueDirectly(budget);
}
}
}
private boolean isInCurrentPage(DecimalboxDirectValue intbox) {
Treeitem treeItem = (Treeitem) intbox.getParent()
.getParent().getParent();
List<Treeitem> treeItems = new ArrayList<Treeitem>(
tree.getItems());
int position = treeItems.indexOf(treeItem);
if (position < 0) {
throw new RuntimeException("Treeitem " + treeItem
+ " has to belong to tree.getItems() list");
}
return (position / tree.getPageSize()) == tree
.getActivePage();
}
};
}
public void updateBudgetFor(T element) {
if (!readOnly && element.isLeaf()) {
Decimalbox decimalbox = budgetDecimalboxByElement.get(element);
Treecell tc = (Treecell) decimalbox.getParent();
decimalbox.invalidate();
refreshBudgetValueForThisNodeAndParents(element);
}
}
public void refreshBudgetValueForThisNodeAndParents(T node) {
List<T> nodeAndItsParents = getModel().getParents(node);
nodeAndItsParents.add(node);
refreshBudgetValueForNodes(nodeAndItsParents);
}
public void refreshBudgetValueForNodes(List<T> nodes) {
for (T node : nodes) {
Decimalbox decimalbox = budgetDecimalboxByElement.get(node);
// For the Order node there is no associated decimalbox
if (decimalbox != null) {
BigDecimal currentBudget = getBudgetHandler().getBudgetFor(node);
decimalbox.setValue(currentBudget);
}
}
}
private Constraint getBudgetConstraintFor(final T line) {
return new Constraint() {
@Override
public void validate(Component comp, Object value)
throws WrongValueException {
if (((BigDecimal) value).compareTo(BigDecimal.ZERO) < 0) {
throw new WrongValueException(comp,
_("Budget value cannot be negative"));
}
}
};
}
public void addHoursCell(final T currentElement) {
Intbox intboxHours = buildHoursIntboxFor(currentElement);
hoursIntBoxByElement.put(currentElement, intboxHours);
@ -1057,6 +1174,15 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
protected abstract IHoursGroupHandler<T> getHoursGroupHandler();
public interface IBudgetHandler<T> {
BigDecimal getBudgetFor(T element);
void setBudgetHours(T element, BigDecimal budget);
}
protected abstract IBudgetHandler<T> getBudgetHandler();
/**
* Disable control buttons (new, up, down, indent, unindent, delete)
*/
@ -1137,4 +1263,24 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
}
}
/**
* This class is to give visibility to method
* {@link Decimalbox#setValueDirectly} which is marked as protected in
* {@link Decimalbox} class.
*
* <br />
*
* This is needed to prevent calling {@link AbstractComponent#smartUpdate}
* when the {@link Decimalbox} is not in current page. <tt>smartUpdate</tt>
* is called by {@link Decimalbox#setValue(Integer)}. This call causes a
* JavaScript error when trying to update {@link Decimalbox} that are not in
* current page in the tree.
*/
private class DecimalboxDirectValue extends Decimalbox {
@Override
public void setValueDirectly(Object value) {
super.setValueDirectly(value);
}
}
}

View file

@ -33,9 +33,14 @@ import org.libreplan.business.users.entities.UserRole;
import org.libreplan.web.common.BaseCRUDController;
import org.libreplan.web.common.Util;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Comboitem;
import org.zkoss.zul.Label;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Row;
import org.zkoss.zul.RowRenderer;
/**
* Controller for CRUD actions over a {@link Profile}
@ -162,4 +167,23 @@ public class ProfileCRUDController extends BaseCRUDController<Profile> {
protected void delete(Profile profile) throws InstanceNotFoundException {
profileModel.confirmRemove(profile);
}
public RowRenderer getRolesRenderer() {
return new RowRenderer() {
@Override
public void render(Row row, Object data) throws Exception {
final UserRole role = (UserRole) data;
row.appendChild(new Label(_(role.getDisplayName())));
row.appendChild(Util.createRemoveButton(new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
removeRole(role);
}
}));
}
};
}
}

Some files were not shown because too many files have changed in this diff Show more