Merge branch 'libreplan-1.3' into adapt-planning-according-timesheets

This commit is contained in:
Manuel Rego Casasnovas 2012-11-15 09:45:27 +01:00
commit d258b989f9
63 changed files with 1349 additions and 182 deletions

23
HACKING
View file

@ -87,12 +87,21 @@ Fedora
* Install requirements::
# yum install git maven java-1.6.0-openjdk postgresql postgresql-server python-docutils make gettext gnu-free-fonts-compat
# yum install git maven java-1.7.0-openjdk-devel postgresql postgresql-server python-docutils make gettext gnu-free-fonts-compat
.. WARNING:: Use the following command in Fedora 16 or below::
# yum install git maven java-1.6.0-openjdk postgresql postgresql-server python-docutils make gettext gnu-free-fonts-compat
* Start database service::
# service postgresql initdb
# service postgresql start
# su - postgres -c "PGDATA=/var/lib/pgsql/data initdb"
# systemctl start postgresql.service
.. WARNING:: Use the following commands in Fedora 16 or below::
# service postgresql initdb
# service postgresql start
* Connect to database::
@ -110,11 +119,13 @@ Fedora
ALTER USER postgres WITH PASSWORD 'postgres';
* Edit ``/var/lib/pgsql/data/pg_hba.conf`` and replace ``ident`` by ``md5``
.. WARNING:: These steps are only for Fedora 16 and below:
* Reload database configuration::
* Edit ``/var/lib/pgsql/data/pg_hba.conf`` and replace ``ident`` by ``md5``
# service postgresql reload
* Reload database configuration::
# service postgresql reload
* Download source code::

View file

@ -563,4 +563,11 @@ public abstract class Task implements ITaskFundamentalProperties {
return fundamentalProperties.getLastTimesheetDate();
}
public void firePropertyChangeForTaskDates() {
fundamentalPropertiesListeners.firePropertyChange("beginDate", null,
getBeginDate());
fundamentalPropertiesListeners.firePropertyChange("endDate", null,
getEndDate());
}
}

View file

@ -110,7 +110,7 @@ public abstract class BaseEntity implements INewObject {
}
protected static <T extends BaseEntity> T create(T baseEntity) {
baseEntity.newObject = true;
baseEntity.setNewObject(true);
return baseEntity;
}

View file

@ -67,7 +67,7 @@ public abstract class IntegrationEntity extends BaseEntity {
T integrationEntity, String code) {
BaseEntity.create(integrationEntity);
integrationEntity.code = code;
integrationEntity.setCode(code);
return integrationEntity;
@ -82,7 +82,7 @@ public abstract class IntegrationEntity extends BaseEntity {
T integrationEntity) {
BaseEntity.create(integrationEntity);
integrationEntity.code = generateCode();
integrationEntity.setCode(generateCode());
return integrationEntity;

View file

@ -109,6 +109,8 @@ public interface IOrderElementDAO extends IIntegrationEntityDAO<OrderElement> {
EffortDuration calculateMinWorkedHours(final List<OrderElement> list);
boolean isAlreadyInUse(OrderElement orderElement);
boolean isAlreadyInUseThisOrAnyOfItsChildren(OrderElement orderElement);
/**
@ -130,6 +132,8 @@ public interface IOrderElementDAO extends IIntegrationEntityDAO<OrderElement> {
boolean hasImputedExpenseSheet(Long id) throws InstanceNotFoundException;
boolean hasImputedExpenseSheetThisOrAnyOfItsChildren(Long id) throws InstanceNotFoundException;
OrderElement findByExternalCode(String code) throws InstanceNotFoundException;
public List<OrderElement> findByLabelsAndCriteria(Set<Label> labels,

View file

@ -421,7 +421,8 @@ public class OrderElementDAO extends IntegrationEntityDAO<OrderElement>
return min;
}
private boolean isAlreadyInUse(OrderElement orderElement) {
@Override
public boolean isAlreadyInUse(OrderElement orderElement) {
if (orderElement.isNewObject()) {
return false;
}
@ -518,7 +519,14 @@ public class OrderElementDAO extends IntegrationEntityDAO<OrderElement>
}
@Override
public boolean hasImputedExpenseSheet(Long id) throws InstanceNotFoundException {
public boolean hasImputedExpenseSheet(Long id)
throws InstanceNotFoundException {
OrderElement orderElement = find(id);
return (!expenseSheetLineDAO.findByOrderElement(orderElement).isEmpty());
}
@Override
public boolean hasImputedExpenseSheetThisOrAnyOfItsChildren(Long id) throws InstanceNotFoundException {
OrderElement orderElement = find(id);
return (!expenseSheetLineDAO.findByOrderElementAndChildren(orderElement).isEmpty());
}

View file

@ -42,6 +42,7 @@ import org.hibernate.validator.Valid;
import org.joda.time.LocalDate;
import org.libreplan.business.advance.bootstrap.PredefinedAdvancedTypes;
import org.libreplan.business.advance.entities.AdvanceAssignment;
import org.libreplan.business.advance.entities.AdvanceMeasurement;
import org.libreplan.business.advance.entities.AdvanceType;
import org.libreplan.business.advance.entities.DirectAdvanceAssignment;
import org.libreplan.business.advance.entities.IndirectAdvanceAssignment;
@ -70,6 +71,7 @@ import org.libreplan.business.scenarios.entities.Scenario;
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.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.entities.WorkReportLine;
@ -1630,4 +1632,23 @@ public abstract class OrderElement extends IntegrationEntity implements
return sumChargedEffort.getLastTimesheetDate();
}
public void detachFromParent() {
parent = null;
}
public AdvanceMeasurement getLastAdvanceMeasurement() {
DirectAdvanceAssignment advanceAssignment = getReportGlobalAdvanceAssignment();
if (advanceAssignment == null) {
return null;
}
return advanceAssignment.getLastAdvanceMeasurement();
}
public String getEffortAsString() {
SumChargedEffort sumChargedEffort = getSumChargedEffort();
EffortDuration effort = sumChargedEffort != null ? sumChargedEffort
.getTotalChargedEffort() : EffortDuration.zero();
return effort.toFormattedString();
}
}

View file

@ -3,7 +3,7 @@
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
* Copyright (C) 2010-2012 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -26,13 +26,19 @@ import static org.libreplan.business.i18n.I18nHelper._;
/**
* @author Susana Montes Pedreiera <smotnes@wirelessgalicia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public enum OrderStatusEnum {
OFFERED(_("OFFERED")), ACCEPTED(_("ACCEPTED")), STARTED(_("STARTED")), FINISHED(
_("FINISHED")), CANCELLED(_("CANCELLED")), SUBCONTRACTED_PENDING_ORDER(
_("SUBCONTRACTED PENDING PROJECT")), STORED(_("STORED"));
PRE_SALES(_("PRE-SALES")),
OFFERED(_("OFFERED")),
OUTSOURCED(_("OUTSOURCED")),
ACCEPTED(_("ACCEPTED")),
STARTED(_("STARTED")),
ON_HOLD(_("ON HOLD")),
FINISHED(_("FINISHED")),
CANCELLED(_("CANCELLED")),
STORED(_("STORED"));
private String description;
@ -45,6 +51,6 @@ public enum OrderStatusEnum {
}
public static OrderStatusEnum getDefault() {
return OFFERED;
return PRE_SALES;
}
}

View file

@ -137,7 +137,7 @@ public abstract class TaskElement extends BaseEntity {
protected static <T extends TaskElement> T create(T taskElement,
TaskSource taskSource) {
taskElement.taskSource = taskSource;
taskElement.setTaskSource(taskSource);
taskElement.updateDeadlineFromOrderElement();
taskElement.setName(taskElement.getOrderElement().getName());
taskElement.updateAdvancePercentageFromOrderElement();
@ -221,6 +221,10 @@ public abstract class TaskElement extends BaseEntity {
return taskSource;
}
protected void setTaskSource(TaskSource taskSource) {
this.taskSource = taskSource;
}
protected void copyDependenciesTo(TaskElement result) {
for (Dependency dependency : getDependenciesWithThisOrigin()) {
Dependency.create(result, dependency.getDestination(),

View file

@ -98,7 +98,7 @@ public abstract class AllocationModification {
.unmodifiableList(new ArrayList<Resource>(resources));
}
private boolean hasNoResources() {
protected boolean hasNoResources() {
return resourcesOnWhichApplyAllocation.isEmpty();
}

View file

@ -111,7 +111,7 @@ public interface IWorkerDAO extends IIntegrationEntityDAO<Worker> {
List<Worker> findByFirstNameSecondNameAndNifAnotherTransaction(
String firstname, String surname, String nif);
List<Object[]> getWorkingHoursGroupedPerWorker(List<String> workerNifs,
List<Object[]> getWorkingHoursGroupedPerWorker(List<String> workerCodes,
Date startingDate, Date endingDate);
Worker findByNifAnotherTransaction(String nif)

View file

@ -147,8 +147,8 @@ public class WorkerDAO extends IntegrationEntityDAO<Worker>
@Override
@Transactional(readOnly = true)
public List<Object[]> getWorkingHoursGroupedPerWorker(
List<String> workerNifs, Date startingDate, Date endingDate) {
String strQuery = "SELECT worker.nif, SUM(wrl.effort) "
List<String> workerCodes, Date startingDate, Date endingDate) {
String strQuery = "SELECT worker.code, SUM(wrl.effort) "
+ "FROM Worker worker, WorkReportLine wrl "
+ "LEFT OUTER JOIN wrl.resource resource "
+ "WHERE resource.id = worker.id ";
@ -165,15 +165,15 @@ public class WorkerDAO extends IntegrationEntityDAO<Worker>
}
// Set workers
if (workerNifs != null && !workerNifs.isEmpty()) {
strQuery += "AND worker.nif IN (:workerNifs) ";
if (workerCodes != null && !workerCodes.isEmpty()) {
strQuery += "AND worker.code IN (:workerCodes) ";
}
// Group by
strQuery += "GROUP BY worker.nif ";
strQuery += "GROUP BY worker.code ";
// Order by
strQuery += "ORDER BY worker.nif";
strQuery += "ORDER BY worker.code";
// Set parameters
Query query = getSession().createQuery(strQuery);
@ -183,8 +183,8 @@ public class WorkerDAO extends IntegrationEntityDAO<Worker>
if (endingDate != null) {
query.setParameter("endingDate", endingDate);
}
if (workerNifs != null && !workerNifs.isEmpty()) {
query.setParameterList("workerNifs", workerNifs);
if (workerCodes != null && !workerCodes.isEmpty()) {
query.setParameterList("workerCodes", workerCodes);
}
// Get result

View file

@ -107,18 +107,18 @@ public abstract class OrderElementTemplate extends BaseEntity implements
.getInitDate());
Days fromBeginningToEnd = daysBetween(order.getInitDate(), origin
.getDeadline());
beingBuilt.materialAssignments = copyMaterialAssignmentsFrom(beingBuilt, origin
.getMaterialAssignments());
beingBuilt.criterionRequirements = copyDirectCriterionRequirements(
beingBuilt, origin.getDirectCriterionRequirement());
beingBuilt.labels = new HashSet<Label>(origin.getLabels());
beingBuilt.qualityForms = origin.getQualityForms();
beingBuilt.advanceAssignmentTemplates = copyDirectAdvanceAssignments(
beingBuilt, origin.getDirectAdvanceAssignments());
beingBuilt.infoComponent = infoComponentCopied;
beingBuilt.schedulingStateType = origin.getSchedulingStateType();
beingBuilt.setMaterialAssignments(copyMaterialAssignmentsFrom(beingBuilt, origin
.getMaterialAssignments()));
beingBuilt.setCriterionRequirements(copyDirectCriterionRequirements(
beingBuilt, origin.getDirectCriterionRequirement()));
beingBuilt.addLabels(origin.getLabels());
beingBuilt.setQualityForms(origin.getQualityForms());
beingBuilt.setAdvanceAssignmentTemplates(copyDirectAdvanceAssignments(
beingBuilt, origin.getDirectAdvanceAssignments()));
beingBuilt.setInfoComponent(infoComponentCopied);
beingBuilt.setSchedulingStateType(origin.getSchedulingStateType());
assignDates(beingBuilt, fromBeginningToStart, fromBeginningToEnd);
beingBuilt.origin = origin;
beingBuilt.setOrigin(origin);
return create(beingBuilt);
}
@ -155,7 +155,7 @@ public abstract class OrderElementTemplate extends BaseEntity implements
}
public static <T extends OrderElementTemplate> T createNew(T beingBuilt) {
beingBuilt.infoComponent = new InfoComponent();
beingBuilt.setInfoComponent(new InfoComponent());
assignDates(beingBuilt, null, null);
return create(beingBuilt);
}
@ -314,6 +314,10 @@ public abstract class OrderElementTemplate extends BaseEntity implements
return schedulingStateType;
}
protected void setSchedulingStateType(SchedulingState.Type schedulingStateType) {
this.schedulingStateType = schedulingStateType;
}
public OrderLineGroupTemplate getParent() {
return parent;
}
@ -322,13 +326,17 @@ public abstract class OrderElementTemplate extends BaseEntity implements
this.parent = parent;
}
private InfoComponent getInfoComponent() {
protected InfoComponent getInfoComponent() {
if (infoComponent == null) {
infoComponent = new InfoComponent();
}
return infoComponent;
}
protected void setInfoComponent(InfoComponent infoComponent) {
this.infoComponent = infoComponent;
}
/**
* @return a description of the type or template this object is
*/
@ -388,6 +396,10 @@ public abstract class OrderElementTemplate extends BaseEntity implements
return Collections.unmodifiableSet(materialAssignments);
}
protected void setMaterialAssignments(Set<MaterialAssignmentTemplate> materialAssigments) {
this.materialAssignments = materialAssignments;
}
public void addMaterialAssignment(
MaterialAssignmentTemplate materialAssignment) {
Validate.notNull(materialAssignment);
@ -429,6 +441,11 @@ public abstract class OrderElementTemplate extends BaseEntity implements
this.labels.add(label);
}
public void addLabels(Collection<Label> labels){
Validate.notNull(labels);
this.labels.addAll(labels);
}
public void removeLabel(Label label) {
this.labels.remove(label);
}
@ -437,6 +454,10 @@ public abstract class OrderElementTemplate extends BaseEntity implements
return Collections.unmodifiableSet(qualityForms);
}
protected void setQualityForms(Set<QualityForm> qualityForms) {
this.qualityForms = qualityForms;
}
public void addQualityForm(QualityForm qualityForm) {
qualityForms.add(qualityForm);
}
@ -450,6 +471,11 @@ public abstract class OrderElementTemplate extends BaseEntity implements
return Collections.unmodifiableSet(advanceAssignmentTemplates);
}
protected void setAdvanceAssignmentTemplates(
Set<AdvanceAssignmentTemplate> advanceAssignmentTemplates) {
this.advanceAssignmentTemplates = advanceAssignmentTemplates;
}
public boolean isRoot() {
return getParent() == null;
}
@ -497,6 +523,10 @@ public abstract class OrderElementTemplate extends BaseEntity implements
return Collections.unmodifiableSet(criterionRequirements);
}
protected void setCriterionRequirements(Set<CriterionRequirement> criterionRequirements) {
this.criterionRequirements = criterionRequirements;
}
public abstract List<HoursGroup> getHoursGroups();
public abstract Integer getWorkHours();

View file

@ -120,7 +120,7 @@ public class OrderLineGroupTemplate extends OrderElementTemplate implements
OrderElementTemplate.create(beingBuilt, group);
List<OrderElementTemplate> result = buildChildrenTemplates(beingBuilt,
group.getChildren());
beingBuilt.children = result;
beingBuilt.setChildren(result);
beingBuilt.propagateIndirectCriterionRequirements();
return beingBuilt;
}
@ -134,7 +134,7 @@ public class OrderLineGroupTemplate extends OrderElementTemplate implements
* and it's needed to propagate its criteria while preserving the original
* value of 'valid' field in {@link IndirectCriterionRequirement}
*/
private void propagateIndirectCriterionRequirements() {
protected void propagateIndirectCriterionRequirements() {
propagateIndirectCriterionRequirementsForOrderLineGroup(this);
propagateIndirectCriterionRequirementsForOrderLines(this);
}
@ -217,6 +217,10 @@ public class OrderLineGroupTemplate extends OrderElementTemplate implements
return Collections.unmodifiableList(children);
}
protected void setChildren(List<OrderElementTemplate> children) {
this.children = children;
}
@Override
public ITreeParentNode<OrderElementTemplate> toContainer() {
return this;

View file

@ -59,4 +59,7 @@ public interface IWorkReportDAO extends IIntegrationEntityDAO<WorkReport> {
boolean isAnyPersonalTimesheetAlreadySaved();
List<WorkReport> findPersonalTimesheetsByResourceAndOrderElement(
Resource resource);
}

View file

@ -74,4 +74,7 @@ public interface IWorkReportLineDAO extends
Boolean isFinished(OrderElement orderElement);
List<WorkReportLine> findByOrderElementAndWorkReports(
OrderElement orderElement, List<WorkReport> workReports);
}

View file

@ -143,19 +143,9 @@ public class WorkReportDAO extends IntegrationEntityDAO<WorkReport>
@SuppressWarnings("unchecked")
public WorkReport getPersonalTimesheetWorkReport(Resource resource,
LocalDate date, PersonalTimesheetsPeriodicityEnum periodicity) {
WorkReportType workReportType;
try {
workReportType = workReportTypeDAO
.findUniqueByName(PredefinedWorkReportTypes.PERSONAL_TIMESHEETS
.getName());
} catch (NonUniqueResultException e) {
throw new RuntimeException(e);
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
Criteria criteria = getSession().createCriteria(WorkReport.class);
criteria.add(Restrictions.eq("workReportType", workReportType));
criteria.add(Restrictions.eq("workReportType",
getPersonalTimesheetsWorkReportType()));
List<WorkReport> personalTimesheets = criteria.add(
Restrictions.eq("resource", resource)).list();
@ -179,8 +169,7 @@ public class WorkReportDAO extends IntegrationEntityDAO<WorkReport>
return null;
}
@Override
public boolean isAnyPersonalTimesheetAlreadySaved() {
private WorkReportType getPersonalTimesheetsWorkReportType() {
WorkReportType workReportType;
try {
workReportType = workReportTypeDAO
@ -191,10 +180,29 @@ public class WorkReportDAO extends IntegrationEntityDAO<WorkReport>
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
return workReportType;
}
@Override
public boolean isAnyPersonalTimesheetAlreadySaved() {
WorkReportType workReportType = getPersonalTimesheetsWorkReportType();
Criteria criteria = getSession().createCriteria(WorkReport.class);
criteria.add(Restrictions.eq("workReportType", workReportType));
return criteria.list().isEmpty();
}
@Override
@SuppressWarnings("unchecked")
public List<WorkReport> findPersonalTimesheetsByResourceAndOrderElement(
Resource resource) {
Criteria criteria = getSession().createCriteria(WorkReport.class);
criteria.add(Restrictions.eq("workReportType",
getPersonalTimesheetsWorkReportType()));
criteria.add(Restrictions.eq("resource", resource));
return criteria.list();
}
}

View file

@ -199,4 +199,16 @@ public class WorkReportLineDAO extends IntegrationEntityDAO<WorkReportLine>
return criteria.uniqueResult() != null;
}
@SuppressWarnings("unchecked")
@Override
public List<WorkReportLine> findByOrderElementAndWorkReports(
OrderElement orderElement, List<WorkReport> workReports) {
Criteria criteria = getSession().createCriteria(WorkReportLine.class);
criteria.add(Restrictions.eq("orderElement", orderElement));
criteria.add(Restrictions.in("workReport", workReports));
return (List<WorkReportLine>) criteria.list();
}
}

View file

@ -155,4 +155,36 @@
columnDataType="BOOLEAN" />
</changeSet>
<changeSet id="update-status-values-in-order_table" author="mrego">
<comment>Updating status values in order_table</comment>
<update tableName="order_table">
<column name="state" value="8" />
<where>state='6'</where>
</update>
<update tableName="order_table">
<column name="state" value="7" />
<where>state='4'</where>
</update>
<update tableName="order_table">
<column name="state" value="6" />
<where>state='3'</where>
</update>
<update tableName="order_table">
<column name="state" value="4" />
<where>state='2'</where>
</update>
<update tableName="order_table">
<column name="state" value="3" />
<where>state='1'</where>
</update>
<update tableName="order_table">
<column name="state" value="2" />
<where>state='5'</where>
</update>
<update tableName="order_table">
<column name="state" value="1" />
<where>state='0'</where>
</update>
</changeSet>
</databaseChangeLog>

View file

@ -55,7 +55,8 @@
access="field"
cascade="all"
class="org.libreplan.business.orders.entities.OrderLineGroup"
index="idx_order_element_on_parent"/>
index="idx_order_element_on_parent"
lazy="false" />
<many-to-one name="template"
access="field"

View file

@ -48,8 +48,12 @@ public class ConcurrentModificationController extends GenericForwardComposer {
.info(
"an OptimistLockingFailureException caused a disruption to an user",
exception);
Executions.sendRedirect("/common/concurrent_modification.zul?back="
+ backURL);
if (Executions.getCurrent() != null) {
Executions.sendRedirect("/common/concurrent_modification.zul?back="
+ backURL);
} else {
LOG.warn("Impossible to do redirect due to OptimisticLockingFailureException because of Executions.getCurrent() is null");
}
}
private static String getBackURL() {

View file

@ -133,7 +133,7 @@ public interface IOrderModel extends IIntegrationEntityModel {
PlanningState getPlanningState();
boolean hasImputedExpenseSheets(OrderElement order);
boolean hasImputedExpenseSheetsThisOrAnyOfItsChildren(OrderElement order);
void removeAskedEndDate(EndDateCommunication endDate);
@ -145,4 +145,7 @@ public interface IOrderModel extends IIntegrationEntityModel {
boolean isAnyTaskWithConstraint(PositionConstraintType type);
boolean isOnlyChildAndParentAlreadyInUseByHoursOrExpenses(
OrderElement orderElement);
}

View file

@ -843,7 +843,7 @@ public class OrderCRUDController extends GenericForwardComposer {
}
private void remove(Order order) {
boolean hasImputedExpenseSheets = orderModel.hasImputedExpenseSheets(order);
boolean hasImputedExpenseSheets = orderModel.hasImputedExpenseSheetsThisOrAnyOfItsChildren(order);
if (hasImputedExpenseSheets) {
messagesForUser
.showMessage(

View file

@ -702,7 +702,8 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
@Override
public void remove(OrderElement element) {
boolean hasImputedExpenseSheets = orderModel.hasImputedExpenseSheets(element);
boolean hasImputedExpenseSheets = orderModel
.hasImputedExpenseSheetsThisOrAnyOfItsChildren(element);
if (hasImputedExpenseSheets) {
messagesForUser
.showMessage(
@ -719,10 +720,22 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
Level.ERROR,
_("You cannot remove the task \"{0}\" because it has work reported on it or any of its children",
element.getName()));
} else {
super.remove(element);
getRenderer().removeCodeTextbox(element);
return;
}
boolean onlyChildAndParentAlreadyInUseByHoursOrExpenses = orderModel
.isOnlyChildAndParentAlreadyInUseByHoursOrExpenses(element);
if (onlyChildAndParentAlreadyInUseByHoursOrExpenses) {
messagesForUser
.showMessage(
Level.ERROR,
_("You cannot remove the task \"{0}\" because it is the only child of its parent and its parent has tracked time or imputed expenses",
element.getName()));
return;
}
super.remove(element);
getRenderer().removeCodeTextbox(element);
}
@Override

View file

@ -869,12 +869,12 @@ public class OrderModel extends IntegrationEntityModel implements IOrderModel {
@Override
@Transactional(readOnly = true)
public boolean hasImputedExpenseSheets(OrderElement order) {
public boolean hasImputedExpenseSheetsThisOrAnyOfItsChildren(OrderElement order) {
if (order.isNewObject()) {
return false;
}
try {
return orderElementDAO.hasImputedExpenseSheet(order.getId());
return orderElementDAO.hasImputedExpenseSheetThisOrAnyOfItsChildren(order.getId());
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
@ -931,4 +931,24 @@ public class OrderModel extends IntegrationEntityModel implements IOrderModel {
return planningState.getRootTask().isAnyTaskWithConstraint(type);
}
@Override
@Transactional(readOnly = true)
public boolean isOnlyChildAndParentAlreadyInUseByHoursOrExpenses(
OrderElement orderElement) {
try {
OrderLineGroup parent = orderElement.getParent();
if (!parent.isOrder() && parent.getChildren().size() == 1) {
if (orderElementDAO.isAlreadyInUse(parent)) {
return true;
}
if (orderElementDAO.hasImputedExpenseSheet(parent.getId())) {
return true;
}
}
return false;
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1098,7 +1098,7 @@ _(
if (Arrays.asList(OrderStatusEnum.ACCEPTED,
OrderStatusEnum.OFFERED, OrderStatusEnum.STARTED,
OrderStatusEnum.SUBCONTRACTED_PENDING_ORDER).contains(
OrderStatusEnum.OUTSOURCED).contains(
state)) {
if (taskElement.getAssignedStatus() == "assigned") {
cssClass = "order-open-assigned";

View file

@ -160,7 +160,7 @@ public class CompanyPlanningModel implements ICompanyPlanningModel {
private static final EnumSet<OrderStatusEnum> STATUS_VISUALIZED = EnumSet
.of(OrderStatusEnum.ACCEPTED, OrderStatusEnum.OFFERED,
OrderStatusEnum.STARTED,
OrderStatusEnum.SUBCONTRACTED_PENDING_ORDER);
OrderStatusEnum.OUTSOURCED);
public void setPlanningControllerEntryPoints(
MultipleTabsPlannerController entryPoints) {

View file

@ -217,7 +217,10 @@ public class PlanningStateCreator {
public PlanningState retrieveOrCreate(Desktop desktop, Order order,
IActionsOnRetrieval onRetrieval) {
Object existent = desktop.getAttribute(ATTRIBUTE_NAME);
Object existent = null;
if (desktop != null) {
existent = desktop.getAttribute(ATTRIBUTE_NAME);
}
if (existent instanceof PlanningState) {
PlanningState result = (PlanningState) existent;
if (ObjectUtils.equals(order.getId(), result.getOrder().getId())) {
@ -230,7 +233,9 @@ public class PlanningStateCreator {
}
PlanningState result = createPlanning(reload(order));
result.onRetrieval();
desktop.setAttribute(ATTRIBUTE_NAME, result);
if (desktop != null) {
desktop.setAttribute(ATTRIBUTE_NAME, result);
}
return result;
}

View file

@ -316,6 +316,10 @@ public class EditTaskController extends GenericForwardComposer {
if (context.getRelativeTo() instanceof TaskComponent) {
((TaskComponent) context.getRelativeTo()).updateProperties();
((TaskComponent) context.getRelativeTo()).invalidate();
org.zkoss.ganttz.data.Task task = context.getMapper()
.findAssociatedBean(taskElement);
task.firePropertyChangeForTaskDates();
}
}
}

View file

@ -253,6 +253,10 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
if (node.isLeaf() && !node.isEmptyLeaf()) {
// Then a new container will be created
nameTextbox = getRenderer().getNameTextbox(node);
} else {
// select the parent row to add new children ASAP
tree.setSelectedItem(getRenderer().getTreeitemForNode(
newNode.getParent().getThis()));
}
} else {
getModel().addElement(name.getValue(), hours.getValue());
@ -1107,6 +1111,14 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
}
}
public Treeitem getTreeitemForNode(T node) {
Component cmp = hoursIntBoxByElement.get(node);
while (!(cmp instanceof Treeitem)) {
cmp = cmp.getParent();
}
return (Treeitem) cmp;
}
private Constraint getHoursConstraintFor(final T line) {
return new Constraint() {
@Override

View file

@ -28,12 +28,9 @@ import javax.annotation.Resource;
import org.joda.time.LocalDate;
import org.libreplan.business.advance.entities.AdvanceMeasurement;
import org.libreplan.business.advance.entities.DirectAdvanceAssignment;
import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.SumChargedEffort;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.web.common.Util;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
@ -75,21 +72,15 @@ public class MyTasksAreaController extends GenericForwardComposer {
Util.appendLabel(row, getProgress(orderElement));
Util.appendLabel(row, getEffort(orderElement));
Util.appendLabel(row, _("{0} h", orderElement.getEffortAsString()));
appendTimeTrackingButton(row, task);
}
private String getEffort(OrderElement orderElement) {
SumChargedEffort sumChargedEffort = orderElement.getSumChargedEffort();
EffortDuration effort = sumChargedEffort != null ? sumChargedEffort
.getTotalChargedEffort() : EffortDuration.zero();
return _("{0} h", effort.toFormattedString());
}
private String getProgress(OrderElement orderElement) {
AdvanceMeasurement lastAdvanceMeasurement = getLastAdvanceMeasurement(orderElement);
AdvanceMeasurement lastAdvanceMeasurement = orderElement
.getLastAdvanceMeasurement();
if (lastAdvanceMeasurement != null) {
return MessageFormat.format("[{0} %] ({1})",
lastAdvanceMeasurement.getValue(),
@ -98,17 +89,6 @@ public class MyTasksAreaController extends GenericForwardComposer {
return "";
}
private AdvanceMeasurement getLastAdvanceMeasurement(
OrderElement orderElement) {
DirectAdvanceAssignment advanceAssignment = orderElement
.getReportGlobalAdvanceAssignment();
if (advanceAssignment == null) {
return null;
}
return advanceAssignment
.getLastAdvanceMeasurement();
}
private void appendTimeTrackingButton(Row row, final Task task) {
EventListener trackTimeButtonListener = new EventListener() {
@Override

View file

@ -24,6 +24,7 @@ import java.util.List;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.users.entities.User;
import org.libreplan.web.UserUtil;
/**
* Utilities class for user dashboard window
@ -38,4 +39,12 @@ public class UserDashboardUtil {
return resource;
}
public static Resource getBoundResourceFromSession() {
User user = UserUtil.getUserFromSession();
if (user.isBound()) {
return user.getWorker();
}
return null;
}
}

View file

@ -0,0 +1,39 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.ws.boundusers.api;
import javax.ws.rs.core.Response;
/**
* Service for managing operations related with bound users.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface IBoundUserService {
TaskListDTO getTasks();
Response getTimesheetEntriesByTask(String taskCode);
Response importTimesheetEntries(PersonalTimesheetEntryListDTO dto);
}

View file

@ -0,0 +1,52 @@
/*
* 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.ws.boundusers.api;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.datatype.XMLGregorianCalendar;
/**
* DTO for an entry in a personal timesheet.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@XmlRootElement(name = "personal-timesheet-entry")
public class PersonalTimesheetEntryDTO {
@XmlAttribute
public String task;
@XmlAttribute(name = "date")
public XMLGregorianCalendar date;
@XmlAttribute
public String effort;
public PersonalTimesheetEntryDTO() {}
public PersonalTimesheetEntryDTO(String task, XMLGregorianCalendar date,
String effort) {
this.task = task;
this.date = date;
this.effort = effort;
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.ws.boundusers.api;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* DTO for a list of personal timesheet entries.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@XmlRootElement(name = "personal-timesheet-entry-list")
public class PersonalTimesheetEntryListDTO {
@XmlElement(name = "personal-timesheet-entry")
public List<PersonalTimesheetEntryDTO> entries = new ArrayList<PersonalTimesheetEntryDTO>();
public PersonalTimesheetEntryListDTO() {}
public PersonalTimesheetEntryListDTO(List<PersonalTimesheetEntryDTO> entries) {
this.entries = entries;
}
}

View file

@ -0,0 +1,82 @@
/*
* 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.ws.boundusers.api;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.datatype.XMLGregorianCalendar;
import org.libreplan.business.planner.entities.Task;
/**
* DTO for a {@link Task} entity.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@XmlRootElement(name = "task")
public class TaskDTO {
@XmlAttribute
public String name;
@XmlAttribute
public String code;
@XmlAttribute(name = "project-code")
public String projectCode;
@XmlAttribute(name = "project-name")
public String projectName;
@XmlAttribute(name = "start-date")
public XMLGregorianCalendar startDate;
@XmlAttribute(name = "end-date")
public XMLGregorianCalendar endDate;
@XmlAttribute(name = "progress-value")
public BigDecimal progressValue;
@XmlAttribute(name = "progress-date")
public XMLGregorianCalendar progressDate;
@XmlAttribute
public String effort;
public TaskDTO() {}
public TaskDTO(String name, String code, String projectCode,
String projectName, XMLGregorianCalendar startDate,
XMLGregorianCalendar endDate, BigDecimal progressValue,
XMLGregorianCalendar progressDate, String effort) {
this.name = name;
this.code = code;
this.projectCode = projectCode;
this.projectName = projectName;
this.startDate = startDate;
this.endDate = endDate;
this.progressValue = progressValue;
this.progressDate = progressDate;
this.effort = effort;
}
}

View file

@ -0,0 +1,47 @@
/*
* 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.ws.boundusers.api;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.libreplan.business.planner.entities.Task;
/**
* DTO for a list of {@link Task} entities.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@XmlRootElement(name = "task-list")
public class TaskListDTO {
@XmlElement(name = "task")
public List<TaskDTO> tasks = new ArrayList<TaskDTO>();
public TaskListDTO() {}
public TaskListDTO(List<TaskDTO> tasks) {
this.tasks = tasks;
}
}

View file

@ -0,0 +1,28 @@
/*
* 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/>.
*/
/**
* Specification of namespace for REST-based services.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@javax.xml.bind.annotation.XmlSchema(elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED, namespace = WSCommonGlobalNames.REST_NAMESPACE)
package org.libreplan.ws.boundusers.api;
import org.libreplan.ws.common.api.WSCommonGlobalNames;

View file

@ -0,0 +1,136 @@
/*
* 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.ws.boundusers.impl;
import java.util.Collections;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.joda.time.LocalDate;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.orders.daos.IOrderElementDAO;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.daos.IWorkReportDAO;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.entities.WorkReport;
import org.libreplan.business.workreports.entities.WorkReportLine;
import org.libreplan.web.users.dashboard.IMyTasksAreaModel;
import org.libreplan.web.users.dashboard.IPersonalTimesheetModel;
import org.libreplan.web.users.dashboard.UserDashboardUtil;
import org.libreplan.ws.boundusers.api.IBoundUserService;
import org.libreplan.ws.boundusers.api.PersonalTimesheetEntryDTO;
import org.libreplan.ws.boundusers.api.PersonalTimesheetEntryListDTO;
import org.libreplan.ws.boundusers.api.TaskListDTO;
import org.libreplan.ws.common.impl.DateConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* REST-based implementation of {@link IBoundUserService};
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Path("/bounduser/")
@Produces("application/xml")
@Service("boundUserServiceREST")
public class BoundUserServiceREST implements IBoundUserService {
@Autowired
private IMyTasksAreaModel myTasksAreaModel;
@Autowired
private IOrderElementDAO orderElementDAO;
@Autowired
private IWorkReportDAO workReportDAO;
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired
private IPersonalTimesheetModel personalTimesheetModel;
@Override
@GET
@Transactional(readOnly = true)
@Path("/mytasks/")
public TaskListDTO getTasks() {
return TaskConverter.toDTO(myTasksAreaModel.getTasks());
}
@Override
@GET
@Transactional(readOnly = true)
@Path("/timesheets/{task-code}/")
@SuppressWarnings("unchecked")
public Response getTimesheetEntriesByTask(
@PathParam("task-code") String taskCode) {
try {
OrderElement orderElement = orderElementDAO.findByCode(taskCode);
List<WorkReport> workReports = workReportDAO
.findPersonalTimesheetsByResourceAndOrderElement(UserDashboardUtil
.getBoundResourceFromSession());
List<WorkReportLine> workReportLines = workReportLineDAO
.findByOrderElementAndWorkReports(orderElement, workReports);
Collections.sort(workReportLines);
Collections.reverse(workReportLines);
PersonalTimesheetEntryListDTO dto = PersonalTimesheetEntryConverter
.toDTO(workReportLines);
return Response.ok(dto).build();
} catch (InstanceNotFoundException e) {
return Response.status(Status.NOT_FOUND).build();
}
}
@Override
@POST
@Transactional
@Path("/timesheets/")
public Response importTimesheetEntries(PersonalTimesheetEntryListDTO dto) {
try {
for (PersonalTimesheetEntryDTO each : dto.entries) {
LocalDate date = DateConverter.toLocalDate(each.date);
OrderElement orderElement = orderElementDAO
.findByCode(each.task);
EffortDuration effortDuration = EffortDuration
.parseFromFormattedString(each.effort);
personalTimesheetModel.initCreateOrEdit(date);
personalTimesheetModel.setEffortDuration(orderElement, date,
effortDuration);
personalTimesheetModel.save();
}
return Response.ok().build();
} catch (InstanceNotFoundException e) {
return Response.status(Status.NOT_FOUND).build();
}
}
}

View file

@ -0,0 +1,57 @@
/*
* 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.ws.boundusers.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.libreplan.business.workreports.entities.WorkReportLine;
import org.libreplan.ws.boundusers.api.PersonalTimesheetEntryDTO;
import org.libreplan.ws.boundusers.api.PersonalTimesheetEntryListDTO;
import org.libreplan.ws.common.impl.DateConverter;
/**
* Converter from/to {@link WorkReportLine} to/from DTOs.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public final class PersonalTimesheetEntryConverter {
private PersonalTimesheetEntryConverter() {
}
public final static PersonalTimesheetEntryDTO toDTO(
WorkReportLine workReportLine) {
return new PersonalTimesheetEntryDTO(workReportLine.getOrderElement()
.getCode(), DateConverter.toXMLGregorianCalendar(workReportLine
.getDate()), workReportLine.getEffort().toFormattedString());
}
public final static PersonalTimesheetEntryListDTO toDTO(
Collection<WorkReportLine> workReportLines) {
List<PersonalTimesheetEntryDTO> dtos = new ArrayList<PersonalTimesheetEntryDTO>();
for (WorkReportLine line : workReportLines) {
dtos.add(toDTO(line));
}
return new PersonalTimesheetEntryListDTO(dtos);
}
}

View file

@ -0,0 +1,74 @@
/*
* 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.ws.boundusers.impl;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
import org.libreplan.business.advance.entities.AdvanceMeasurement;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.ws.boundusers.api.TaskDTO;
import org.libreplan.ws.boundusers.api.TaskListDTO;
import org.libreplan.ws.common.impl.DateConverter;
/**
* Converter from/to {@link Task} related entities to/from DTOs.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public final class TaskConverter {
private TaskConverter() {
}
public final static TaskDTO toDTO(Task task) {
OrderElement orderElement = task.getOrderElement();
AdvanceMeasurement lastAdvanceMeasurement = orderElement
.getLastAdvanceMeasurement();
BigDecimal progressValue = null;
LocalDate progressDate = null;
if (lastAdvanceMeasurement != null) {
progressValue = lastAdvanceMeasurement.getValue();
progressDate = lastAdvanceMeasurement.getDate();
}
return new TaskDTO(task.getName(), orderElement.getCode(), orderElement
.getOrder().getCode(), orderElement.getOrder().getName(),
DateConverter.toXMLGregorianCalendar(task.getStartDate()),
DateConverter.toXMLGregorianCalendar(task.getEndDate()),
progressValue,
DateConverter.toXMLGregorianCalendar(progressDate),
orderElement.getEffortAsString());
}
public final static TaskListDTO toDTO(Collection<Task> tasks) {
List<TaskDTO> dtos = new ArrayList<TaskDTO>();
for (Task each : tasks) {
dtos.add(toDTO(each));
}
return new TaskListDTO(dtos);
}
}

View file

@ -0,0 +1,49 @@
/*
* 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.ws.common.api;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* DTO for representing any concurrent modification exception in the web
* services.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@XmlRootElement(name = "concurrent-modification-error")
public class ConcurrentModificationErrorDTO {
@XmlAttribute(name = "message")
public String message;
@XmlElement(name = "stack-trace")
public String stackTrace;
public ConcurrentModificationErrorDTO() {
}
public ConcurrentModificationErrorDTO(String message, String stackTrace) {
this.message = message;
this.stackTrace = stackTrace;
}
}

View file

@ -0,0 +1,43 @@
/*
* 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.ws.common.api;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
/**
* DTO for representing any basic error in the web services.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@XmlRootElement(name = "error")
public class ErrorDTO {
@XmlAttribute
public String message;
public ErrorDTO() {
}
public ErrorDTO(String message) {
this.message = message;
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.ws.common.impl;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.libreplan.ws.common.api.ConcurrentModificationErrorDTO;
import org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException;
import org.springframework.stereotype.Component;
/**
* Exception mapper for {@link HibernateOptimisticLockingFailureExceptionMapper}
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Provider
@Component("hibernateOptimisticLockingFailureException")
public class HibernateOptimisticLockingFailureExceptionMapper implements
ExceptionMapper<HibernateOptimisticLockingFailureException> {
public Response toResponse(HibernateOptimisticLockingFailureException e) {
return Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ConcurrentModificationErrorDTO(
e.getMessage(), Util.getStackTrace(e)))
.type("application/xml").build();
}
}

View file

@ -39,4 +39,6 @@ public interface IOrderElementService {
Response getOrderElement(String code);
Response removeOrderElement(String code);
}

View file

@ -21,18 +21,28 @@
package org.libreplan.ws.orders.impl;
import java.util.UUID;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.libreplan.business.common.daos.IIntegrationEntityDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
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.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLineGroup;
import org.libreplan.web.orders.IOrderModel;
import org.libreplan.ws.common.api.ErrorDTO;
import org.libreplan.ws.common.api.InstanceConstraintViolationsListDTO;
import org.libreplan.ws.common.api.OrderDTO;
import org.libreplan.ws.common.impl.ConfigurationOrderElementConverter;
@ -60,6 +70,12 @@ public class OrderElementServiceREST extends
@Autowired
private IOrderDAO orderDAO;
@Autowired
private IOrderElementDAO orderElementDAO;
@Autowired
private IOrderModel orderModel;
@Override
@GET
@Transactional(readOnly = true)
@ -108,4 +124,95 @@ public class OrderElementServiceREST extends
return getDTOByCode(code);
}
@Override
@DELETE
@Path("/{code}/")
@Transactional
public Response removeOrderElement(@PathParam("code") String code) {
try {
OrderElement orderElement = orderElementDAO.findByCode(code);
String errorMessage = checkRemovalValidation(orderElement);
if (errorMessage != null) {
return Response.status(Status.FORBIDDEN)
.entity(new ErrorDTO(errorMessage)).build();
}
if (orderElement.isOrder()) {
orderModel.remove((Order) orderElement);
} else {
Order order = orderDAO.loadOrderAvoidingProxyFor(orderElement);
orderModel.initEdit(order, null);
order = orderModel.getOrder();
orderElement = findOrderElement(order, orderElement.getId());
OrderLineGroup parent = orderElement.getParent();
parent.remove(orderElement);
orderElement.detachFromParent();
if (!parent.isOrder() && parent.getChildren().isEmpty()) {
OrderElement newElement = parent.toLeaf();
if (!order.isCodeAutogenerated()) {
newElement.setCode(UUID.randomUUID().toString());
}
parent.getParent().replace(parent, newElement);
parent.detachFromParent();
}
orderModel.save();
}
return Response.ok().build();
} catch (InstanceNotFoundException e) {
return Response.status(Status.NOT_FOUND).build();
}
}
private String checkRemovalValidation(OrderElement orderElement) {
try {
if (orderElementDAO
.isAlreadyInUseThisOrAnyOfItsChildren(orderElement)) {
return "You cannot remove the order element '"
+ orderElement.getName()
+ "' because it or any of its children have tracked time in some work report";
}
if (orderElementDAO.hasImputedExpenseSheetThisOrAnyOfItsChildren(orderElement.getId())) {
return "You cannot remove the order element '"
+ orderElement.getName()
+ "' because it or any of its children have tracked expenses in some expenses sheet";
}
OrderLineGroup parent = orderElement.getParent();
if (!parent.isOrder() && parent.getChildren().size() == 1) {
if (orderElementDAO.isAlreadyInUse(parent)) {
return "You cannot remove the order element '"
+ orderElement.getName()
+ "' because it is the only child of its parent and its parent has tracked time in some work report";
}
if (orderElementDAO.hasImputedExpenseSheet(parent.getId())) {
return "You cannot remove the order element '"
+ orderElement.getName()
+ "' because it is the only child of its parent and its parent has tracked expenses in some expenses sheet";
}
}
return null;
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
}
private OrderElement findOrderElement(OrderElement orderElement, Long id) {
if (orderElement.getId().equals(id)) {
return orderElement;
}
for (OrderElement child : orderElement.getChildren()) {
OrderElement found = findOrderElement(child, id);
if (found != null) {
return found;
}
}
return null;
}
}

View file

@ -87,13 +87,13 @@ public class ResourceHoursServiceREST implements IResourceHoursService {
throw new RuntimeException(e);
}
List<String> workerNifs = null;
List<String> workerCodes = null;
if (resourceCode != null) {
workerNifs = Arrays.asList(resourceCode);
workerCodes = Arrays.asList(resourceCode);
}
List<Object[]> hoursPerWorker = workerDAO
.getWorkingHoursGroupedPerWorker(workerNifs, startingDate,
.getWorkingHoursGroupedPerWorker(workerCodes, startingDate,
endingDate);
for (Object[] pair : hoursPerWorker) {

View file

@ -323,7 +323,7 @@ public class SubcontractServiceREST implements ISubcontractService {
order.setCode(code);
generateCodes(order);
order.setState(OrderStatusEnum.SUBCONTRACTED_PENDING_ORDER);
order.setState(OrderStatusEnum.OUTSOURCED);
if (subcontractedTaskDataDTO.workDescription != null) {
order.setName(subcontractedTaskDataDTO.workDescription);

View file

@ -73,11 +73,13 @@
<ref bean="materialServiceREST"/>
<ref bean="unitTypeServiceREST"/>
<ref bean="expenseSheetServiceREST"/>
<ref bean="boundUserServiceREST"/>
</jaxrs:serviceBeans>
<jaxrs:providers>
<ref bean="runtimeExceptionMapper" />
<ref bean="instanceNotFoundExceptionMapper" />
<ref bean="incompatibleTypeExceptionMapper" />
<ref bean="hibernateOptimisticLockingFailureException" />
</jaxrs:providers>
<!-- FIXME: in root pom.xml, enable CXF logging on development and
disable it in production.

View file

@ -13,6 +13,12 @@
entry-point-ref="customAuthenticationEntryPoint">
<!-- Web services -->
<intercept-url pattern="/ws/rest/bounduser/**"
access="ROLE_BOUND_USER"
method="GET" />
<intercept-url pattern="/ws/rest/bounduser/**"
access="ROLE_BOUND_USER"
method="POST" />
<intercept-url pattern="/ws/rest/subcontracting/**"
access="ROLE_WS_SUBCONTRACTING"
method="GET" />

View file

@ -45,6 +45,8 @@ import java.util.SortedSet;
import java.util.UUID;
import javax.annotation.Resource;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.hibernate.SessionFactory;
import org.joda.time.LocalDate;
@ -536,6 +538,18 @@ public class OrderElementServiceTest {
public void validOrderWithOrderLineGroup() {
String code = UUID.randomUUID().toString();
OrderDTO orderDTO = createOrderDTOWithChildren(code);
OrderListDTO orderListDTO = createOrderListDTO(orderDTO);
List<InstanceConstraintViolationsDTO> instanceConstraintViolationsList = orderElementService
.addOrders(orderListDTO).instanceConstraintViolationsList;
assertTrue(instanceConstraintViolationsList.toString(),
instanceConstraintViolationsList.size() == 0);
checkIfExistsByCodeInAnotherTransaction(code);
}
private OrderDTO createOrderDTOWithChildren(String code) {
OrderDTO orderDTO = new OrderDTO();
orderDTO.name = "Order name " + UUID.randomUUID().toString();
orderDTO.code = code;
@ -557,6 +571,14 @@ public class OrderElementServiceTest {
orderLineGroupDTO.children.add(orderLineDTO);
orderDTO.children.add(orderLineGroupDTO);
return orderDTO;
}
@Test
public void removeOrderElement() {
final String code = UUID.randomUUID().toString();
final OrderDTO orderDTO = createOrderDTOWithChildren(code);
OrderListDTO orderListDTO = createOrderListDTO(orderDTO);
List<InstanceConstraintViolationsDTO> instanceConstraintViolationsList = orderElementService
@ -565,6 +587,32 @@ public class OrderElementServiceTest {
instanceConstraintViolationsList.size() == 0);
checkIfExistsByCodeInAnotherTransaction(code);
transactionService.runOnAnotherTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
String codeToRemove = orderDTO.children.get(0).code;
Response response = orderElementService
.removeOrderElement(codeToRemove);
assertThat(response.getStatus(),
equalTo(Status.OK.getStatusCode()));
try {
orderElementDAO.findByCode(codeToRemove);
} catch (InstanceNotFoundException e) {
assertTrue(true);
}
try {
OrderElement order = orderElementDAO.findByCode(code);
assertTrue(order.getChildren().isEmpty());
} catch (InstanceNotFoundException e) {
fail();
}
return null;
}
});
}
@Test

View file

@ -205,7 +205,7 @@ public class SubcontractServiceTest {
assertNotNull(order.getExternalCode());
assertThat(order.getExternalCode(), equalTo(orderLineCode));
assertThat(order.getState(),
equalTo(OrderStatusEnum.SUBCONTRACTED_PENDING_ORDER));
equalTo(OrderStatusEnum.OUTSOURCED));
assertThat(order.getWorkHours(), equalTo(0));
assertThat(order.getCustomer().getId(),
equalTo(externalCompany.getId()));

View file

@ -505,7 +505,7 @@
</exclusion>
</exclusions>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
@ -518,17 +518,18 @@
</exclusions>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.5.4</version>
<version>1.7.1</version>
</dependency>
<!-- AspectJ (required by Spring Security) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>

View file

@ -47,17 +47,20 @@ methods with the following meaning:
* If it already exists: Update entity with new data.
* If it does not exist: Add the new entity.
These means that delete is not allowed from web services, in that way only new
info could be added or updated. This is because of entities are related with
others and remove operation could be dangerous. Then if necessary, the
recommendation would be add a field to disable such entity.
These means that delete is not allowed for all the entities in the web services,
in that way only new info could be added or updated. This is because of entities
are related with others and remove operation could be dangerous. Anyway for some
specific entities the delete operation has been implemented.
Requirements
------------
These scripts are written in bash so you need to be running a bash terminal to
use them.
use them. And they use cURL to do the HTTP requests, you can install it with the
following command in Debian based distributions::
# apt-get install curl
Moreover, it is recommended to have Tidy available in your system. You can
install it with the following command in Debian based distributions::
@ -205,11 +208,11 @@ For each entity there are the following methods:
* No parameters
* URL: ``/ws/rest/<service-path>/``
* Remove entity (only available for work reports):
* Remove entity (only available for work reports and order elements):
* HTTP method: ``DELETE``
* Parameter: ``entity-code``
* URL: ``/ws/rest/workreports/<entity-code>/``
* URL: ``/ws/rest/<service-path>/<entity-code>/``
* Special URL for work report lines:
``/ws/rest/workreports/line/<entity-code>/``

View file

@ -0,0 +1,42 @@
#!/bin/sh
. ./rest-common-env.sh
printf "BOUND USER\n"
printf "Username: "
read loginName
printf "Password: "
read password
file=$1
if [ "$1" = "--prod" ]; then
baseServiceURL=$PRODUCTION_BASE_SERVICE_URL
certificate=$PRODUCTION_CERTIFICATE
file=$2
elif [ "$1" = "--dev" ]; then
baseServiceURL=$DEVELOPMENT_BASE_SERVICE_URL
certificate=$DEVELOPMENT_CERTIFICATE
file=$2
else
baseServiceURL=$DEMO_BASE_SERVICE_URL
certificate=$DEMO_CERTIFICATE
fi
if [ "$file" = "" ]; then
printf "Missing file\n" 1>&2
exit 1
fi
authorization=`echo -n "$loginName:$password" | base64`
result=`curl -sv -X POST $certificate -d @$file \
--header "Content-type: application/xml" \
--header "Authorization: Basic $authorization" \
$baseServiceURL/bounduser/timesheets/`
if hash tidy &> /dev/null; then
echo $result | tidy -xml -i -q -utf8
else
echo $result
fi

View file

@ -0,0 +1,31 @@
#!/bin/sh
. ./rest-common-env.sh
printf "BOUND USER\n"
printf "Username: "
read loginName
printf "Password: "
read password
if [ "$1" = "--prod" ]; then
baseServiceURL=$PRODUCTION_BASE_SERVICE_URL
certificate=$PRODUCTION_CERTIFICATE
elif [ "$1" = "--dev" ]; then
baseServiceURL=$DEVELOPMENT_BASE_SERVICE_URL
certificate=$DEVELOPMENT_CERTIFICATE
else
baseServiceURL=$DEMO_BASE_SERVICE_URL
certificate=$DEMO_CERTIFICATE
fi
authorization=`echo -n "$loginName:$password" | base64`
result=`curl -sv -X GET $certificate --header "Authorization: Basic $authorization" \
$baseServiceURL/bounduser/mytasks`
if hash tidy &> /dev/null; then
echo $result | tidy -xml -i -q -utf8
else
echo $result
fi

View file

@ -0,0 +1,40 @@
#!/bin/sh
. ./rest-common-env.sh
printf "BOUND USER\n"
printf "Username: "
read loginName
printf "Password: "
read password
task=$1
if [ "$1" = "--prod" ]; then
baseServiceURL=$PRODUCTION_BASE_SERVICE_URL
certificate=$PRODUCTION_CERTIFICATE
task=$2
elif [ "$1" = "--dev" ]; then
baseServiceURL=$DEVELOPMENT_BASE_SERVICE_URL
certificate=$DEVELOPMENT_CERTIFICATE
task=$2
else
baseServiceURL=$DEMO_BASE_SERVICE_URL
certificate=$DEMO_CERTIFICATE
fi
if [ "$task" = "" ]; then
printf "Missing task\n" 1>&2
exit 1
fi
authorization=`echo -n "$loginName:$password" | base64`
result=`curl -sv -X GET $certificate --header "Authorization: Basic $authorization" \
$baseServiceURL/bounduser/timesheets/$task`
if hash tidy &> /dev/null; then
echo $result | tidy -xml -i -q -utf8
else
echo $result
fi

View file

@ -7,19 +7,21 @@ read loginName
printf "Password: "
read password
if [ "$3" = "--prod" ]; then
file=$2
if [ "$2" = "--prod" ]; then
baseServiceURL=$PRODUCTION_BASE_SERVICE_URL
certificate=$PRODUCTION_CERTIFICATE
elif [ "$3" = "--dev" ]; then
baseServiceURL=$DEVELOPMENT_BASE_SERVICE_URL
certificate=$DEVELOPMENT_CERTIFICATE
file=$3
elif [ "$2" = "--dev" ]; then
baseServiceURL=$DEVELOPMENT_BASE_SERVICE_URL
certificate=$DEVELOPMENT_CERTIFICATE
file=$3
else
baseServiceURL=$DEMO_BASE_SERVICE_URL
certificate=$DEMO_CERTIFICATE
baseServiceURL=$DEMO_BASE_SERVICE_URL
certificate=$DEMO_CERTIFICATE
fi
file=$2
if [ "$file" = "" ]; then
printf "Missing file\n" 1>&2
exit 1

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<personal-timesheet-entry-list xmlns="http://rest.ws.libreplan.org">
<personal-timesheet-entry effort="8" date="2012-11-08" task="ORDER0001-0001"/>
</personal-timesheet-entry-list>

View file

@ -0,0 +1,3 @@
#!/bin/sh
./remove.sh orderelements $*

View file

@ -1,34 +1,3 @@
#!/bin/sh
. ./rest-common-env.sh
printf "Username: "
read loginName
printf "Password: "
read password
code=$1
if [ "$1" = "--prod" ]; then
baseServiceURL=$PRODUCTION_BASE_SERVICE_URL
certificate=$PRODUCTION_CERTIFICATE
code=$2
elif [ "$1" = "--dev" ]; then
baseServiceURL=$DEVELOPMENT_BASE_SERVICE_URL
certificate=$DEVELOPMENT_CERTIFICATE
code=$2
else
baseServiceURL=$DEMO_BASE_SERVICE_URL
certificate=$DEMO_CERTIFICATE
fi
authorization=`echo -n "$loginName:$password" | base64`
result=`curl -sv -X DELETE $certificate --header "Authorization: Basic $authorization" \
$baseServiceURL/workreports/line/$code`
if hash tidy &> /dev/null; then
echo $result | tidy -xml -i -q -utf8
else
echo $result
fi
./remove.sh workreports/line $*

View file

@ -1,34 +1,3 @@
#!/bin/sh
. ./rest-common-env.sh
printf "Username: "
read loginName
printf "Password: "
read password
code=$1
if [ "$1" = "--prod" ]; then
baseServiceURL=$PRODUCTION_BASE_SERVICE_URL
certificate=$PRODUCTION_CERTIFICATE
code=$2
elif [ "$1" = "--dev" ]; then
baseServiceURL=$DEVELOPMENT_BASE_SERVICE_URL
certificate=$DEVELOPMENT_CERTIFICATE
code=$2
else
baseServiceURL=$DEMO_BASE_SERVICE_URL
certificate=$DEMO_CERTIFICATE
fi
authorization=`echo -n "$loginName:$password" | base64`
result=`curl -sv -X DELETE $certificate --header "Authorization: Basic $authorization" \
$baseServiceURL/workreports/$code`
if hash tidy &> /dev/null; then
echo $result | tidy -xml -i -q -utf8
else
echo $result
fi
./remove.sh workreports $*

34
scripts/rest-clients/remove.sh Executable file
View file

@ -0,0 +1,34 @@
#!/bin/sh
. ./rest-common-env.sh
printf "Username: "
read loginName
printf "Password: "
read password
code=$2
if [ "$2" = "--prod" ]; then
baseServiceURL=$PRODUCTION_BASE_SERVICE_URL
certificate=$PRODUCTION_CERTIFICATE
code=$3
elif [ "$2" = "--dev" ]; then
baseServiceURL=$DEVELOPMENT_BASE_SERVICE_URL
certificate=$DEVELOPMENT_CERTIFICATE
code=$3
else
baseServiceURL=$DEMO_BASE_SERVICE_URL
certificate=$DEMO_CERTIFICATE
fi
authorization=`echo -n "$loginName:$password" | base64`
result=`curl -sv -X DELETE $certificate --header "Authorization: Basic $authorization" \
$baseServiceURL/$1/$code`
if hash tidy &> /dev/null; then
echo $result | tidy -xml -i -q -utf8
else
echo $result
fi