Merge branch 'master' into subcontracting-merger-master
This commit is contained in:
commit
2008ea6744
120 changed files with 4895 additions and 545 deletions
108
INSTALL
108
INSTALL
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
============================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
================
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
================
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -2427,4 +2427,4 @@ public class GanttDiagramGraph<V, D extends IDependency<V>> implements
|
|||
return adapter.getChildren(task);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>');
|
||||
|
|
|
|||
|
|
@ -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>');
|
||||
|
||||
|
|
|
|||
|
|
@ -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>');
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -594,4 +594,6 @@ public abstract class OrderElementTemplate extends BaseEntity implements
|
|||
this.origin = origin;
|
||||
}
|
||||
|
||||
public abstract BigDecimal getBudget();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>() {
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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)))));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,4 +60,11 @@ public class DetailsOrderElementController extends
|
|||
return orderElementModel.isCodeAutogenerated();
|
||||
}
|
||||
|
||||
public boolean isContainer() {
|
||||
if (orderElementModel.getOrderElement() == null) {
|
||||
return false;
|
||||
}
|
||||
return !orderElementModel.getOrderElement().isLeaf();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ public class OrderElementController extends GenericForwardComposer {
|
|||
assignedHoursToOrderElementController.openWindow(orderElementModel);
|
||||
} else {
|
||||
redraw(orderElementHours);
|
||||
assignedHoursToOrderElementController.paintProgressBars();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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") + ": "
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -38,4 +38,6 @@ public interface IOrderPlanningGate {
|
|||
|
||||
void goToOrderDetails(Order order);
|
||||
|
||||
void goToDashboard(Order order);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)"),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue