PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/com/osf/jira/plugins/report/workplan/WorkPlan.java

https://bitbucket.org/azhdanov/jiraworkplan
Java | 726 lines | 574 code | 83 blank | 69 comment | 169 complexity | 8a925914b916e74023112ed2868acb83 MD5 | raw file
  1. /**
  2. * Copyright (c) 2008, Outsourcing Factory Inc.,
  3. * http://www.outsourcing-factory.com/
  4. *
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. *
  10. * * Redistributions of source code must retain the above copyright notice,
  11. * this list of conditions and the following disclaimer.
  12. *
  13. * * Redistributions in binary form must reproduce the above copyright notice,
  14. * this list of conditions and the following disclaimer in the documentation
  15. * and/or other materials provided with the distribution.
  16. *
  17. * * Neither the name of the Outsourcing Factory Inc. nor the names of its
  18. * contributors may be used to endorse or promote products derived from this
  19. * software without specific prior written permission.
  20. *
  21. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  22. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  24. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
  25. * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  26. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  27. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  28. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  29. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  30. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  31. * POSSIBILITY OF SUCH DAMAGE.
  32. *
  33. */
  34. package com.osf.jira.plugins.report.workplan;
  35. import java.text.DateFormat;
  36. import java.text.NumberFormat;
  37. import java.text.ParseException;
  38. import java.util.ArrayList;
  39. import java.util.Arrays;
  40. import java.util.Calendar;
  41. import java.util.Collection;
  42. import java.util.Date;
  43. import java.util.HashMap;
  44. import java.util.HashSet;
  45. import java.util.Iterator;
  46. import java.util.List;
  47. import java.util.Map;
  48. import java.util.Set;
  49. import java.util.TreeSet;
  50. import org.apache.log4j.Logger;
  51. import org.ofbiz.core.entity.DelegatorInterface;
  52. import org.ofbiz.core.entity.EntityCondition;
  53. import org.ofbiz.core.entity.EntityConditionList;
  54. import org.ofbiz.core.entity.EntityExpr;
  55. import org.ofbiz.core.entity.EntityOperator;
  56. import org.ofbiz.core.entity.GenericEntityException;
  57. import org.ofbiz.core.entity.GenericValue;
  58. import org.ofbiz.core.util.UtilMisc;
  59. import com.atlassian.core.util.DateUtils;
  60. import com.atlassian.core.util.DateUtils.Duration;
  61. import com.atlassian.core.util.InvalidDurationException;
  62. import com.atlassian.jira.component.ComponentAccessor;
  63. import com.atlassian.jira.config.properties.ApplicationProperties;
  64. import com.atlassian.jira.datetime.DateTimeFormatter;
  65. import com.atlassian.jira.datetime.DateTimeFormatterFactory;
  66. import com.atlassian.jira.datetime.DateTimeStyle;
  67. import com.atlassian.jira.issue.CustomFieldManager;
  68. import com.atlassian.jira.issue.Issue;
  69. import com.atlassian.jira.issue.IssueFactory;
  70. import com.atlassian.jira.issue.IssueFieldConstants;
  71. import com.atlassian.jira.issue.MutableIssue;
  72. import com.atlassian.jira.issue.fields.CustomField;
  73. import com.atlassian.jira.issue.search.SearchException;
  74. import com.atlassian.jira.plugin.report.impl.AbstractReport;
  75. import com.atlassian.jira.project.Project;
  76. import com.atlassian.jira.project.ProjectManager;
  77. import com.atlassian.jira.security.JiraAuthenticationContext;
  78. import com.atlassian.jira.security.PermissionManager;
  79. import com.atlassian.jira.security.roles.ProjectRole;
  80. import com.atlassian.jira.security.roles.ProjectRoleActors;
  81. import com.atlassian.jira.security.roles.ProjectRoleManager;
  82. import com.atlassian.jira.security.roles.RoleActor;
  83. import com.atlassian.jira.user.ApplicationUser;
  84. import com.atlassian.jira.user.util.UserManager;
  85. import com.atlassian.jira.user.util.UserUtil;
  86. import com.atlassian.jira.util.I18nHelper;
  87. import com.atlassian.jira.util.ParameterUtils;
  88. import com.atlassian.jira.web.action.ProjectActionSupport;
  89. import com.atlassian.jira.web.util.OutlookDateManager;
  90. import com.osf.jira.plugins.util.Holidays;
  91. import com.osf.jira.plugins.util.TextUtil;
  92. /**
  93. * @author Andriy Zhdanov
  94. */
  95. public class WorkPlan extends AbstractReport {
  96. private static final Logger log = Logger.getLogger(WorkPlan.class);
  97. private static final String ALLPROJECTS_KEY = "allProjects";
  98. private final IssueFactory issueFactory;
  99. private final OutlookDateManager outlookDateManager;
  100. private final UserManager userManager;
  101. private final UserUtil userUtil;
  102. private final PermissionManager permissionManager;
  103. private final JiraAuthenticationContext authenticationContext;
  104. private final ProjectRoleManager projectRoleManager;
  105. private final ProjectManager projectManager;
  106. private final CustomField issueStartDateCF;
  107. private final CustomField issueUtilizationCF;
  108. private final List splitStartDateCFs;
  109. private final List splitEstimateCFs;
  110. private final List splitUtilizationCFs;
  111. private final long secondsPerDay;
  112. private final long secondsPerWeek;
  113. private final DateTimeFormatterFactory dateTimeFormatterFactory;
  114. private Date reportStartDate;
  115. private Date reportEndDate;
  116. public WorkPlan(IssueFactory issueFactory,
  117. PermissionManager permissionManager,
  118. CustomFieldManager customFieldManager,
  119. OutlookDateManager outlookDateManager,
  120. JiraAuthenticationContext authenticationContext,
  121. UserManager userManager,
  122. ProjectRoleManager projectRoleManager,
  123. UserUtil userUtil,
  124. ProjectManager projectManager,
  125. DateTimeFormatterFactory dateTimeFormatterFactory) {
  126. this.issueFactory = issueFactory;
  127. this.permissionManager = permissionManager;
  128. this.outlookDateManager = outlookDateManager;
  129. this.authenticationContext = authenticationContext;
  130. this.userManager = userManager;
  131. this.projectRoleManager = projectRoleManager;
  132. this.userUtil = userUtil;
  133. this.projectManager = projectManager;
  134. this.dateTimeFormatterFactory = dateTimeFormatterFactory;
  135. issueStartDateCF = customFieldManager.getCustomFieldObjectByName("startDate");
  136. issueUtilizationCF = customFieldManager.getCustomFieldObjectByName("utilization");
  137. splitStartDateCFs = getSplitCustomFields(customFieldManager, "StartDate");
  138. splitEstimateCFs = getSplitCustomFields(customFieldManager, "Estimate");
  139. splitUtilizationCFs = getSplitCustomFields(customFieldManager, "Utilization");
  140. ApplicationProperties ap = ComponentAccessor.getApplicationProperties();
  141. secondsPerDay = new Float(Float.valueOf(ap.getDefaultBackedString("jira.timetracking.hours.per.day")) * 3600).longValue();
  142. secondsPerWeek = new Float(Float.valueOf(ap.getDefaultBackedString("jira.timetracking.days.per.week")) * secondsPerDay).longValue();
  143. }
  144. private List getSplitCustomFields(CustomFieldManager customFieldManager, String subName) {
  145. List customFields = new ArrayList();
  146. for (int i = 1;;i++) {
  147. CustomField cf = customFieldManager.getCustomFieldObjectByName("split" + i + subName);
  148. if (cf != null) {
  149. customFields.add(cf);
  150. } else {
  151. log.info("Found " + (i - 1) + " " + subName + " split custom fields");
  152. break;
  153. }
  154. }
  155. return customFields;
  156. }
  157. // searches for issues by remainingEstimate, startDate (if set) and assignee/project
  158. // note issues are filtered out by startDate later in getWorkPlan
  159. public List findIssues(String targetGroup, ApplicationUser targetUser, Long resourceProjectId, Long projectId, List<String> statusIds) throws GenericEntityException {
  160. EntityExpr estimatetExpr = new EntityExpr(IssueFieldConstants.TIME_ESTIMATE,
  161. EntityOperator.GREATER_THAN, new Long(0));
  162. EntityExpr entityExpr1;
  163. if (targetGroup != null && targetGroup.length() != 0) {
  164. log.info("Quering issues for group " + targetGroup);
  165. Set<ApplicationUser> users = userUtil.getAllUsersInGroupNames(
  166. Arrays.asList(new String[] {targetGroup}));
  167. if (users.size() > 0 ) {
  168. Set<String> userNames = new HashSet<String>();
  169. for (Iterator<ApplicationUser> i = users.iterator(); i.hasNext(); ) {
  170. String userName = i.next().getName();
  171. userNames.add(userName);
  172. }
  173. entityExpr1 = new EntityExpr("assignee",
  174. EntityOperator.IN, userNames);
  175. } else {
  176. // to avoid SQL Exception in SELECT ... WHERE (... (ASSIGNEE IN () ...)
  177. return new ArrayList();
  178. }
  179. } else if (targetUser != null) {
  180. entityExpr1 = new EntityExpr("assignee", EntityOperator.EQUALS, targetUser.getName());
  181. log.info("Quering issues for user " + targetUser.getName());
  182. } else if(resourceProjectId != null) {
  183. Project resourceProject = projectManager.getProjectObj(resourceProjectId);
  184. Collection<ProjectRole> projectRoles = projectRoleManager.getProjectRoles();
  185. Set<String> users = new HashSet<String>();
  186. for (ProjectRole projectRole : projectRoles) {
  187. Set<ApplicationUser> roleUsers = projectRoleManager.getProjectRoleActors(projectRole, resourceProject).getUsers();
  188. for (Iterator<ApplicationUser> j = roleUsers.iterator(); j.hasNext(); ) {
  189. ApplicationUser roleUser = j.next();
  190. users.add(roleUser.getName());
  191. }
  192. }
  193. if (users.size() > 0 ) {
  194. entityExpr1 = new EntityExpr("assignee", EntityOperator.IN, users);
  195. } else {
  196. // to avoid SQL Exception in SELECT ... WHERE (... (ASSIGNEE IN () ...)
  197. return new ArrayList();
  198. }
  199. } else {
  200. entityExpr1 = new EntityExpr("project",
  201. EntityOperator.EQUALS, projectId);
  202. log.info("Quering issues for projectId = " + projectId);
  203. }
  204. EntityCondition statusCondition;
  205. if (statusIds == null) {
  206. EntityExpr statusNotClosedExpr = new EntityExpr(IssueFieldConstants.STATUS, EntityOperator.NOT_EQUAL,
  207. Integer.toString(IssueFieldConstants.CLOSED_STATUS_ID));
  208. EntityExpr statusNotResolvedExpr = new EntityExpr(IssueFieldConstants.STATUS, EntityOperator.NOT_EQUAL,
  209. Integer.toString(IssueFieldConstants.RESOLVED_STATUS_ID));
  210. statusCondition = new EntityConditionList(
  211. UtilMisc.toList(statusNotClosedExpr, statusNotResolvedExpr), EntityOperator.AND);
  212. } else {
  213. List<EntityExpr> statusExprList = new ArrayList<EntityExpr>(statusIds.size());
  214. for (String statusId : statusIds) {
  215. EntityExpr statusExpr = new EntityExpr(IssueFieldConstants.STATUS, EntityOperator.EQUALS,
  216. statusId);
  217. statusExprList.add(statusExpr);
  218. }
  219. statusCondition = new EntityConditionList(statusExprList, EntityOperator.OR);
  220. }
  221. final EntityCondition entityCondition = new EntityConditionList(
  222. UtilMisc.toList(estimatetExpr, entityExpr1, statusCondition), EntityOperator.AND);
  223. /* not working!!! :(
  224. EntityExpr resolvedExpr = new EntityExpr(IssueFieldConstants.RESOLUTION, EntityOperator.,
  225. new Long(0));
  226. */
  227. return ComponentAccessor.getComponent(DelegatorInterface.class).findByCondition("Issue", entityCondition, null, null);
  228. }
  229. public Map getWorkPlan(I18nHelper i18nBean, Map params, ApplicationUser remoteUser) throws GenericEntityException, SearchException {
  230. String targetGroup = ParameterUtils.getStringParam(params, "targetGroup");
  231. ApplicationUser targetUser = ParameterUtils.getUserParam(params, "targetUser");
  232. Long targetResourceProjectId = ParameterUtils.getLongParam(params, "targetResourceProjectId");
  233. Long projectId = ParameterUtils.getLongParam(params, "projectId");
  234. List<String> statusIds = ParameterUtils.getListParam(params, "statusIds");
  235. if (statusIds == null) {
  236. String statusId = (String) params.get("statusIds");
  237. if (statusId != null && !statusId.isEmpty()) {
  238. statusIds = UtilMisc.toList(statusId);
  239. }
  240. }
  241. List issues = findIssues(targetGroup, targetUser, targetResourceProjectId, projectId, statusIds);
  242. Collection users = findUsers(targetGroup, targetUser, targetResourceProjectId, projectId);
  243. log.info("Query returned : " + issues.size() + " issues");
  244. Map workPlan = new HashMap();
  245. // fill in work planned per user
  246. for (Iterator issuesIterator = issues.iterator(); issuesIterator.hasNext();) {
  247. GenericValue genericIssue = (GenericValue) issuesIterator.next();
  248. MutableIssue issue = issueFactory.getIssue(genericIssue);
  249. EstimatedWork work = getEstimatedWorkFromGV(issue, i18nBean, params);
  250. // skip issue if work planned is out of range
  251. if (work.getSplits().size() == 0) {
  252. log.info("Skipping issue " + issue.getKey());
  253. continue;
  254. }
  255. ApplicationUser assignee = work.getAssignee();
  256. Map projectsPerAssignee = (Map) workPlan.get(assignee);
  257. if (projectsPerAssignee == null && users.contains(assignee)) {
  258. projectsPerAssignee = createEmptyProjectsPerAssignee();
  259. workPlan.put(assignee, projectsPerAssignee);
  260. }
  261. Project project = work.getProject();
  262. if (projectsPerAssignee != null) {
  263. ProjectBean projectBean = (ProjectBean) projectsPerAssignee.get(project.getKey());
  264. if(projectBean == null) {
  265. projectBean = new ProjectBean();
  266. projectBean.setKey(project.getKey());
  267. projectBean.setName(project.getName());
  268. projectsPerAssignee.put(project.getKey(), projectBean);
  269. }
  270. projectBean.getEstimatedWorks().add(work);
  271. }
  272. }
  273. return workPlan;
  274. }
  275. private Map createEmptyProjectsPerAssignee() {
  276. Map projectsPerAssignee = new HashMap();
  277. ProjectBean totalProject = new ProjectBean();
  278. totalProject.setKey(ALLPROJECTS_KEY);
  279. //totalProject.setName(i18nBean.getText("report.workplan.allprojects"));
  280. //FIXME: hardcoded l10n
  281. totalProject.setName("All Projects");
  282. projectsPerAssignee.put(ALLPROJECTS_KEY, totalProject);
  283. return projectsPerAssignee;
  284. }
  285. private Collection findUsers(String targetGroup, ApplicationUser targetUser, Long targetResourceProjectId, Long projectId) throws GenericEntityException {
  286. Set users = new HashSet();
  287. if (targetGroup != null && targetGroup.length() != 0) {
  288. users = userUtil.getAllUsersInGroupNames(
  289. Arrays.asList(new String[] {targetGroup}));
  290. } else if (targetUser != null) {
  291. users.add(targetUser);
  292. } else if(targetResourceProjectId != null || projectId != null) {
  293. Long membersSourceProjectId = (targetResourceProjectId != null ? targetResourceProjectId : projectId);
  294. Project resourceProject = projectManager.getProjectObj(membersSourceProjectId);
  295. Collection projectRoles = projectRoleManager.getProjectRoles();
  296. ProjectRoleActors allProjectRoleActors;
  297. Set actorsObjects;
  298. RoleActor actor;
  299. for (Iterator i = projectRoles.iterator(); i.hasNext(); ) {
  300. ProjectRole projectRole = (ProjectRole) i.next();
  301. if ("Users".equals(projectRole.getName())) {
  302. continue;
  303. }
  304. allProjectRoleActors = projectRoleManager.getProjectRoleActors(projectRole, resourceProject);
  305. if (allProjectRoleActors != null) {
  306. actorsObjects = allProjectRoleActors.getRoleActorsByType("atlassian-user-role-actor");
  307. if (actorsObjects != null && !actorsObjects.isEmpty()) {
  308. for (Iterator k = actorsObjects.iterator(); k.hasNext(); ) {
  309. actor = (RoleActor) k.next();
  310. for (Iterator j = actor.getUsers().iterator(); j.hasNext(); ) {
  311. ApplicationUser roleUser = (ApplicationUser)j.next();
  312. users.add(roleUser);
  313. }
  314. }
  315. }
  316. actorsObjects = allProjectRoleActors.getRoleActorsByType("atlassian-group-role-actor");
  317. if (actorsObjects != null && !actorsObjects.isEmpty()) {
  318. for (Iterator k = actorsObjects.iterator(); k.hasNext(); ) {
  319. actor = (RoleActor) k.next();
  320. for (Iterator j = actor.getUsers().iterator(); j.hasNext(); ) {
  321. ApplicationUser roleUser = (ApplicationUser) j.next();
  322. users.add(roleUser);
  323. }
  324. }
  325. }
  326. }
  327. }
  328. }
  329. return users;
  330. }
  331. private EstimatedWork getEstimatedWorkFromGV(Issue issue, I18nHelper i18nBean, Map params) {
  332. EstimatedWork work = new EstimatedWork(issue);
  333. work.setSplits(new TreeSet(new EstimatedWork.SplitComparator()));
  334. boolean doOldStyle = true;
  335. for (int i = 0; i < splitStartDateCFs.size(); i++) {
  336. CustomField splitStartDateCF = (CustomField) splitStartDateCFs.get(i);
  337. CustomField splitEstimateCF = null;
  338. if (splitEstimateCFs.size() > i) {
  339. splitEstimateCF = (CustomField) splitEstimateCFs.get(i);
  340. }
  341. long estimate = 0;
  342. if (splitEstimateCF != null) {
  343. String estimateStr = (String) splitEstimateCF.getValue(issue);
  344. if (estimateStr != null) {
  345. doOldStyle = false; // if at least one split value is specified
  346. try {
  347. estimate = DateUtils.getDurationSeconds(estimateStr, secondsPerDay, secondsPerWeek, Duration.MINUTE);
  348. } catch (InvalidDurationException e) {
  349. log.warn("Split is ignored: " + e.getMessage() + ": " + estimateStr);
  350. }
  351. }
  352. }
  353. CustomField splitUtilizationCF = null;
  354. if (splitUtilizationCFs.size() > i) {
  355. splitUtilizationCF = (CustomField) splitUtilizationCFs.get(i);
  356. }
  357. if (estimate > 0) {
  358. EstimatedWork.Split split =
  359. getSplit(issue, splitStartDateCF, estimate, splitUtilizationCF, i18nBean, params);
  360. if (split != null) {
  361. work.getSplits().add(split);
  362. }
  363. }
  364. }
  365. // use issue standard estimate field.
  366. if (doOldStyle) {
  367. long estimate = 0;
  368. if (issue.getEstimate() != null) {
  369. estimate = issue.getEstimate().longValue();
  370. }
  371. EstimatedWork.Split split =
  372. getSplit(issue, issueStartDateCF, estimate, issueUtilizationCF, i18nBean, params);
  373. if (split != null) {
  374. work.getSplits().add(split);
  375. }
  376. }
  377. return work;
  378. }
  379. // if utilizationStr is null, utilization will be 100%
  380. // return null if split does not fit into report date range
  381. private EstimatedWork.Split getSplit(Issue issue, CustomField startDateCF, long estimate, CustomField utilizationCF, I18nHelper i18nBean, Map params) {
  382. EstimatedWork.Split split = new EstimatedWork.Split();
  383. split.setEstimate(estimate);
  384. // startDate <- split1StartDate || startDate || today
  385. Date startDate = null;
  386. if (startDateCF != null) {
  387. startDate = (Date) startDateCF.getValue(issue);
  388. }
  389. // if no split start date set, use issue startDate custom field
  390. if (startDate == null && issueStartDateCF != null && issueStartDateCF != startDateCF) {
  391. startDate = (Date) issueStartDateCF.getValue(issue);
  392. }
  393. Calendar today = roundDate(Calendar.getInstance().getTime());
  394. // make remaining estimate start from today
  395. // if no start date specified
  396. if ((startDate == null ||
  397. // or planned start date passed, but beside splits,
  398. (startDate.before(today.getTime()) && issueStartDateCF == startDateCF)
  399. // only if today is in report range
  400. && reportEndDate.after(today.getTime()))) {
  401. startDate = today.getTime();
  402. }
  403. Calendar startCalendar = roundDate(startDate);
  404. split.setStartDate(startCalendar.getTime());
  405. String utilizationStr = null;
  406. if (utilizationCF != null) {
  407. utilizationStr = (String) utilizationCF.getValue(issue);
  408. }
  409. // if no split utilization set, use issue utilization custom field
  410. if (utilizationStr == null && issueUtilizationCF != null && issueUtilizationCF != utilizationCF) {
  411. utilizationStr = (String) issueUtilizationCF.getValue(issue);
  412. }
  413. NumberFormat percentFormat = NumberFormat.getPercentInstance(i18nBean.getLocale());
  414. double utilization = 0;
  415. if (utilizationStr != null) {
  416. try {
  417. utilization = percentFormat.parse(utilizationStr).doubleValue();
  418. } catch (ParseException e) {
  419. log.warn("Utilization will be 100%: " + e.getMessage());
  420. }
  421. }
  422. if (utilization <= 0) { // avoid illegal utilization
  423. utilization = 1; // 100%
  424. utilizationStr = percentFormat.format(utilization);
  425. }
  426. int estimateInDays = (int) Math.ceil(estimate / (secondsPerDay * utilization));
  427. estimateInDays += getNonWorkingDays(startDate, estimateInDays, params);
  428. split.setEstimateInDays(estimateInDays);
  429. split.setUtilizationStr(utilizationStr);
  430. split.setUtilization(utilization);
  431. if (startDate.after(reportEndDate)) {
  432. log.info("startDate " + startDate + " is after reportEndDate");
  433. return null; // does not fit into report date range
  434. } else if (startDate.after(reportStartDate)) {
  435. return split; // starts in report date range
  436. } else {
  437. Calendar endCalendar = (Calendar) startCalendar.clone();
  438. endCalendar.add(Calendar.DAY_OF_YEAR, estimateInDays);
  439. endCalendar.add(Calendar.MINUTE, -1);
  440. Date endDate = endCalendar.getTime();
  441. if (endDate.before(reportStartDate)) {
  442. log.info("endDate " + endDate + " is before reportStartDate");
  443. return null; // does not fit into report date range
  444. } else {
  445. split.setStartDate(reportStartDate);
  446. // adjust estimate in days
  447. while (startDate.before(reportStartDate)) {
  448. estimateInDays--;
  449. startCalendar.add(Calendar.DAY_OF_YEAR, 1);
  450. startDate = startCalendar.getTime();
  451. }
  452. split.setEstimateInDays(estimateInDays); // estimate is untouched
  453. return split; // is in report date range
  454. }
  455. }
  456. }
  457. // get number of non working dates in estimated work
  458. private int getNonWorkingDays(Date startDate, int estimateInDays, Map params) {
  459. int nonWorkingDays = 0;
  460. boolean workOnWeekends = params.containsKey("workOnWeekends");
  461. boolean workOnHolidays = params.containsKey("workOnHolidays");
  462. Calendar calendarDate = roundDate(startDate);
  463. while (estimateInDays-- > 0) {
  464. int dayOfWeek = calendarDate.get(Calendar.DAY_OF_WEEK);
  465. if ((!workOnWeekends && (dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY))
  466. || (!workOnHolidays && Holidays.getHolidays().containsKey(calendarDate.getTime()))) {
  467. nonWorkingDays++;
  468. estimateInDays++;
  469. }
  470. calendarDate.add(Calendar.DAY_OF_YEAR, 1);
  471. }
  472. return nonWorkingDays;
  473. }
  474. // fill dates (ordered list) and week days (corresponding to each date)
  475. public List getWeekDays(Date beginDate, Date endDate, I18nHelper i18nBean) {
  476. List weekDays = new ArrayList();
  477. DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, i18nBean.getLocale());
  478. Calendar calendarDate = getCalendar(endDate.getTime());
  479. calendarDate.add(Calendar.DAY_OF_YEAR, 1);
  480. endDate = calendarDate.getTime();
  481. calendarDate = roundDate(beginDate);
  482. while (endDate.after(calendarDate.getTime())) {
  483. int dayOfWeek = calendarDate.get(Calendar.DAY_OF_WEEK);
  484. WeekDay wd = new WeekDay();
  485. wd.setTime(calendarDate.getTime().getTime());
  486. wd.setKey(df.format(calendarDate.getTime()));
  487. String holiday = (String) Holidays.getHolidays().get(calendarDate.getTime());
  488. wd.setHoliday(holiday); // null if not a holiday
  489. wd.setCss((dayOfWeek == Calendar.SATURDAY
  490. || dayOfWeek == Calendar.SUNDAY
  491. || holiday != null) ? "nonBusinessDay" : "");
  492. weekDays.add(wd);
  493. calendarDate.add(Calendar.DAY_OF_YEAR, 1);
  494. }
  495. return weekDays;
  496. }
  497. public void calculateDayTotals(Map<ApplicationUser, Map<String, ProjectBean>> workPlan, List weekDays, Map params) {
  498. boolean workOnWeekends = params.containsKey("workOnWeekends");
  499. boolean workOnHolidays = params.containsKey("workOnHolidays");
  500. for (Iterator<ApplicationUser> i = workPlan.keySet().iterator(); i.hasNext();) {
  501. ApplicationUser assignee = i.next();
  502. Map<String, ProjectBean> projectsPerUser = workPlan.get(assignee);
  503. ProjectBean totalProject = projectsPerUser.get(ALLPROJECTS_KEY);
  504. Map dayTotalsPerUser = totalProject.getEstimateTotals();
  505. Map<Long, Long> dueDateTotalsPerUser = totalProject.getDueDateTotals();
  506. for (String projectKey : projectsPerUser.keySet()) {
  507. ProjectBean project = projectsPerUser.get(projectKey);
  508. List issuesPerProjectAndUser = project.getEstimatedWorks();
  509. Map dayTotalsPerUserAndProject = project.getEstimateTotals();
  510. for (Iterator j = issuesPerProjectAndUser.iterator(); j.hasNext();) {
  511. EstimatedWork ew = (EstimatedWork) j.next();
  512. for (Iterator k = ew.getSplits().iterator(); k.hasNext();) {
  513. EstimatedWork.Split split = (EstimatedWork.Split) k.next();
  514. long startTime = split.getStartTime();
  515. Calendar c = getCalendar(startTime);
  516. for (int day = 0; day < split.getEstimateInDays(); day++) {
  517. Long date = new Long(c.getTime().getTime());
  518. int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
  519. boolean isHoliday = Holidays.getHolidays().containsKey(c.getTime());
  520. c.add(Calendar.DAY_OF_YEAR, 1);
  521. if (!workOnWeekends && (dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY)
  522. || !workOnHolidays && isHoliday) {
  523. continue;
  524. }
  525. Long totalPerUser = (Long) dayTotalsPerUser.get(date);
  526. Long totalPerUserAndProject = (Long) dayTotalsPerUserAndProject.get(date);
  527. long miliseconds;
  528. // treat specially last day
  529. if (day < split.getEstimateInDays() - 1) {
  530. miliseconds = Math.round(split.getUtilization() * secondsPerDay);
  531. } else {
  532. // remaining hours
  533. miliseconds = Math.round(split.getEstimate() % (split.getUtilization() * secondsPerDay));
  534. if (miliseconds == 0) {
  535. // whole day
  536. miliseconds = Math.round(split.getUtilization() * secondsPerDay);
  537. }
  538. }
  539. if (totalPerUserAndProject == null) {
  540. dayTotalsPerUserAndProject.put(date, new Long(miliseconds));
  541. } else {
  542. dayTotalsPerUserAndProject.put(date, new Long(totalPerUserAndProject.longValue() + miliseconds));
  543. }
  544. if (totalPerUser == null) {
  545. dayTotalsPerUser.put(date, new Long(miliseconds));
  546. } else {
  547. dayTotalsPerUser.put(date, new Long(totalPerUser.longValue() + miliseconds));
  548. }
  549. }
  550. }
  551. Date dueDate = ew.getDueDate();
  552. if (false && dueDate != null) { // feature disabled
  553. Calendar due = roundDate(dueDate);
  554. Long date = due.getTimeInMillis();
  555. Long dueDateTotalPerUser = dueDateTotalsPerUser.get(date);
  556. Long miliseconds = ew.getEstimate();
  557. if (dueDateTotalPerUser == null) {
  558. dueDateTotalsPerUser.put(date, miliseconds);
  559. } else {
  560. dueDateTotalsPerUser.put(date, dueDateTotalPerUser + miliseconds);
  561. }
  562. }
  563. }
  564. }
  565. }
  566. }
  567. private Calendar roundDate(Date date) {
  568. Calendar calendarDate = Calendar.getInstance();
  569. calendarDate.setTime(date);
  570. calendarDate.set(Calendar.AM_PM, Calendar.AM);
  571. calendarDate.set(Calendar.HOUR, 0);
  572. calendarDate.set(Calendar.MINUTE, 0);
  573. calendarDate.set(Calendar.SECOND, 0);
  574. calendarDate.set(Calendar.MILLISECOND, 0);
  575. return calendarDate;
  576. }
  577. private Calendar getCalendar(long date) {
  578. Calendar calendarDate = Calendar.getInstance();
  579. calendarDate.setTimeInMillis(date);
  580. return calendarDate;
  581. }
  582. public String generateReport(ProjectActionSupport action, Map params, boolean htmlView) throws Exception {
  583. ApplicationUser remoteUser = authenticationContext.getLoggedInUser();
  584. I18nHelper i18nBean = authenticationContext.getI18nHelper();
  585. reportStartDate = roundDate(ParameterUtils.getDateParam(params, "startDate", i18nBean.getLocale())).getTime();
  586. reportEndDate = ParameterUtils.getDateParam(params, "endDate", i18nBean.getLocale());
  587. Calendar c = Calendar.getInstance();
  588. c.setTime(reportEndDate);
  589. c.set(Calendar.HOUR_OF_DAY, 23);
  590. c.set(Calendar.MINUTE, 59);
  591. c.set(Calendar.SECOND, 59);
  592. c.set(Calendar.MILLISECOND, 999);
  593. reportEndDate = c.getTime();
  594. Map velocityParams = new HashMap();
  595. Map workPlan = getWorkPlan(i18nBean, params, remoteUser);
  596. List weekDays = getWeekDays(reportStartDate, reportEndDate, i18nBean);
  597. calculateDayTotals(workPlan, weekDays, params);
  598. velocityParams.put("workPlan", workPlan);
  599. velocityParams.put("weekDays", weekDays);
  600. velocityParams.put("textUtil", new TextUtil(i18nBean));
  601. DateTimeFormatter formatter = dateTimeFormatterFactory.formatter().forLoggedInUser();
  602. velocityParams.put("outlookDate", formatter.withStyle(DateTimeStyle.DATE));
  603. velocityParams.put("additionalField", params.get("additionalField"));
  604. return descriptor.getHtml(htmlView ? "view" : "excel", velocityParams);
  605. }
  606. public String generateReportHtml(ProjectActionSupport action, Map params) throws Exception {
  607. return generateReport(action, params, true);
  608. }
  609. @Override
  610. public String generateReportExcel(ProjectActionSupport action, Map params) throws Exception {
  611. return generateReport(action, params, false);
  612. }
  613. /**
  614. * Validate the parameters set by the user.
  615. */
  616. @Override
  617. public void validate(ProjectActionSupport action, Map params)
  618. {
  619. ApplicationUser remoteUser = action.getLoggedInUser();
  620. Date startDate = ParameterUtils.getDateParam(params, "startDate", action.getLocale());
  621. Date endDate = ParameterUtils.getDateParam(params, "endDate", action.getLocale());
  622. Long projectId = ParameterUtils.getLongParam(params, "projectId");
  623. ApplicationUser targetUser = ParameterUtils.getUserParam(params, "targetUser");
  624. if (startDate == null) {
  625. action.addError("startDate", action.getText("report.workplan.startdate.required"));
  626. }
  627. if (endDate == null) {
  628. action.addError("endDate", action.getText("report.workplan.enddate.required"));
  629. }
  630. // The end date must be after the start date
  631. if (startDate != null && endDate != null && endDate.before(startDate)) {
  632. action.addError("endDate", action.getText("report.workplan.enddate.before.startdate"));
  633. }
  634. if (projectId == null && targetUser == null) {
  635. action.addError("projectId", action.getText("report.workplan.projectoruser.required"));
  636. }
  637. }
  638. /* TODO: get rid of it, but report looks ugly */
  639. @Override
  640. public boolean isExcelViewSupported() {
  641. return true;
  642. }
  643. }