/*
* 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.business.workreports.entities;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import javax.validation.Valid;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import org.joda.time.Seconds;
import org.libreplan.business.common.IntegrationEntity;
import org.libreplan.business.common.Registry;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.labels.entities.Label;
import org.libreplan.business.labels.entities.LabelType;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.valueobjects.DescriptionField;
import org.libreplan.business.workreports.valueobjects.DescriptionValue;
/**
* Note: this class has a natural ordering that is inconsistent with equals.
*
* @author Diego Pino García <dpino@igalia.com>
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
*/
public class WorkReportLine extends IntegrationEntity implements Comparable<WorkReportLine>, IWorkReportsElements {
private EffortDuration effort;
private Date date;
private LocalTime clockStart;
private LocalTime clockFinish;
private Resource resource;
private OrderElement orderElement;
private Set<Label> labels = new HashSet<>();
private Set<DescriptionValue> descriptionValues = new HashSet<>();
private WorkReport workReport;
private TypeOfWorkHours typeOfWorkHours;
private Boolean finished = false;
/**
* Constructor for hibernate. Do not use!
*/
public WorkReportLine() {
}
public WorkReportLine(WorkReport workReport) {
this.setWorkReport(workReport);
}
public static WorkReportLine create(WorkReport workReport) {
return create(new WorkReportLine(workReport));
}
@NotNull(message = "effort not specified")
public EffortDuration getEffort() {
return effort;
}
public void setEffort(EffortDuration effort) {
this.effort = effort;
if ( (workReport != null) &&
(workReport.getWorkReportType() != null) &&
(workReport.getWorkReportType().getHoursManagement()
.equals(HoursManagementEnum.HOURS_CALCULATED_BY_CLOCK)) ) {
this.effort = getDiferenceBetweenTimeStartAndFinish();
}
}
public LocalTime getClockFinish() {
return clockFinish;
}
public void setClockFinish(Date clockFinish) {
if (clockFinish != null) {
setClockFinish(LocalTime.fromDateFields(clockFinish));
}
}
public void setClockFinish(LocalTime clockFinish) {
this.clockFinish = clockFinish;
updateEffort();
}
public LocalTime getClockStart() {
return clockStart;
}
public void setClockStart(Date clockStart) {
if (clockStart != null) {
setClockStart(LocalTime.fromDateFields(clockStart));
}
}
public void setClockStart(LocalTime clockStart) {
this.clockStart = clockStart;
updateEffort();
}
@Override
@NotNull(message = "date not specified")
public Date getDate() {
return date;
}
public LocalDate getLocalDate() {
if (getDate() == null) {
return null;
}
return LocalDate.fromDateFields(getDate());
}
@Override
public void setDate(Date date) {
this.date = date;
if ( (workReport != null) &&
(workReport.getWorkReportType() != null) &&
workReport.getWorkReportType().getDateIsSharedByLines() ) {
this.date = workReport.getDate();
}
}
@Override
@NotNull(message = "resource not specified")
public Resource getResource() {
return resource;
}
@Override
public void setResource(Resource resource) {
this.resource = resource;
if ( (workReport != null) &&
(workReport.getWorkReportType() != null) &&
workReport.getWorkReportType().getResourceIsSharedInLines() ) {
this.resource = workReport.getResource();
}
}
@Override
@NotNull(message = "task not specified")
public OrderElement getOrderElement() {
return orderElement;
}
@Override
public void setOrderElement(OrderElement orderElement) {
this.orderElement = orderElement;
if ( (workReport != null) &&
(workReport.getWorkReportType() != null) &&
workReport.getWorkReportType().getOrderElementIsSharedInLines() ) {
this.orderElement = workReport.getOrderElement();
}
}
@Override
public Set<Label> getLabels() {
return labels;
}
@Override
public void setLabels(Set<Label> labels) {
this.labels = labels;
}
@NotNull(message = "timesheet not specified")
public WorkReport getWorkReport() {
return workReport;
}
private void setWorkReport(WorkReport workReport) {
this.workReport = workReport;
// Update and copy the fields and label for each line
updateItsFieldsAndLabels();
// Copy the required fields if these are shared by lines
updatesAllSharedDataByLines();
// Update calculated effort
updateEffort();
}
@Override
@Valid
public Set<DescriptionValue> getDescriptionValues() {
return descriptionValues;
}
@Override
public void setDescriptionValues(Set<DescriptionValue> descriptionValues) {
this.descriptionValues = descriptionValues;
}
@NotNull(message = "type of work hours not specified")
public TypeOfWorkHours getTypeOfWorkHours() {
return typeOfWorkHours;
}
public void setTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) {
this.typeOfWorkHours = typeOfWorkHours;
}
@Override
public int compareTo(WorkReportLine workReportLine) {
if (date != null) {
return date.compareTo(workReportLine.getDate());
}
return -1;
}
@AssertTrue(message = "closckStart:the clockStart must be not null if number of hours is calcultate by clock")
public boolean isClockStartMustBeNotNullIfIsCalculatedByClockConstraint() {
return !firstLevelValidationsPassed() ||
!workReport.getWorkReportType().getHoursManagement()
.equals(HoursManagementEnum.HOURS_CALCULATED_BY_CLOCK) ||
(getClockStart() != null);
}
@AssertTrue(message = "clock finish cannot be empty if number of hours is calcultate by clock")
public boolean isClockFinishMustBeNotNullIfIsCalculatedByClockConstraint() {
return !firstLevelValidationsPassed() ||
!workReport.getWorkReportType().getHoursManagement()
.equals(HoursManagementEnum.HOURS_CALCULATED_BY_CLOCK) ||
(getClockFinish() != null);
}
@AssertTrue(message = "Start hour cannot be greater than finish hour")
public boolean isCannotBeHigherConstraint() {
return !firstLevelValidationsPassed() ||
!workReport.getWorkReportType().getHoursManagement()
.equals(HoursManagementEnum.HOURS_CALCULATED_BY_CLOCK) ||
checkCannotBeHigher(this.clockStart, this.clockFinish);
}
public boolean checkCannotBeHigher(LocalTime starting, LocalTime ending) {
return !((ending != null) && (starting != null) && (starting.compareTo(ending) > 0));
}
void updateItsFieldsAndLabels() {
if (workReport != null) {
assignItsLabels(workReport.getWorkReportType());
assignItsDescriptionValues(workReport.getWorkReportType());
}
}
private void assignItsLabels(WorkReportType workReportType) {
Set<Label> updatedLabels = new HashSet<>();
if (workReportType != null) {
for (WorkReportLabelTypeAssignment labelTypeAssignment : workReportType.getLineLabels()) {
Label label = getLabelBy(labelTypeAssignment);
if (label != null) {
updatedLabels.add(label);
} else {
updatedLabels.add(labelTypeAssignment.getDefaultLabel());
}
}
this.labels = updatedLabels;
}
}
private Label getLabelBy(WorkReportLabelTypeAssignment labelTypeAssignment) {
LabelType type = labelTypeAssignment.getLabelType();
for (Label label : labels) {
if (label.getType().getId().equals(type.getId())) {
return label;
}
}
return null;
}
private void assignItsDescriptionValues(WorkReportType workReportType) {
Set<DescriptionValue> updatedDescriptionValues = new HashSet<>();
if (workReportType != null) {
for (DescriptionField descriptionField : workReportType.getLineFields()) {
DescriptionValue descriptionValue;
try {
descriptionValue = this.getDescriptionValueByFieldName(descriptionField.getFieldName());
} catch (InstanceNotFoundException e) {
descriptionValue = DescriptionValue.create(descriptionField.getFieldName(), null);
}
updatedDescriptionValues.add(descriptionValue);
}
this.descriptionValues = updatedDescriptionValues;
}
}
void updatesAllSharedDataByLines() {
// Copy the required fields if these are shared by lines
updateSharedDateByLines();
updateSharedResourceByLines();
updateSharedOrderElementByLines();
}
void updateSharedDateByLines() {
if ( (workReport != null) && (workReport.getWorkReportType() != null) &&
(workReport.getWorkReportType().getDateIsSharedByLines()) ) {
setDate(workReport.getDate());
}
}
void updateSharedResourceByLines() {
if ( (workReport != null) &&
(workReport.getWorkReportType() != null) &&
(workReport.getWorkReportType().getResourceIsSharedInLines()) ) {
setResource(workReport.getResource());
}
}
void updateSharedOrderElementByLines() {
if ( (workReport != null) &&
(workReport.getWorkReportType() != null) &&
(workReport.getWorkReportType().getOrderElementIsSharedInLines()) ) {
setOrderElement(workReport.getOrderElement());
}
}
private void updateEffort() {
if ( (workReport != null) &&
(workReport.getWorkReportType() != null) &&
workReport.getWorkReportType().getHoursManagement()
.equals(HoursManagementEnum.HOURS_CALCULATED_BY_CLOCK) ) {
setEffort(getDiferenceBetweenTimeStartAndFinish());
}
}
private EffortDuration getDiferenceBetweenTimeStartAndFinish() {
return (clockStart != null) && (clockFinish != null) ?
EffortDuration.seconds(Seconds.secondsBetween(clockStart, clockFinish).getSeconds()) : null;
}
@Override
protected IWorkReportLineDAO getIntegrationEntityDAO() {
return Registry.getWorkReportLineDAO();
}
@AssertTrue(message = "fields should match with timesheet data if are shared by lines")
public boolean isFieldsMatchWithWorkReportIfAreSharedByLinesConstraint() {
if (!firstLevelValidationsPassed()) {
return true;
}
if (workReport.getWorkReportType().getDateIsSharedByLines()) {
if (!workReport.getDate().equals(date)) {
return false;
}
}
if (workReport.getWorkReportType().getOrderElementIsSharedInLines()) {
if (!workReport.getOrderElement().getId().equals(
orderElement.getId())) {
return false;
}
}
if (workReport.getWorkReportType().getResourceIsSharedInLines()) {
if (!workReport.getResource().getId().equals(resource.getId())) {
return false;
}
}
return true;
}
@AssertTrue(message = "Number of hours is not properly calculated according to start date and end date")
public boolean isHoursCalculatedByClockConstraint() {
if (!firstLevelValidationsPassed()) {
return true;
}
if (workReport.getWorkReportType().getHoursManagement().equals(HoursManagementEnum.HOURS_CALCULATED_BY_CLOCK)) {
if (getDiferenceBetweenTimeStartAndFinish().compareTo(effort) != 0) {
return false;
}
}
return true;
}
private boolean firstLevelValidationsPassed() {
return (workReport != null) &&
(typeOfWorkHours != null) &&
(effort != null) &&
(date != null) &&
(resource != null) &&
(orderElement != null);
}
@AssertTrue(message = "label type: the timesheet has not assigned this label type")
public boolean isAssignedLabelTypesConstraint() {
if (this.workReport == null || this.workReport.getWorkReportType() == null) {
return true;
}
if (this.workReport.getWorkReportType().getLineLabels().size() != this.labels.size()) {
return false;
}
for (WorkReportLabelTypeAssignment typeAssignment : this.workReport.getWorkReportType().getLineLabels()) {
try {
getLabelByType(typeAssignment.getLabelType());
} catch (InstanceNotFoundException e) {
return false;
}
}
return true;
}
@AssertTrue(message = "description value: the timesheet has not assigned the description field")
public boolean isAssignedDescriptionValuesConstraint() {
if (this.workReport == null || this.workReport.getWorkReportType() == null) {
return true;
}
if (this.workReport.getWorkReportType().getLineFields().size() > this.descriptionValues.size()) {
return false;
}
for (DescriptionField field : this.workReport.getWorkReportType().getLineFields()) {
try {
getDescriptionValueByFieldName(field.getFieldName());
} catch (InstanceNotFoundException e) {
return false;
}
}
return true;
}
@AssertTrue(message = "there are repeated description values in the timesheet lines")
public boolean isAssignedRepeatedDescriptionValuesConstraint() {
Set<String> textFields = new HashSet<>();
for (DescriptionValue v : this.descriptionValues) {
String name = v.getFieldName();
if (!StringUtils.isBlank(name)) {
if (textFields.contains(name.toLowerCase())) {
return false;
} else {
textFields.add(name.toLowerCase());
}
}
}
return true;
}
public DescriptionValue getDescriptionValueByFieldName(String fieldName) throws InstanceNotFoundException {
if (StringUtils.isBlank(fieldName)) {
throw new InstanceNotFoundException(fieldName, DescriptionValue.class.getName());
}
for (DescriptionValue v : this.descriptionValues) {
if (v.getFieldName().equalsIgnoreCase(StringUtils.trim(fieldName))) {
return v;
}
}
throw new InstanceNotFoundException(fieldName, DescriptionValue.class.getName());
}
public Label getLabelByType(LabelType type) throws InstanceNotFoundException {
if (type == null) {
throw new InstanceNotFoundException(type, LabelType.class.getName());
}
for (Label l : this.labels) {
if (l.getType().getId().equals(type.getId())) {
return l;
}
}
throw new InstanceNotFoundException(type, LabelType.class.getName());
}
@Override
public void setCodeAutogenerated(Boolean codeAutogenerated) {
// Do nothing
}
@Override
public Boolean isCodeAutogenerated() {
return getWorkReport() != null ? getWorkReport().isCodeAutogenerated() : false;
}
@NotNull(message = "finished not specified")
public Boolean isFinished() {
return finished;
}
public void setFinished(Boolean finished) {
this.finished = finished;
}
@AssertTrue(message = "there is a timesheet line in another work report marking as finished the same task")
public boolean isOrderElementFinishedInAnotherWorkReportConstraint() {
if (!finished) {
return true;
}
List<WorkReportLine> lines = Registry
.getWorkReportLineDAO()
.findFinishedByOrderElementNotInWorkReportAnotherTransaction(orderElement, workReport);
return lines.isEmpty();
}
}