/*
* This file is part of LibrePlan
*
* Copyright (C) 2013 St. Antoniusziekenhuis
*
* 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.importers;
import static org.libreplan.web.I18nHelper._;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.NonUniqueResultException;
import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.common.entities.PredefinedConnectorProperties;
import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.orders.daos.IOrderSyncInfoDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.business.resources.daos.IWorkerDAO;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.Worker;
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.PredefinedWorkReportTypes;
import org.libreplan.business.workreports.entities.WorkReport;
import org.libreplan.business.workreports.entities.WorkReportLine;
import org.libreplan.business.workreports.entities.WorkReportType;
import org.libreplan.business.workreports.valueobjects.DescriptionField;
import org.libreplan.business.workreports.valueobjects.DescriptionValue;
import org.libreplan.importers.jira.IssueDTO;
import org.libreplan.importers.jira.WorkLogDTO;
import org.libreplan.importers.jira.WorkLogItemDTO;
import org.libreplan.web.workreports.IWorkReportModel;
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;
import org.springframework.transaction.annotation.Transactional;
/**
* Implementation of Synchronize timesheets with jira issues.
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
private SynchronizationInfo synchronizationInfo;
private List<Worker> workers;
private WorkReportType workReportType;
private TypeOfWorkHours typeOfWorkHours;
@Autowired
private IWorkerDAO workerDAO;
@Autowired
private IWorkReportTypeDAO workReportTypeDAO;
@Autowired
private IWorkReportDAO workReportDAO;
@Autowired
private IWorkReportModel workReportModel;
@Autowired
private ITypeOfWorkHoursDAO typeOfWorkHoursDAO;
@Autowired
private IConnectorDAO connectorDAO;
@Autowired
private IOrderSyncInfoDAO orderSyncInfoDAO;
@Override
@Transactional
public void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order) throws ConnectorException {
synchronizationInfo = new SynchronizationInfo(_("Synchronization"));
workReportType = getJiraTimesheetsWorkReportType();
typeOfWorkHours = getTypeOfWorkHours();
workers = getWorkers();
if (workers == null && workers.isEmpty()) {
synchronizationInfo.addFailedReason(_("No workers found"));
return;
}
OrderSyncInfo orderSyncInfo = orderSyncInfoDAO
.findLastSynchronizedInfoByOrderAndConnectorName(order, PredefinedConnectors.JIRA.getName());
if (orderSyncInfo == null) {
synchronizationInfo.addFailedReason(
_("Order \"{0}\" not found. Order probalbly not synchronized", order.getName()));
return;
}
if (StringUtils.isBlank(orderSyncInfo.getKey())) {
synchronizationInfo.addFailedReason(_("Key for Order \"{0}\" is empty", order.getName()));
return;
}
String code = order.getCode() + "-" + orderSyncInfo.getKey();
WorkReport workReport = updateOrCreateWorkReport(code);
for (IssueDTO issue : issues) {
WorkLogDTO workLog = issue.getFields().getWorklog();
if (workLog == null) {
synchronizationInfo.addFailedReason(_("No worklogs found for \"{0}\" key", issue.getKey()));
} else {
List<WorkLogItemDTO> workLogItems = workLog.getWorklogs();
if (workLogItems == null || workLogItems.isEmpty()) {
synchronizationInfo.addFailedReason(_("No worklog items found for \"{0}\" issue", issue.getKey()));
} else {
String codeOrderElement =
PredefinedConnectorProperties.JIRA_CODE_PREFIX + order.getCode() + "-" + issue.getKey();
OrderElement orderElement = order.getOrderElement(codeOrderElement);
if (orderElement == null) {
synchronizationInfo.addFailedReason(_("Order element \"{0}\" not found", code));
} else {
updateOrCreateWorkReportLineAndAddToWorkReport(workReport, orderElement, workLogItems);
}
}
}
}
saveWorkReportIfNotEmpty();
}
private void saveWorkReportIfNotEmpty() {
if (workReportModel.getWorkReport().getWorkReportLines().size() > 0) {
workReportModel.confirmSave();
}
}
/**
* Updates {@link WorkReport} if exist, if not creates new one.
*
* @param code
* search criteria for workReport
* @return the workReport
*/
private WorkReport updateOrCreateWorkReport(String code) {
WorkReport workReport = findWorkReport(code);
if (workReport == null) {
workReportModel.initCreate(workReportType);
workReport = workReportModel.getWorkReport();
workReport.setCode(code);
} else {
workReportModel.initEdit(workReport);
}
workReportModel.setCodeAutogenerated(false);
return workReport;
}
/**
* Updates {@link WorkReportLine} if exist. If not creates new one and adds to <code>workReport</code>.
*
* @param workReport
* an existing or new created workReport
* @param orderElement
* the orderElement
* @param workLogItems
* jira's workLog items to be added to workReportLine
*/
private void updateOrCreateWorkReportLineAndAddToWorkReport(WorkReport workReport,
OrderElement orderElement,
List<WorkLogItemDTO> workLogItems) {
for (WorkLogItemDTO workLogItem : workLogItems) {
Resource resource = getWorker(workLogItem.getAuthor().getName());
if (resource == null) {
continue;
}
WorkReportLine workReportLine;
String code = orderElement.getCode() + "-" + workLogItem.getId();
try {
workReportLine = workReport.getWorkReportLineByCode(code);
} catch (InstanceNotFoundException e) {
workReportLine = WorkReportLine.create(workReport);
workReport.addWorkReportLine(workReportLine);
workReportLine.setCode(code);
}
updateWorkReportLine(workReportLine, orderElement, workLogItem, resource);
}
}
/**
* Updates {@link WorkReportLine} with <code>workLogItem</code>.
*
* @param workReportLine
* workReportLine to be updated
* @param orderElement
* the orderElement
* @param workLogItem
* workLogItem to update the workReportLine
* @param resource
* the resource
*/
private void updateWorkReportLine(WorkReportLine workReportLine,
OrderElement orderElement,
WorkLogItemDTO workLogItem,
Resource resource) {
int timeSpent = workLogItem.getTimeSpentSeconds();
workReportLine.setDate(workLogItem.getStarted());
workReportLine.setResource(resource);
workReportLine.setOrderElement(orderElement);
workReportLine.setEffort(EffortDuration.seconds(timeSpent));
workReportLine.setTypeOfWorkHours(typeOfWorkHours);
updateOrCreateDescriptionValuesAndAddToWorkReportLine(workReportLine, workLogItem.getComment());
}
/**
* Updates {@link DescriptionValue} if exist. if not creates new one and adds to <code>workReportLine</code>.
*
* @param workReportLine
* workReprtLinew where descriptionvalues to be added to
* @param comment
* the description value
*/
private void updateOrCreateDescriptionValuesAndAddToWorkReportLine(WorkReportLine workReportLine, String comment) {
DescriptionField descriptionField = workReportType.getLineFields().iterator().next();
Integer maxLength = descriptionField.getLength();
if (comment.length() > maxLength) {
comment = comment.substring(0, maxLength - 1);
}
Set<DescriptionValue> descriptionValues = workReportLine.getDescriptionValues();
if (descriptionValues.isEmpty()) {
descriptionValues.add(DescriptionValue.create(descriptionField.getFieldName(), comment));
} else {
descriptionValues.iterator().next().setValue(comment);
}
workReportLine.setDescriptionValues(descriptionValues);
}
/**
* Returns {@link WorkReportType} for JIRA connector.
*
* @return WorkReportType for JIRA connector
*/
private WorkReportType getJiraTimesheetsWorkReportType() {
WorkReportType workReportType;
try {
workReportType = workReportTypeDAO.findUniqueByName(PredefinedWorkReportTypes.JIRA_TIMESHEETS.getName());
} catch (NonUniqueResultException | InstanceNotFoundException e) {
throw new RuntimeException(e);
}
return workReportType;
}
/**
* Returns {@link TypeOfWorkHours} configured for JIRA connector.
*
* @return TypeOfWorkHours for JIRA connector
* @throws ConnectorException
*/
private TypeOfWorkHours getTypeOfWorkHours() throws ConnectorException {
Connector connector = connectorDAO.findUniqueByName(PredefinedConnectors.JIRA.getName());
if (connector == null) {
throw new ConnectorException(_("JIRA connector not found"));
}
TypeOfWorkHours typeOfWorkHours;
String name = connector.getPropertiesAsMap().get(PredefinedConnectorProperties.JIRA_HOURS_TYPE);
if (StringUtils.isBlank(name)) {
throw new ConnectorException(_("Hours type should not be empty to synchronine timesheets"));
}
try {
typeOfWorkHours = typeOfWorkHoursDAO.findUniqueByName(name);
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
return typeOfWorkHours;
}
/**
* Searches for {@link WorkReport} for the specified parameter <code>code</code>.
*
* @param code
* unique code
* @return workReportType if found, null otherwise
*/
private WorkReport findWorkReport(String code) {
try {
return workReportDAO.findByCode(code);
} catch (InstanceNotFoundException e) {
}
return null;
}
/**
* Gets all LibrePlan workers.
*
* @return list of workers
*/
private List<Worker> getWorkers() {
return workerDAO.findAll();
}
/**
* Searches for {@link Worker} for the specified parameter <code>nif</code>.
*
* @param nif
* unique id
* @return worker if found, null otherwise
*/
private Worker getWorker(String nif) {
for (Worker worker : workers) {
if (worker.getNif().equals(nif)) {
return worker;
}
}
synchronizationInfo.addFailedReason(_("Worker \"{0}\" not found", nif));
return null;
}
@Override
public SynchronizationInfo getSynchronizationInfo() {
return synchronizationInfo;
}
}