taskjuggler 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (571) hide show
  1. data/COPYING +280 -0
  2. data/README +31 -0
  3. data/Rakefile +20 -0
  4. data/benchmarks/UTF-8-Strings.rb +58 -0
  5. data/benchmarks/allocate.tjp +30 -0
  6. data/benchmarks/booking.tjp +62 -0
  7. data/benchmarks/depends.tjp +112 -0
  8. data/benchmarks/htmltaskreport.tjp +45 -0
  9. data/benchmarks/runbench.rb +24 -0
  10. data/bin/tj3 +3 -0
  11. data/bin/tj3man +3 -0
  12. data/doc/classes/AppConfig.html +808 -0
  13. data/doc/classes/Arguments.html +226 -0
  14. data/doc/classes/String.html +395 -0
  15. data/doc/classes/TaskJuggler.html +1358 -0
  16. data/doc/classes/TaskJuggler/Account.html +257 -0
  17. data/doc/classes/TaskJuggler/AccountScenario.html +218 -0
  18. data/doc/classes/TaskJuggler/Allocation.html +419 -0
  19. data/doc/classes/TaskJuggler/AllocationAttribute.html +291 -0
  20. data/doc/classes/TaskJuggler/AttributeBase.html +608 -0
  21. data/doc/classes/TaskJuggler/AttributeDefinition.html +259 -0
  22. data/doc/classes/TaskJuggler/Booking.html +307 -0
  23. data/doc/classes/TaskJuggler/BookingListAttribute.html +263 -0
  24. data/doc/classes/TaskJuggler/BooleanAttribute.html +261 -0
  25. data/doc/classes/TaskJuggler/CSVFile.html +325 -0
  26. data/doc/classes/TaskJuggler/Charge.html +279 -0
  27. data/doc/classes/TaskJuggler/ChargeListAttribute.html +229 -0
  28. data/doc/classes/TaskJuggler/ChargeSet.html +440 -0
  29. data/doc/classes/TaskJuggler/ChargeSetListAttribute.html +276 -0
  30. data/doc/classes/TaskJuggler/ColumnTable.html +260 -0
  31. data/doc/classes/TaskJuggler/DateAttribute.html +194 -0
  32. data/doc/classes/TaskJuggler/DependencyListAttribute.html +267 -0
  33. data/doc/classes/TaskJuggler/DurationAttribute.html +229 -0
  34. data/doc/classes/TaskJuggler/FixnumAttribute.html +194 -0
  35. data/doc/classes/TaskJuggler/FlagListAttribute.html +263 -0
  36. data/doc/classes/TaskJuggler/FloatAttribute.html +229 -0
  37. data/doc/classes/TaskJuggler/GanttChart.html +667 -0
  38. data/doc/classes/TaskJuggler/GanttContainer.html +441 -0
  39. data/doc/classes/TaskJuggler/GanttHeader.html +280 -0
  40. data/doc/classes/TaskJuggler/GanttHeaderScaleItem.html +245 -0
  41. data/doc/classes/TaskJuggler/GanttLine.html +398 -0
  42. data/doc/classes/TaskJuggler/GanttLoadStack.html +327 -0
  43. data/doc/classes/TaskJuggler/GanttMilestone.html +415 -0
  44. data/doc/classes/TaskJuggler/GanttRouter.html +425 -0
  45. data/doc/classes/TaskJuggler/GanttTaskBar.html +429 -0
  46. data/doc/classes/TaskJuggler/HTMLDocument.html +240 -0
  47. data/doc/classes/TaskJuggler/HTMLGraphics.html +231 -0
  48. data/doc/classes/TaskJuggler/Interval.html +552 -0
  49. data/doc/classes/TaskJuggler/IntervalListAttribute.html +267 -0
  50. data/doc/classes/TaskJuggler/KeywordDocumentation.html +796 -0
  51. data/doc/classes/TaskJuggler/Limits.html +416 -0
  52. data/doc/classes/TaskJuggler/Limits/Limit.html +381 -0
  53. data/doc/classes/TaskJuggler/LimitsAttribute.html +261 -0
  54. data/doc/classes/TaskJuggler/Log.html +613 -0
  55. data/doc/classes/TaskJuggler/LogicalAttribute.html +226 -0
  56. data/doc/classes/TaskJuggler/LogicalExpression.html +251 -0
  57. data/doc/classes/TaskJuggler/LogicalFlag.html +229 -0
  58. data/doc/classes/TaskJuggler/LogicalFunction.html +324 -0
  59. data/doc/classes/TaskJuggler/LogicalOperation.html +299 -0
  60. data/doc/classes/TaskJuggler/Macro.html +194 -0
  61. data/doc/classes/TaskJuggler/MacroParser.html +360 -0
  62. data/doc/classes/TaskJuggler/MacroTable.html +366 -0
  63. data/doc/classes/TaskJuggler/Message.html +281 -0
  64. data/doc/classes/TaskJuggler/MessageHandler.html +215 -0
  65. data/doc/classes/TaskJuggler/Project.html +1606 -0
  66. data/doc/classes/TaskJuggler/ProjectFileParser.html +412 -0
  67. data/doc/classes/TaskJuggler/PropertyList.html +597 -0
  68. data/doc/classes/TaskJuggler/PropertySet.html +1200 -0
  69. data/doc/classes/TaskJuggler/PropertyTreeNode.html +1449 -0
  70. data/doc/classes/TaskJuggler/Query.html +600 -0
  71. data/doc/classes/TaskJuggler/RealFormat.html +252 -0
  72. data/doc/classes/TaskJuggler/ReferenceAttribute.html +194 -0
  73. data/doc/classes/TaskJuggler/Report.html +528 -0
  74. data/doc/classes/TaskJuggler/ReportElement.html +1070 -0
  75. data/doc/classes/TaskJuggler/ReportTable.html +497 -0
  76. data/doc/classes/TaskJuggler/ReportTableCell.html +518 -0
  77. data/doc/classes/TaskJuggler/ReportTableColumn.html +364 -0
  78. data/doc/classes/TaskJuggler/ReportTableElement.html +644 -0
  79. data/doc/classes/TaskJuggler/ReportTableLegend.html +343 -0
  80. data/doc/classes/TaskJuggler/ReportTableLine.html +431 -0
  81. data/doc/classes/TaskJuggler/Resource.html +211 -0
  82. data/doc/classes/TaskJuggler/ResourceListAttribute.html +267 -0
  83. data/doc/classes/TaskJuggler/ResourceListRE.html +249 -0
  84. data/doc/classes/TaskJuggler/ResourceScenario.html +1137 -0
  85. data/doc/classes/TaskJuggler/RichText.html +537 -0
  86. data/doc/classes/TaskJuggler/RichTextAttribute.html +229 -0
  87. data/doc/classes/TaskJuggler/RichTextDocument.html +418 -0
  88. data/doc/classes/TaskJuggler/RichTextElement.html +829 -0
  89. data/doc/classes/TaskJuggler/RichTextException.html +212 -0
  90. data/doc/classes/TaskJuggler/RichTextParser.html +317 -0
  91. data/doc/classes/TaskJuggler/RichTextProtocolExample.html +303 -0
  92. data/doc/classes/TaskJuggler/RichTextProtocolHandler.html +194 -0
  93. data/doc/classes/TaskJuggler/RichTextScanner.html +561 -0
  94. data/doc/classes/TaskJuggler/RichTextSnip.html +364 -0
  95. data/doc/classes/TaskJuggler/RichTextSyntaxRules.html +883 -0
  96. data/doc/classes/TaskJuggler/Scenario.html +163 -0
  97. data/doc/classes/TaskJuggler/ScenarioData.html +354 -0
  98. data/doc/classes/TaskJuggler/Scoreboard.html +638 -0
  99. data/doc/classes/TaskJuggler/Shift.html +255 -0
  100. data/doc/classes/TaskJuggler/ShiftAssignment.html +488 -0
  101. data/doc/classes/TaskJuggler/ShiftAssignments.html +715 -0
  102. data/doc/classes/TaskJuggler/ShiftAssignmentsAttribute.html +261 -0
  103. data/doc/classes/TaskJuggler/ShiftScenario.html +282 -0
  104. data/doc/classes/TaskJuggler/SourceFileInfo.html +247 -0
  105. data/doc/classes/TaskJuggler/StringAttribute.html +229 -0
  106. data/doc/classes/TaskJuggler/SymbolAttribute.html +194 -0
  107. data/doc/classes/TaskJuggler/SyntaxReference.html +516 -0
  108. data/doc/classes/TaskJuggler/TOCEntry.html +242 -0
  109. data/doc/classes/TaskJuggler/TableColumnDefinition.html +273 -0
  110. data/doc/classes/TaskJuggler/TableOfContents.html +256 -0
  111. data/doc/classes/TaskJuggler/Task.html +203 -0
  112. data/doc/classes/TaskJuggler/TaskDependency.html +251 -0
  113. data/doc/classes/TaskJuggler/TaskListAttribute.html +267 -0
  114. data/doc/classes/TaskJuggler/TaskListRE.html +250 -0
  115. data/doc/classes/TaskJuggler/TaskScenario.html +2206 -0
  116. data/doc/classes/TaskJuggler/TextParser.html +670 -0
  117. data/doc/classes/TaskJuggler/TextParser/Pattern.html +923 -0
  118. data/doc/classes/TaskJuggler/TextParser/Rule.html +779 -0
  119. data/doc/classes/TaskJuggler/TextParser/StackElement.html +267 -0
  120. data/doc/classes/TaskJuggler/TextParser/TextParserResultArray.html +212 -0
  121. data/doc/classes/TaskJuggler/TextParser/TokenDoc.html +221 -0
  122. data/doc/classes/TaskJuggler/TextScanner.html +708 -0
  123. data/doc/classes/TaskJuggler/TextScanner/BufferStreamHandle.html +355 -0
  124. data/doc/classes/TaskJuggler/TextScanner/FileStreamHandle.html +341 -0
  125. data/doc/classes/TaskJuggler/TextScanner/StreamHandle.html +260 -0
  126. data/doc/classes/TaskJuggler/TjException.html +185 -0
  127. data/doc/classes/TaskJuggler/TjTime.html +1845 -0
  128. data/doc/classes/TaskJuggler/TjpExample.html +310 -0
  129. data/doc/classes/TaskJuggler/TjpExportRE.html +329 -0
  130. data/doc/classes/TaskJuggler/TjpSyntaxRules.html +8928 -0
  131. data/doc/classes/TaskJuggler/UserManual.html +606 -0
  132. data/doc/classes/TaskJuggler/WorkingHours.html +582 -0
  133. data/doc/classes/TaskJuggler/WorkingHoursAttribute.html +284 -0
  134. data/doc/classes/TaskJuggler/XMLBlob.html +207 -0
  135. data/doc/classes/TaskJuggler/XMLComment.html +206 -0
  136. data/doc/classes/TaskJuggler/XMLDocument.html +293 -0
  137. data/doc/classes/TaskJuggler/XMLElement.html +341 -0
  138. data/doc/classes/TaskJuggler/XMLNamedText.html +174 -0
  139. data/doc/classes/TaskJuggler/XMLText.html +221 -0
  140. data/doc/files/COPYING.html +448 -0
  141. data/doc/files/README.html +134 -0
  142. data/doc/files/lib/AccountScenario_rb.html +116 -0
  143. data/doc/files/lib/Account_rb.html +118 -0
  144. data/doc/files/lib/Allocation_rb.html +118 -0
  145. data/doc/files/lib/AppConfig_rb.html +116 -0
  146. data/doc/files/lib/AttributeBase_rb.html +106 -0
  147. data/doc/files/lib/AttributeDefinition_rb.html +106 -0
  148. data/doc/files/lib/Attributes_rb.html +130 -0
  149. data/doc/files/lib/Booking_rb.html +106 -0
  150. data/doc/files/lib/ChargeSet_rb.html +116 -0
  151. data/doc/files/lib/Charge_rb.html +116 -0
  152. data/doc/files/lib/HTMLDocument_rb.html +116 -0
  153. data/doc/files/lib/Interval_rb.html +116 -0
  154. data/doc/files/lib/KeywordDocumentation_rb.html +122 -0
  155. data/doc/files/lib/Limits_rb.html +116 -0
  156. data/doc/files/lib/Log_rb.html +116 -0
  157. data/doc/files/lib/LogicalExpression_rb.html +122 -0
  158. data/doc/files/lib/LogicalFlag_rb.html +116 -0
  159. data/doc/files/lib/LogicalFunction_rb.html +116 -0
  160. data/doc/files/lib/LogicalOperation_rb.html +116 -0
  161. data/doc/files/lib/MacroParser_rb.html +118 -0
  162. data/doc/files/lib/MacroTable_rb.html +122 -0
  163. data/doc/files/lib/MessageHandler_rb.html +106 -0
  164. data/doc/files/lib/Message_rb.html +116 -0
  165. data/doc/files/lib/ProjectFileParser_rb.html +122 -0
  166. data/doc/files/lib/Project_rb.html +148 -0
  167. data/doc/files/lib/PropertyList_rb.html +106 -0
  168. data/doc/files/lib/PropertySet_rb.html +118 -0
  169. data/doc/files/lib/PropertyTreeNode_rb.html +106 -0
  170. data/doc/files/lib/Query_rb.html +116 -0
  171. data/doc/files/lib/RealFormat_rb.html +106 -0
  172. data/doc/files/lib/ResourceScenario_rb.html +116 -0
  173. data/doc/files/lib/Resource_rb.html +118 -0
  174. data/doc/files/lib/RichTextDocument_rb.html +120 -0
  175. data/doc/files/lib/RichTextElement_rb.html +120 -0
  176. data/doc/files/lib/RichTextParser_rb.html +120 -0
  177. data/doc/files/lib/RichTextProtocolExample_rb.html +120 -0
  178. data/doc/files/lib/RichTextProtocolHandler_rb.html +106 -0
  179. data/doc/files/lib/RichTextScanner_rb.html +116 -0
  180. data/doc/files/lib/RichTextSnip_rb.html +118 -0
  181. data/doc/files/lib/RichTextSyntaxRules_rb.html +106 -0
  182. data/doc/files/lib/RichText_rb.html +118 -0
  183. data/doc/files/lib/ScenarioData_rb.html +118 -0
  184. data/doc/files/lib/Scenario_rb.html +116 -0
  185. data/doc/files/lib/Scoreboard_rb.html +106 -0
  186. data/doc/files/lib/ShiftAssignments_rb.html +116 -0
  187. data/doc/files/lib/ShiftScenario_rb.html +116 -0
  188. data/doc/files/lib/Shift_rb.html +118 -0
  189. data/doc/files/lib/SourceFileInfo_rb.html +106 -0
  190. data/doc/files/lib/SyntaxReference_rb.html +122 -0
  191. data/doc/files/lib/TOCEntry_rb.html +118 -0
  192. data/doc/files/lib/TableColumnDefinition_rb.html +106 -0
  193. data/doc/files/lib/TableOfContents_rb.html +118 -0
  194. data/doc/files/lib/TaskDependency_rb.html +106 -0
  195. data/doc/files/lib/TaskJuggler_rb.html +120 -0
  196. data/doc/files/lib/TaskScenario_rb.html +116 -0
  197. data/doc/files/lib/Task_rb.html +118 -0
  198. data/doc/files/lib/TextParser/Pattern_rb.html +116 -0
  199. data/doc/files/lib/TextParser/Rule_rb.html +106 -0
  200. data/doc/files/lib/TextParser/StackElement_rb.html +106 -0
  201. data/doc/files/lib/TextParser/TokenDoc_rb.html +106 -0
  202. data/doc/files/lib/TextParser_rb.html +124 -0
  203. data/doc/files/lib/TextScanner_rb.html +128 -0
  204. data/doc/files/lib/Tj3Config_rb.html +118 -0
  205. data/doc/files/lib/TjException_rb.html +106 -0
  206. data/doc/files/lib/TjTime_rb.html +118 -0
  207. data/doc/files/lib/TjpExample_rb.html +116 -0
  208. data/doc/files/lib/TjpSyntaxRules_rb.html +106 -0
  209. data/doc/files/lib/UTF8String_rb.html +132 -0
  210. data/doc/files/lib/UserManual_rb.html +124 -0
  211. data/doc/files/lib/WorkingHours_rb.html +116 -0
  212. data/doc/files/lib/XMLDocument_rb.html +116 -0
  213. data/doc/files/lib/XMLElement_rb.html +116 -0
  214. data/doc/files/lib/reports/CSVFile_rb.html +116 -0
  215. data/doc/files/lib/reports/ColumnTable_rb.html +116 -0
  216. data/doc/files/lib/reports/GanttChart_rb.html +122 -0
  217. data/doc/files/lib/reports/GanttContainer_rb.html +116 -0
  218. data/doc/files/lib/reports/GanttHeaderScaleItem_rb.html +106 -0
  219. data/doc/files/lib/reports/GanttHeader_rb.html +116 -0
  220. data/doc/files/lib/reports/GanttLine_rb.html +126 -0
  221. data/doc/files/lib/reports/GanttLoadStack_rb.html +116 -0
  222. data/doc/files/lib/reports/GanttMilestone_rb.html +116 -0
  223. data/doc/files/lib/reports/GanttRouter_rb.html +106 -0
  224. data/doc/files/lib/reports/GanttTaskBar_rb.html +116 -0
  225. data/doc/files/lib/reports/HTMLGraphics_rb.html +106 -0
  226. data/doc/files/lib/reports/ReportElement_rb.html +118 -0
  227. data/doc/files/lib/reports/ReportTableCell_rb.html +106 -0
  228. data/doc/files/lib/reports/ReportTableColumn_rb.html +106 -0
  229. data/doc/files/lib/reports/ReportTableElement_rb.html +122 -0
  230. data/doc/files/lib/reports/ReportTableLegend_rb.html +106 -0
  231. data/doc/files/lib/reports/ReportTableLine_rb.html +116 -0
  232. data/doc/files/lib/reports/ReportTable_rb.html +118 -0
  233. data/doc/files/lib/reports/Report_rb.html +126 -0
  234. data/doc/files/lib/reports/ResourceListRE_rb.html +122 -0
  235. data/doc/files/lib/reports/TaskListRE_rb.html +122 -0
  236. data/doc/files/lib/reports/TjpExportRE_rb.html +116 -0
  237. data/doc/files/lib/taskjuggler3_rb.html +276 -0
  238. data/doc/files/lib/tj3man_rb.html +189 -0
  239. data/doc/fr_class_index.html +285 -0
  240. data/doc/fr_file_index.html +223 -0
  241. data/doc/fr_method_index.html +1953 -0
  242. data/doc/index.html +21 -0
  243. data/doc/rdoc-style.css +299 -0
  244. data/examples/tutorial.tjp +361 -0
  245. data/gem_spec.rb +30 -0
  246. data/lib/Account.rb +50 -0
  247. data/lib/AccountScenario.rb +39 -0
  248. data/lib/Allocation.rb +102 -0
  249. data/lib/AppConfig.rb +134 -0
  250. data/lib/AttributeBase.rb +131 -0
  251. data/lib/AttributeDefinition.rb +47 -0
  252. data/lib/Attributes.rb +478 -0
  253. data/lib/BatchProcessor.rb +209 -0
  254. data/lib/Booking.rb +59 -0
  255. data/lib/Charge.rb +71 -0
  256. data/lib/ChargeSet.rb +126 -0
  257. data/lib/HTMLDocument.rb +59 -0
  258. data/lib/Interval.rb +127 -0
  259. data/lib/KeywordDocumentation.rb +560 -0
  260. data/lib/Limits.rb +219 -0
  261. data/lib/Log.rb +160 -0
  262. data/lib/LogicalExpression.rb +71 -0
  263. data/lib/LogicalFlag.rb +34 -0
  264. data/lib/LogicalFunction.rb +102 -0
  265. data/lib/LogicalOperation.rb +118 -0
  266. data/lib/MacroParser.rb +77 -0
  267. data/lib/MacroTable.rb +84 -0
  268. data/lib/Message.rb +56 -0
  269. data/lib/MessageHandler.rb +34 -0
  270. data/lib/Project.rb +662 -0
  271. data/lib/ProjectFileParser.rb +333 -0
  272. data/lib/PropertyList.rb +181 -0
  273. data/lib/PropertySet.rb +304 -0
  274. data/lib/PropertyTreeNode.rb +461 -0
  275. data/lib/Query.rb +227 -0
  276. data/lib/RealFormat.rb +73 -0
  277. data/lib/Resource.rb +42 -0
  278. data/lib/ResourceScenario.rb +511 -0
  279. data/lib/RichText.rb +147 -0
  280. data/lib/RichTextDocument.rb +139 -0
  281. data/lib/RichTextElement.rb +391 -0
  282. data/lib/RichTextParser.rb +66 -0
  283. data/lib/RichTextProtocolExample.rb +65 -0
  284. data/lib/RichTextProtocolHandler.rb +35 -0
  285. data/lib/RichTextScanner.rb +390 -0
  286. data/lib/RichTextSnip.rb +104 -0
  287. data/lib/RichTextSyntaxRules.rb +265 -0
  288. data/lib/Scenario.rb +27 -0
  289. data/lib/ScenarioData.rb +65 -0
  290. data/lib/Scoreboard.rb +141 -0
  291. data/lib/Shift.rb +48 -0
  292. data/lib/ShiftAssignments.rb +291 -0
  293. data/lib/ShiftScenario.rb +46 -0
  294. data/lib/SourceFileInfo.rb +37 -0
  295. data/lib/SyntaxReference.rb +284 -0
  296. data/lib/TOCEntry.rb +76 -0
  297. data/lib/TableColumnDefinition.rb +54 -0
  298. data/lib/TableOfContents.rb +46 -0
  299. data/lib/Task.rb +37 -0
  300. data/lib/TaskDependency.rb +39 -0
  301. data/lib/TaskJuggler.rb +84 -0
  302. data/lib/TaskScenario.rb +1622 -0
  303. data/lib/TextParser.rb +416 -0
  304. data/lib/TextParser/Pattern.rb +263 -0
  305. data/lib/TextParser/Rule.rb +171 -0
  306. data/lib/TextParser/StackElement.rb +45 -0
  307. data/lib/TextParser/TokenDoc.rb +38 -0
  308. data/lib/TextScanner.rb +682 -0
  309. data/lib/Tj3Config.rb +27 -0
  310. data/lib/TjException.rb +27 -0
  311. data/lib/TjTime.rb +395 -0
  312. data/lib/TjpExample.rb +119 -0
  313. data/lib/TjpSyntaxRules.rb +4022 -0
  314. data/lib/UTF8String.rb +100 -0
  315. data/lib/UserManual.rb +282 -0
  316. data/lib/WorkingHours.rb +323 -0
  317. data/lib/XMLDocument.rb +54 -0
  318. data/lib/XMLElement.rb +175 -0
  319. data/lib/reports/CSVFile.rb +146 -0
  320. data/lib/reports/ColumnTable.rb +66 -0
  321. data/lib/reports/GanttChart.rb +308 -0
  322. data/lib/reports/GanttContainer.rb +107 -0
  323. data/lib/reports/GanttHeader.rb +141 -0
  324. data/lib/reports/GanttHeaderScaleItem.rb +42 -0
  325. data/lib/reports/GanttLine.rb +329 -0
  326. data/lib/reports/GanttLoadStack.rb +113 -0
  327. data/lib/reports/GanttMilestone.rb +80 -0
  328. data/lib/reports/GanttRouter.rb +375 -0
  329. data/lib/reports/GanttTaskBar.rb +95 -0
  330. data/lib/reports/HTMLGraphics.rb +65 -0
  331. data/lib/reports/Report.rb +344 -0
  332. data/lib/reports/ReportElement.rb +427 -0
  333. data/lib/reports/ReportTable.rb +144 -0
  334. data/lib/reports/ReportTableCell.rb +142 -0
  335. data/lib/reports/ReportTableColumn.rb +82 -0
  336. data/lib/reports/ReportTableElement.rb +852 -0
  337. data/lib/reports/ReportTableLegend.rb +167 -0
  338. data/lib/reports/ReportTableLine.rb +87 -0
  339. data/lib/reports/ResourceListRE.rb +72 -0
  340. data/lib/reports/TaskListRE.rb +73 -0
  341. data/lib/reports/TjpExportRE.rb +394 -0
  342. data/lib/taskjuggler3.rb +106 -0
  343. data/lib/tj3man.rb +88 -0
  344. data/manual/Day_To_Day_Juggling +168 -0
  345. data/manual/Getting_Started +61 -0
  346. data/manual/How_To_Contribute +185 -0
  347. data/manual/Installation +68 -0
  348. data/manual/Intro +102 -0
  349. data/manual/Reporting_Bugs +26 -0
  350. data/manual/Rich_Text_Attributes +90 -0
  351. data/manual/TaskJuggler_2x_Migration +40 -0
  352. data/manual/Tutorial +579 -0
  353. data/manual/fdl +450 -0
  354. data/prj_cfg.rb +43 -0
  355. data/setup.rb +1585 -0
  356. data/tasks/csts.rake +72 -0
  357. data/tasks/gem.rake +14 -0
  358. data/tasks/manual.rake +10 -0
  359. data/tasks/missing.rake +21 -0
  360. data/tasks/rcov.rake +14 -0
  361. data/tasks/rdoc.rake +17 -0
  362. data/tasks/rexml_fix.rb +16 -0
  363. data/tasks/rexml_fix_19.rb +49 -0
  364. data/tasks/show.rake +21 -0
  365. data/tasks/stats.rake +25 -0
  366. data/tasks/test.rake +11 -0
  367. data/test/MessageChecker.rb +53 -0
  368. data/test/TestSuite/CSV-Reports/celltext-Reference.csv +14 -0
  369. data/test/TestSuite/CSV-Reports/celltext.tjp +7 -0
  370. data/test/TestSuite/CSV-Reports/genrefs +6 -0
  371. data/test/TestSuite/CSV-Reports/project-1.tji +57 -0
  372. data/test/TestSuite/CSV-Reports/resourcereport-Reference.csv +4 -0
  373. data/test/TestSuite/CSV-Reports/resourcereport.tjp +10 -0
  374. data/test/TestSuite/CSV-Reports/resourcereport_with_tasks-Reference.csv +22 -0
  375. data/test/TestSuite/CSV-Reports/resourcereport_with_tasks.tjp +11 -0
  376. data/test/TestSuite/CSV-Reports/sortByTree-Reference.csv +14 -0
  377. data/test/TestSuite/CSV-Reports/sortByTree.tjp +8 -0
  378. data/test/TestSuite/CSV-Reports/sortBy_duration.down-Reference.csv +14 -0
  379. data/test/TestSuite/CSV-Reports/sortBy_duration.down.tjp +10 -0
  380. data/test/TestSuite/CSV-Reports/sortBy_effort.up-Reference.csv +14 -0
  381. data/test/TestSuite/CSV-Reports/sortBy_effort.up.tjp +10 -0
  382. data/test/TestSuite/CSV-Reports/sortBy_plan.start.down-Reference.csv +14 -0
  383. data/test/TestSuite/CSV-Reports/sortBy_plan.start.down.tjp +10 -0
  384. data/test/TestSuite/CSV-Reports/taskreport-Reference.csv +14 -0
  385. data/test/TestSuite/CSV-Reports/taskreport.tjp +10 -0
  386. data/test/TestSuite/CSV-Reports/taskreport_with_resources-Reference.csv +24 -0
  387. data/test/TestSuite/CSV-Reports/taskreport_with_resources.tjp +11 -0
  388. data/test/TestSuite/Scheduler/Correct/Allocate.tjp +86 -0
  389. data/test/TestSuite/Scheduler/Correct/AutomaticMilestones.tjp +63 -0
  390. data/test/TestSuite/Scheduler/Correct/Booking.tjp +161 -0
  391. data/test/TestSuite/Scheduler/Correct/Depends.tjp +50 -0
  392. data/test/TestSuite/Scheduler/Correct/Duration.tjp +34 -0
  393. data/test/TestSuite/Scheduler/Correct/InheritStartEnd.tjp +129 -0
  394. data/test/TestSuite/Scheduler/Correct/Limits.tjp +81 -0
  395. data/test/TestSuite/Scheduler/Correct/MultipleMandatories.tjp +43 -0
  396. data/test/TestSuite/Scheduler/Correct/Optimize-1.tjp +28 -0
  397. data/test/TestSuite/Scheduler/Correct/Optimize-2.tjp +33 -0
  398. data/test/TestSuite/Scheduler/Correct/Optimize-3.tjp +33 -0
  399. data/test/TestSuite/Scheduler/Correct/Optimize-4.tjp +34 -0
  400. data/test/TestSuite/Scheduler/Correct/Optimize-5.tjp +62 -0
  401. data/test/TestSuite/Scheduler/Correct/Precedes.tjp +50 -0
  402. data/test/TestSuite/Scheduler/Correct/Shift.tjp +102 -0
  403. data/test/TestSuite/Scheduler/Errors/account_no_leaf.tjp +13 -0
  404. data/test/TestSuite/Scheduler/Errors/booking_conflict.tjp +10 -0
  405. data/test/TestSuite/Scheduler/Errors/booking_no_duty.tjp +9 -0
  406. data/test/TestSuite/Scheduler/Errors/booking_on_vacation.tjp +9 -0
  407. data/test/TestSuite/Scheduler/Errors/container_booking.tjp +14 -0
  408. data/test/TestSuite/Scheduler/Errors/container_duration.tjp +11 -0
  409. data/test/TestSuite/Scheduler/Errors/effort_no_allocations.tjp +7 -0
  410. data/test/TestSuite/Scheduler/Errors/loop_detected_1.tjp +19 -0
  411. data/test/TestSuite/Scheduler/Errors/loop_detected_10.tjp +36 -0
  412. data/test/TestSuite/Scheduler/Errors/loop_detected_11.tjp +27 -0
  413. data/test/TestSuite/Scheduler/Errors/loop_detected_12.tjp +20 -0
  414. data/test/TestSuite/Scheduler/Errors/loop_detected_13.tjp +27 -0
  415. data/test/TestSuite/Scheduler/Errors/loop_detected_14.tjp +26 -0
  416. data/test/TestSuite/Scheduler/Errors/loop_detected_2.tjp +24 -0
  417. data/test/TestSuite/Scheduler/Errors/loop_detected_3.tjp +18 -0
  418. data/test/TestSuite/Scheduler/Errors/loop_detected_4.tjp +36 -0
  419. data/test/TestSuite/Scheduler/Errors/loop_detected_5.tjp +37 -0
  420. data/test/TestSuite/Scheduler/Errors/loop_detected_6.tjp +35 -0
  421. data/test/TestSuite/Scheduler/Errors/loop_detected_7.tjp +46 -0
  422. data/test/TestSuite/Scheduler/Errors/loop_detected_8.tjp +51 -0
  423. data/test/TestSuite/Scheduler/Errors/loop_detected_9.tjp +20 -0
  424. data/test/TestSuite/Scheduler/Errors/maxend.tjp +8 -0
  425. data/test/TestSuite/Scheduler/Errors/maxstart.tjp +8 -0
  426. data/test/TestSuite/Scheduler/Errors/milestone_booking.tjp +10 -0
  427. data/test/TestSuite/Scheduler/Errors/milestone_duration.tjp +8 -0
  428. data/test/TestSuite/Scheduler/Errors/milestone_start_end.tjp +8 -0
  429. data/test/TestSuite/Scheduler/Errors/minend.tjp +8 -0
  430. data/test/TestSuite/Scheduler/Errors/minstart.tjp +8 -0
  431. data/test/TestSuite/Scheduler/Errors/multiple_durations.tjp +11 -0
  432. data/test/TestSuite/Scheduler/Errors/no_tasks.tjp +6 -0
  433. data/test/TestSuite/Scheduler/Errors/not_scheduled.tjp +8 -0
  434. data/test/TestSuite/Scheduler/Errors/task_depend_child.tjp +10 -0
  435. data/test/TestSuite/Scheduler/Errors/task_depend_multi.tjp +13 -0
  436. data/test/TestSuite/Scheduler/Errors/task_depend_parent.tjp +11 -0
  437. data/test/TestSuite/Scheduler/Errors/task_depend_self.tjp +10 -0
  438. data/test/TestSuite/Scheduler/Errors/task_depend_unknown.tjp +10 -0
  439. data/test/TestSuite/Scheduler/Errors/task_overspecified_1.tjp +9 -0
  440. data/test/TestSuite/Scheduler/Errors/task_overspecified_2.tjp +14 -0
  441. data/test/TestSuite/Scheduler/Errors/task_overspecified_3.tjp +14 -0
  442. data/test/TestSuite/Scheduler/Errors/task_underspecified_1.tjp +8 -0
  443. data/test/TestSuite/Scheduler/Errors/task_underspecified_2.tjp +8 -0
  444. data/test/TestSuite/Scheduler/Errors/task_underspecified_3.tjp +9 -0
  445. data/test/TestSuite/Syntax/Correct/Account.tjp +53 -0
  446. data/test/TestSuite/Syntax/Correct/Allocate-1.tjp +24 -0
  447. data/test/TestSuite/Syntax/Correct/Alternative.tjp +13 -0
  448. data/test/TestSuite/Syntax/Correct/AutoMacros.tjp +14 -0
  449. data/test/TestSuite/Syntax/Correct/Booking.tjp +26 -0
  450. data/test/TestSuite/Syntax/Correct/Caption.tjp +33 -0
  451. data/test/TestSuite/Syntax/Correct/Celltext.tjp +28 -0
  452. data/test/TestSuite/Syntax/Correct/Comments.tjp +29 -0
  453. data/test/TestSuite/Syntax/Correct/Complete.tjp +16 -0
  454. data/test/TestSuite/Syntax/Correct/CompletedWork.tji +20 -0
  455. data/test/TestSuite/Syntax/Correct/CriticalPath.tjp +31 -0
  456. data/test/TestSuite/Syntax/Correct/Currencyformat.tjp +12 -0
  457. data/test/TestSuite/Syntax/Correct/CustomAttributes.tjp +14 -0
  458. data/test/TestSuite/Syntax/Correct/Depends1.tjp +22 -0
  459. data/test/TestSuite/Syntax/Correct/Durations.tjp +29 -0
  460. data/test/TestSuite/Syntax/Correct/Efficiency.tjp +19 -0
  461. data/test/TestSuite/Syntax/Correct/Export.tjp +40 -0
  462. data/test/TestSuite/Syntax/Correct/Flags.tjp +32 -0
  463. data/test/TestSuite/Syntax/Correct/Freeze.tjp +28 -0
  464. data/test/TestSuite/Syntax/Correct/Gap.tjp +15 -0
  465. data/test/TestSuite/Syntax/Correct/HtmlTaskReport.tjp +33 -0
  466. data/test/TestSuite/Syntax/Correct/Limits-1.tjp +71 -0
  467. data/test/TestSuite/Syntax/Correct/LoadUnits.tjp +31 -0
  468. data/test/TestSuite/Syntax/Correct/Macro-1.tjp +19 -0
  469. data/test/TestSuite/Syntax/Correct/Mandatory.tjp +17 -0
  470. data/test/TestSuite/Syntax/Correct/Milestone.tjp +12 -0
  471. data/test/TestSuite/Syntax/Correct/MinMax.tjp +17 -0
  472. data/test/TestSuite/Syntax/Correct/Numberformat.tjp +12 -0
  473. data/test/TestSuite/Syntax/Correct/Period.tjp +16 -0
  474. data/test/TestSuite/Syntax/Correct/Persistent.tjp +11 -0
  475. data/test/TestSuite/Syntax/Correct/Precedes1.tjp +17 -0
  476. data/test/TestSuite/Syntax/Correct/Priority.tjp +30 -0
  477. data/test/TestSuite/Syntax/Correct/Project.tjp +21 -0
  478. data/test/TestSuite/Syntax/Correct/ProjectIDs.tjp +23 -0
  479. data/test/TestSuite/Syntax/Correct/RawHTML.tjp +29 -0
  480. data/test/TestSuite/Syntax/Correct/Reports.tjp +54 -0
  481. data/test/TestSuite/Syntax/Correct/Resource.tjp +20 -0
  482. data/test/TestSuite/Syntax/Correct/Responsible.tjp +16 -0
  483. data/test/TestSuite/Syntax/Correct/Scenario.tjp +15 -0
  484. data/test/TestSuite/Syntax/Correct/Scheduling.tjp +26 -0
  485. data/test/TestSuite/Syntax/Correct/Select.tjp +27 -0
  486. data/test/TestSuite/Syntax/Correct/Shift.tjp +55 -0
  487. data/test/TestSuite/Syntax/Correct/Simple.tjp +25 -0
  488. data/test/TestSuite/Syntax/Correct/String.tjp +12 -0
  489. data/test/TestSuite/Syntax/Correct/Supplement.tjp +24 -0
  490. data/test/TestSuite/Syntax/Correct/TaskRoot.tjp +34 -0
  491. data/test/TestSuite/Syntax/Correct/TimeFrame.tjp +19 -0
  492. data/test/TestSuite/Syntax/Correct/Timezone.tjp +8 -0
  493. data/test/TestSuite/Syntax/Correct/Vacation.tjp +33 -0
  494. data/test/TestSuite/Syntax/Correct/csvtest +16 -0
  495. data/test/TestSuite/Syntax/Correct/manual2example.rb +24 -0
  496. data/test/TestSuite/Syntax/Correct/tutorial.tjp +485 -0
  497. data/test/TestSuite/Syntax/Errors/bad_include.tjp +11 -0
  498. data/test/TestSuite/Syntax/Errors/booking_group.tjp +14 -0
  499. data/test/TestSuite/Syntax/Errors/booking_milestone.tjp +13 -0
  500. data/test/TestSuite/Syntax/Errors/booking_no_leaf.tjp +13 -0
  501. data/test/TestSuite/Syntax/Errors/chargeset.tjp +14 -0
  502. data/test/TestSuite/Syntax/Errors/chargeset_master.tjp +15 -0
  503. data/test/TestSuite/Syntax/Errors/container_attribute.tjp +13 -0
  504. data/test/TestSuite/Syntax/Errors/cost_acct_no_top.tjp +24 -0
  505. data/test/TestSuite/Syntax/Errors/cost_rev_same.tjp +24 -0
  506. data/test/TestSuite/Syntax/Errors/date_in_range.tjp +7 -0
  507. data/test/TestSuite/Syntax/Errors/effort_zero.tjp +8 -0
  508. data/test/TestSuite/Syntax/Errors/empty.tjp +1 -0
  509. data/test/TestSuite/Syntax/Errors/export_bad_extn.tjp +9 -0
  510. data/test/TestSuite/Syntax/Errors/extend_id_cap.tjp +7 -0
  511. data/test/TestSuite/Syntax/Errors/interval_end_in_range.tjp +7 -0
  512. data/test/TestSuite/Syntax/Errors/interval_start_in_range.tjp +7 -0
  513. data/test/TestSuite/Syntax/Errors/leaf_resource_id_expected.tjp +12 -0
  514. data/test/TestSuite/Syntax/Errors/misaligned_date.tjp +7 -0
  515. data/test/TestSuite/Syntax/Errors/no_csv_suffix.tjp +10 -0
  516. data/test/TestSuite/Syntax/Errors/no_html_suffix.tjp +10 -0
  517. data/test/TestSuite/Syntax/Errors/operand_attribute.tjp +11 -0
  518. data/test/TestSuite/Syntax/Errors/operand_unkn_flag.tjp +11 -0
  519. data/test/TestSuite/Syntax/Errors/operand_unkn_scen.tjp +11 -0
  520. data/test/TestSuite/Syntax/Errors/overtime_range.tjp +13 -0
  521. data/test/TestSuite/Syntax/Errors/purge_no_list.tjp +8 -0
  522. data/test/TestSuite/Syntax/Errors/purge_unknown_id.tjp +8 -0
  523. data/test/TestSuite/Syntax/Errors/report_end.tjp +10 -0
  524. data/test/TestSuite/Syntax/Errors/report_redifinition.tjp +10 -0
  525. data/test/TestSuite/Syntax/Errors/report_start.tjp +10 -0
  526. data/test/TestSuite/Syntax/Errors/resource_exists.tjp +7 -0
  527. data/test/TestSuite/Syntax/Errors/resource_id_expected.tjp +8 -0
  528. data/test/TestSuite/Syntax/Errors/rev_acct_no_top.tjp +24 -0
  529. data/test/TestSuite/Syntax/Errors/scenario_exists.tjp +7 -0
  530. data/test/TestSuite/Syntax/Errors/shift_assignment_overlap.tjp +15 -0
  531. data/test/TestSuite/Syntax/Errors/shift_exists.tjp +7 -0
  532. data/test/TestSuite/Syntax/Errors/shift_id_expected.tjp +7 -0
  533. data/test/TestSuite/Syntax/Errors/sloppy_range.tjp +13 -0
  534. data/test/TestSuite/Syntax/Errors/sort_direction.tjp +11 -0
  535. data/test/TestSuite/Syntax/Errors/sort_unknown_scen.tjp +11 -0
  536. data/test/TestSuite/Syntax/Errors/sorting_crit_exptd1.tjp +11 -0
  537. data/test/TestSuite/Syntax/Errors/sorting_crit_exptd2.tjp +11 -0
  538. data/test/TestSuite/Syntax/Errors/sorting_wbs.tjp +11 -0
  539. data/test/TestSuite/Syntax/Errors/start_before_end1.tjp +7 -0
  540. data/test/TestSuite/Syntax/Errors/start_before_end2.tjp +6 -0
  541. data/test/TestSuite/Syntax/Errors/task_complete.tjp +8 -0
  542. data/test/TestSuite/Syntax/Errors/task_exists.tjp +7 -0
  543. data/test/TestSuite/Syntax/Errors/task_priority.tjp +8 -0
  544. data/test/TestSuite/Syntax/Errors/task_without_chargeset.tjp +9 -0
  545. data/test/TestSuite/Syntax/Errors/time_interval.tjp +12 -0
  546. data/test/TestSuite/Syntax/Errors/too_many_bangs.tjp +10 -0
  547. data/test/TestSuite/Syntax/Errors/undecl_flag.tjp +6 -0
  548. data/test/TestSuite/Syntax/Errors/unknown_projectid.tjp +7 -0
  549. data/test/TestSuite/Syntax/Errors/unknown_scenario_id.tjp +6 -0
  550. data/test/TestSuite/Syntax/Errors/unknown_scenario_idx.tjp +11 -0
  551. data/test/TestSuite/Syntax/Errors/unknown_task.tjp +10 -0
  552. data/test/all.rb +31 -0
  553. data/test/test_BatchProcessor.rb +54 -0
  554. data/test/test_CSV-Reports.rb +101 -0
  555. data/test/test_Limits.rb +104 -0
  556. data/test/test_LogicalExpression.rb +110 -0
  557. data/test/test_MacroTable.rb +51 -0
  558. data/test/test_Project.rb +57 -0
  559. data/test/test_PropertySet.rb +71 -0
  560. data/test/test_Query.rb +83 -0
  561. data/test/test_RealFormat.rb +83 -0
  562. data/test/test_RichText.rb +869 -0
  563. data/test/test_Scheduler.rb +42 -0
  564. data/test/test_ShiftAssignments.rb +77 -0
  565. data/test/test_Syntax.rb +41 -0
  566. data/test/test_TextScanner.rb +95 -0
  567. data/test/test_TjTime.rb +114 -0
  568. data/test/test_TjpExample.rb +169 -0
  569. data/test/test_UTF8String.rb +84 -0
  570. data/test/test_WorkingHours.rb +56 -0
  571. metadata +649 -0
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = TableOfContents.rb -- The TaskJuggler III Project Management Software
5
+ #
6
+ # Copyright (c) 2006, 2007, 2008, 2009 by Chris Schlaeger <cs@kde.org>
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of version 2 of the GNU General Public License as
10
+ # published by the Free Software Foundation.
11
+ #
12
+
13
+ require 'XMLElement'
14
+ require 'TOCEntry'
15
+
16
+ class TaskJuggler
17
+
18
+ # This class can be used to store a table of contents. It's just an Array of
19
+ # TOCEntry objects. Each TOCEntry objects represents the title of a section.
20
+ class TableOfContents
21
+
22
+ # Create an empty TableOfContents object.
23
+ def initialize
24
+ @entries = []
25
+ end
26
+
27
+ # This method must be used to add new TOCEntry objects to the
28
+ # TableOfContents. _entry_ must be a TOCEntry object reference.
29
+ def addEntry(entry)
30
+ @entries << entry
31
+ end
32
+
33
+ # Return HTML elements that represent the content of the TableOfContents
34
+ # object. The result is a tree of XMLElement objects.
35
+ def to_html
36
+ div = XMLElement.new('div', 'style' => 'margin-left:15%; margin-right:15%;')
37
+ div << (table = XMLElement.new('table'))
38
+ @entries.each { |e| table << e.to_html }
39
+
40
+ div
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = Task.rb -- The TaskJuggler III Project Management Software
5
+ #
6
+ # Copyright (c) 2006, 2007, 2008, 2009 by Chris Schlaeger <cs@kde.org>
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of version 2 of the GNU General Public License as
10
+ # published by the Free Software Foundation.
11
+ #
12
+
13
+ require 'PropertyTreeNode'
14
+ require 'TaskScenario'
15
+
16
+ class TaskJuggler
17
+
18
+ class Task < PropertyTreeNode
19
+
20
+ def initialize(project, id, name, parent)
21
+ super(project.tasks, id, name, parent)
22
+ project.addTask(self)
23
+
24
+ @data = Array.new(@project.scenarioCount, nil)
25
+ @project.scenarioCount.times do |i|
26
+ @data[i] = TaskScenario.new(self, i, @scenarioAttributes[i])
27
+ end
28
+ end
29
+
30
+ def readyForScheduling?(scenarioIdx)
31
+ @data[scenarioIdx].readyForScheduling?
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = TaskDependency.rb -- The TaskJuggler III Project Management Software
5
+ #
6
+ # Copyright (c) 2006, 2007, 2008, 2009 by Chris Schlaeger <cs@kde.org>
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of version 2 of the GNU General Public License as
10
+ # published by the Free Software Foundation.
11
+ #
12
+
13
+ class TaskJuggler
14
+
15
+ class TaskDependency
16
+
17
+ attr_accessor :onEnd, :gapDuration, :gapLength
18
+ attr_reader :taskId, :task
19
+
20
+ def initialize(taskId, onEnd)
21
+ @taskId = taskId
22
+ @task = nil
23
+ # Specifies whether the dependency is relative to the start or the
24
+ # end of the dependent task.
25
+ @onEnd = onEnd
26
+ # The gap duration is stored in seconds of calendar time.
27
+ @gapDuration = 0
28
+ # The gap length is stored in number of scheduling slots.
29
+ @gapLength = 0
30
+ end
31
+
32
+ def resolve(project)
33
+ @task = project.task(@taskId)
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = TaskJuggler.rb -- The TaskJuggler III Project Management Software
5
+ #
6
+ # Copyright (c) 2006, 2007, 2008, 2009 by Chris Schlaeger <cs@kde.org>
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of version 2 of the GNU General Public License as
10
+ # published by the Free Software Foundation.
11
+ #
12
+
13
+ require 'Project'
14
+ require 'MessageHandler'
15
+ require 'Log'
16
+
17
+ # The TaskJuggler class models the object that provides access to the
18
+ # fundamental features of the TaskJuggler software. It can read project
19
+ # files, schedule them and generate the reports.
20
+ class TaskJuggler
21
+
22
+ attr_reader :messageHandler
23
+ attr_accessor :maxCpuCores
24
+
25
+ # Create a new TaskJuggler object. _console_ is a boolean that determines
26
+ # whether or not messsages can be written to $stderr.
27
+ def initialize(console)
28
+ @project = nil
29
+ @messageHandler = MessageHandler.new(console)
30
+ @maxCpuCores = 1
31
+ end
32
+
33
+ # Read in the files passed as file names in _files_, parse them and
34
+ # construct a Project object. In case of success true is returned.
35
+ # Otherwise false.
36
+ def parse(files)
37
+ Log.enter('parser', 'Parsing files ...')
38
+ master = true
39
+ @project = nil
40
+
41
+ parser = ProjectFileParser.new(@messageHandler)
42
+ files.each do |file|
43
+ begin
44
+ parser.open(file)
45
+ rescue TjException
46
+ Log.exit('parser')
47
+ return false
48
+ end
49
+ if master
50
+ @project = parser.parse('project')
51
+ master = false
52
+ else
53
+ parser.setGlobalMacros
54
+ parser.parse('properties')
55
+ end
56
+ parser.close
57
+ end
58
+
59
+ Log.exit('parser')
60
+ @messageHandler.messages.empty?
61
+ end
62
+
63
+ # Schedule all scenarios in the project. Return true if no error was
64
+ # detected, false otherwise.
65
+ def schedule
66
+ Log.enter('scheduler', 'Scheduling project ...')
67
+ #puts @project.to_s
68
+ res = @project.schedule
69
+ Log.exit('scheduler')
70
+ res
71
+ end
72
+
73
+ # Generate all specified reports. The project must have be scheduled before
74
+ # this method can be called. It returns true if no error occured, false
75
+ # otherwise.
76
+ def generateReports
77
+ Log.enter('reports', 'Generating reports ...')
78
+ res = @project.generateReports(@maxCpuCores)
79
+ Log.exit('reports')
80
+ res
81
+ end
82
+
83
+ end
84
+
@@ -0,0 +1,1622 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = TaskScenario.rb -- The TaskJuggler III Project Management Software
5
+ #
6
+ # Copyright (c) 2006, 2007, 2008, 2009 by Chris Schlaeger <cs@kde.org>
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of version 2 of the GNU General Public License as
10
+ # published by the Free Software Foundation.
11
+ #
12
+
13
+ require 'ScenarioData'
14
+
15
+ class TaskJuggler
16
+
17
+ class TaskScenario < ScenarioData
18
+
19
+ attr_reader :isRunAway
20
+
21
+ # Create a new TaskScenario object.
22
+ def initialize(task, scenarioIdx, attributes)
23
+ super
24
+
25
+ # A list of all allocated leaf resources.
26
+ @candidates = []
27
+ end
28
+
29
+ # Call this function to reset all scheduling related data prior to
30
+ # scheduling.
31
+ def prepareScheduling
32
+ @property['startpreds', @scenarioIdx] = []
33
+ @property['startsuccs', @scenarioIdx] =[]
34
+ @property['endpreds', @scenarioIdx] = []
35
+ @property['endsuccs', @scenarioIdx] = []
36
+
37
+ @isRunAway = false
38
+
39
+ # The following variables are only used during scheduling
40
+ @lastSlot = nil
41
+ # The 'done' variables count scheduled values in number of time slots.
42
+ @doneDuration = 0
43
+ @doneLength = 0
44
+ # Due to the 'efficiency' factor the effort slots must be a float.
45
+ @doneEffort = 0.0
46
+
47
+ @startIsDetermed = nil
48
+ @endIsDetermed = nil
49
+ @tentativeStart = @tentativeEnd = nil
50
+
51
+ # To avoid multiple calls to propagateDate() we use these flags to know
52
+ # when we've done it already.
53
+ @startPropagated = false
54
+ @endPropagated = false
55
+
56
+ # Inheriting start or end values is a bit tricky. This should really only
57
+ # happen if the task is a leaf task and scheduled away from the specified
58
+ # date. Since the default meachanism inherites all values, we have to
59
+ # delete the wrong ones again.
60
+ unless @property.provided('start', @scenarioIdx)
61
+ @property['start', @scenarioIdx] = nil
62
+ end
63
+ unless @property.provided('end', @scenarioIdx)
64
+ @property['end', @scenarioIdx] = nil
65
+ end
66
+
67
+ # Collect the limits of this task and all parent tasks into a single
68
+ # Array.
69
+ @limits = []
70
+ task = @property
71
+ # Reset the counters of all limits of this task.
72
+ task['limits', @scenarioIdx].reset if task['limits', @scenarioIdx]
73
+ until task.nil?
74
+ if task['limits', @scenarioIdx]
75
+ @limits << task['limits', @scenarioIdx]
76
+ end
77
+ task = task.parent
78
+ end
79
+
80
+ # Collect the mandatory allocations.
81
+ @mandatories = []
82
+ a('allocate').each do |allocation|
83
+ @mandatories << allocation if allocation.mandatory
84
+ end
85
+
86
+ bookBookings
87
+ markMilestone
88
+ end
89
+
90
+ # The parser only stores the full task IDs for each of the dependencies. This
91
+ # function resolves them to task references and checks them. In addition
92
+ # to the 'depends' and 'precedes' property lists we also keep 4 additional
93
+ # lists.
94
+ # startpreds: All precedessors to the start of this task
95
+ # startsuccs: All successors to the start of this task
96
+ # endpreds: All predecessors to the end of this task
97
+ # endsuccs: All successors to the end of this task
98
+ # Each list element consists of a reference/boolean pair. The reference
99
+ # points to the dependent task and the boolean specifies whether the
100
+ # dependency originates from the end of the task or not.
101
+ def Xref
102
+ @property['depends', @scenarioIdx].each do |dependency|
103
+ depTask = checkDependency(dependency, 'depends')
104
+ a('startpreds').push([ depTask, dependency.onEnd ])
105
+ depTask[dependency.onEnd ? 'endsuccs' : 'startsuccs', @scenarioIdx].
106
+ push([ @property, false ])
107
+ end
108
+
109
+ @property['precedes', @scenarioIdx].each do |dependency|
110
+ predTask = checkDependency(dependency, 'precedes')
111
+ a('endsuccs').push([ predTask, dependency.onEnd ])
112
+ predTask[dependency.onEnd ? 'endpreds' : 'startpreds', @scenarioIdx].
113
+ push([@property, true ])
114
+ end
115
+ end
116
+
117
+ # Return true of this Task has a dependency [ _target_, _onEnd_ ] in the
118
+ # dependency category _depType_.
119
+ def hasDependency?(depType, target, onEnd)
120
+ a(depType).include?([target, onEnd])
121
+ end
122
+
123
+ def propagateInitialValues
124
+ unless @startPropagated
125
+ if a('start')
126
+ propagateDate(a('start'), false)
127
+ elsif @property.parent.nil? &&
128
+ @property.canInheritDate?(@scenarioIdx, false)
129
+ propagateDate(@project['start'], false)
130
+ end
131
+ end
132
+
133
+ unless @endPropagated
134
+ if a('end')
135
+ propagateDate(a('end'), true)
136
+ elsif @property.parent.nil? &&
137
+ @property.canInheritDate?(@scenarioIdx, true)
138
+ propagateDate(@project['end'], true)
139
+ end
140
+ end
141
+ end
142
+
143
+ # Before the actual scheduling work can be started, we need to do a few
144
+ # consistency checks on the task.
145
+ def preScheduleCheck
146
+ # Accounts can have sub accounts added after being used in a chargetset.
147
+ # So we need to re-test here.
148
+ a('chargeset').each do |chargeset|
149
+ chargeset.each do |account, share|
150
+ unless account.leaf?
151
+ error('account_no_leaf',
152
+ "Chargesets may not include group account #{account.fullId}.")
153
+ end
154
+ end
155
+ end
156
+
157
+ # Leaf tasks can be turned into containers after bookings have been added.
158
+ # We need to check for this.
159
+ unless @property.leaf? || a('booking').empty?
160
+ error('container_booking',
161
+ "Container task #{@property.fullId} may not have bookings.")
162
+ end
163
+
164
+ # Milestones may not have bookings.
165
+ if a('milestone') && !a('booking').empty?
166
+ error('milestone_booking',
167
+ "Milestone #{@property.fullId} may not have bookings.")
168
+ end
169
+
170
+ # All 'scheduled' tasks must have a fixed start and end date.
171
+ if a('scheduled') && (a('start').nil? || a('end').nil?)
172
+ error('not_scheduled',
173
+ "Task #{@property.fullId} is marked as scheduled but does not " +
174
+ 'have a fixed start and end date.')
175
+ end
176
+
177
+ # If an effort has been specified resources must be allocated as well.
178
+ if a('effort') > 0 && a('allocate').empty?
179
+ error('effort_no_allocations',
180
+ "Task #{@property.fullId} has an effort but no allocations.")
181
+ end
182
+
183
+ durationSpecs = 0
184
+ durationSpecs += 1 if a('effort') > 0
185
+ durationSpecs += 1 if a('length') > 0
186
+ durationSpecs += 1 if a('duration') > 0
187
+ durationSpecs += 1 if a('milestone')
188
+
189
+ # The rest of this function performs a number of plausibility tests with
190
+ # regards to task start and end critiria. To explain the various cases,
191
+ # the following symbols are used:
192
+ #
193
+ # |: fixed start or end date
194
+ # -: no fixed start or end date
195
+ # M: Milestone
196
+ # D: start or end dependency
197
+ # x->: ASAP task with duration criteria
198
+ # <-x: ALAP task with duration criteria
199
+ # -->: ASAP task without duration criteria
200
+ # <--: ALAP task without duration criteria
201
+
202
+ if @property.container?
203
+ if durationSpecs > 0
204
+ error('container_duration',
205
+ "Container task #{@property.fullId} may not have a duration " +
206
+ "or be marked as milestones.")
207
+ end
208
+ elsif a('milestone')
209
+ if durationSpecs > 1
210
+ error('milestone_duration',
211
+ "Milestone task #{@property.fullId} may not have a duration.")
212
+ end
213
+ # Milestones can have the following cases:
214
+ #
215
+ # | M - ok |D M - ok - M - err1 -D M - ok
216
+ # | M | err2 |D M | err2 - M | ok -D M | ok
217
+ # | M -D ok |D M -D ok - M -D ok -D M -D ok
218
+ # | M |D err2 |D M |D err2 - M |D ok -D M |D ok
219
+
220
+ # err1: no start and end
221
+ # already handled by 'start_undetermed' or 'end_undetermed'
222
+
223
+ # err2: differnt start and end dates
224
+ if a('start') && a('end') && a('start') != a('end')
225
+ error('milestone_start_end',
226
+ "Start (#{a('start')}) and end (#{a('end')}) dates of " +
227
+ "milestone task #{@property.fullId} must be identical.")
228
+ end
229
+ else
230
+ # Error table for non-container, non-milestone tasks:
231
+ # AMP: Automatic milestone promotion for underspecified tasks when
232
+ # no bookings or allocations are present.
233
+ # AMPi: Automatic milestone promotion when no bookings or
234
+ # allocations are present. When no bookings but allocations are
235
+ # present the task inherits start and end date.
236
+ # Ref. implicitXref()|
237
+ # inhS: Inherit start date from parent task or project
238
+ # inhE: Inherit end date from parent task or project
239
+ #
240
+ # | x-> - ok |D x-> - ok - x-> - inhS -D x-> - ok
241
+ # | x-> | err1 |D x-> | err1 - x-> | inhS -D x-> | err1
242
+ # | x-> -D ok |D x-> -D ok - x-> -D inhS -D x-> -D ok
243
+ # | x-> |D err1 |D x-> |D err1 - x-> |D inhS -D x-> |D err1
244
+ # | --> - AMP |D --> - AMP - --> - AMPi -D --> - AMP
245
+ # | --> | ok |D --> | ok - --> | inhS -D --> | ok
246
+ # | --> -D ok |D --> -D ok - --> -D inhS -D --> -D ok
247
+ # | --> |D ok |D --> |D ok - --> |D inhS -D --> |D ok
248
+ # | <-x - inhE |D <-x - inhE - <-x - inhE -D <-x - inhE
249
+ # | <-x | err1 |D <-x | err1 - <-x | ok -D <-x | ok
250
+ # | <-x -D err1 |D <-x -D err1 - <-x -D ok -D <-x -D ok
251
+ # | <-x |D err1 |D <-x |D err1 - <-x |D ok -D <-x |D ok
252
+ # | <-- - inhE |D <-- - inhE - <-- - AMP -D <-- - inhE
253
+ # | <-- | ok |D <-- | ok - <-- | AMP -D <-- | ok
254
+ # | <-- -D ok |D <-- -D ok - <-- -D AMP -D <-- -D ok
255
+ # | <-- |D ok |D <-- |D ok - <-- |D AMP -D <-- |D ok
256
+
257
+ # These cases are normally autopromoted to milestones or inherit their
258
+ # start or end dates. But this only works for tasks that have no
259
+ # allocations or bookings.
260
+ # - --> -
261
+ # | --> -
262
+ # |D --> -
263
+ # -D --> -
264
+ # - <-- -
265
+ # - <-- |
266
+ # - <-- -D
267
+ # - <-- |D
268
+ if durationSpecs == 0 && ((a('forward') && a('end').nil?) ||
269
+ (!a('forward') && a('start').nil?))
270
+ error('task_underspecified',
271
+ "Task #{@property.fullId} has too few specifations to be " +
272
+ "scheduled.")
273
+ end
274
+
275
+ # err1: Overspecified (12 cases)
276
+ # | x-> |
277
+ # | <-x |
278
+ # | x-> |D
279
+ # | <-x |D
280
+ # |D x-> |
281
+ # |D <-x |
282
+ # |D <-x |D
283
+ # |D x-> |D
284
+ # -D x-> |
285
+ # -D x-> |D
286
+ # |D <-x -D
287
+ # | <-x -D
288
+ if durationSpecs > 1
289
+ error('multiple_durations',
290
+ "Tasks may only have either a duration, length or effort or " +
291
+ "be a milestone.")
292
+ end
293
+ startSpeced = @property.provided('start', @scenarioIdx)
294
+ endSpeced = @property.provided('end', @scenarioIdx)
295
+ if ((startSpeced && endSpeced) ||
296
+ (hasDependencies(false) && a('forward') && endSpeced) ||
297
+ (hasDependencies(true) && !a('forward') && startSpeced)) &&
298
+ durationSpecs > 0 && !a('scheduled')
299
+ error('task_overspecified',
300
+ "Task #{@property.fullId} has a start, an end and a " +
301
+ 'duration specification.')
302
+ end
303
+ end
304
+
305
+ if !a('booking').empty? && !a('forward') && !a('scheduled')
306
+ error('alap_booking',
307
+ 'A task scheduled in ALAP mode may only have bookings if it ' +
308
+ 'has been marked as fully scheduled. Keep in mind that ' +
309
+ 'certain attributes like \'end\' or \'precedes\' automatically ' +
310
+ 'switch the task to ALAP mode.')
311
+ end
312
+ end
313
+
314
+ # When the actual scheduling process has been completed, this function must
315
+ # be called to do some more housekeeping. It computes some derived data
316
+ # based on the just scheduled values.
317
+ def finishScheduling
318
+ calcCompletion
319
+ # This list is no longer needed, so let's save some memory. Set it to
320
+ # nil so we can detect accidental use.
321
+ @candidates = nil
322
+ end
323
+
324
+ # This function is not essential but does perform a large number of
325
+ # consistency checks. It should be called after the scheduling run has been
326
+ # finished.
327
+ def postScheduleCheck
328
+ @errors = 0
329
+ @property.children.each do |task|
330
+ @errors += 1 unless task.postScheduleCheck(@scenarioIdx)
331
+ end
332
+
333
+ # There is no point to check the parent if the child(s) have errors.
334
+ return false if @errors > 0
335
+
336
+ # Same for runaway tasks. They have already been reported.
337
+ if @isRunAway
338
+ error('sched_runaway', "Some tasks did not fit into the project time " +
339
+ "frame.")
340
+ end
341
+
342
+ # Make sure the task is marked complete
343
+ unless a('scheduled')
344
+ error('not_scheduled',
345
+ "Task #{@property.fullId} has not been marked as scheduled.")
346
+ end
347
+
348
+ # If the task has a follower or predecessor that is a runaway this task
349
+ # is also incomplete.
350
+ (a('startsuccs') + a('endsuccs')).each do |task, onEnd|
351
+ return false if task.isRunAway(@scenarioIdx)
352
+ end
353
+ (a('startpreds') + a('endpreds')).each do |task, onEnd|
354
+ return false if task.isRunAway(@scenarioIdx)
355
+ end
356
+
357
+ # Check if the start time is ok
358
+ if a('start').nil?
359
+ error('task_start_undef',
360
+ "Task #{@property.fullId} has undefined start time")
361
+ end
362
+ if a('start') < @project['start'] || a('start') > @project['end']
363
+ error('task_start_range',
364
+ "The start time (#{a('start')}) of task #{@property.fullId} " +
365
+ "is outside the project interval (#{@project['start']} - " +
366
+ "#{@project['end']})")
367
+ end
368
+ if !a('minstart').nil? && a('start') < a('minstart')
369
+ warning('minstart',
370
+ "The start time (#{a('start')}) of task #{@property.fullId} " +
371
+ "is too early. Must be after #{a('minstart')}.")
372
+ end
373
+ if !a('maxstart').nil? && a('start') > a('maxstart')
374
+ warning('maxstart',
375
+ "The start time (#{a('start')}) of task #{@property.fullId} " +
376
+ "is too late. Must be before #{a('maxstart')}.")
377
+ end
378
+
379
+ # Check if the end time is ok
380
+ error('task_end_undef',
381
+ "Task #{@property.fullId} has undefined end time") if a('end').nil?
382
+ if a('end') < @project['start'] || a('end') > @project['end']
383
+ error('task_end_range',
384
+ "The end time (#{a('end')}) of task #{@property.fullId} " +
385
+ "is outside the project interval (#{@project['start']} - " +
386
+ "#{@project['end']})")
387
+ end
388
+ if !a('minend').nil? && a('end') < a('minend')
389
+ warning('minend',
390
+ "The end time (#{a('end')}) of task #{@property.fullId} " +
391
+ "is too early. Must be after #{a('minend')}.")
392
+ end
393
+ if !a('maxend').nil? && a('end') > a('maxend')
394
+ warning('maxend',
395
+ "The end time (#{a('end')}) of task #{@property.fullId} " +
396
+ "is too late. Must be before #{a('maxend')}.")
397
+ end
398
+
399
+ # Check that tasks fits into parent task.
400
+ unless (parent = @property.parent).nil? ||
401
+ parent['start', @scenarioIdx].nil? ||
402
+ parent['end', @scenarioIdx].nil?
403
+ if a('start') < parent['start', @scenarioIdx]
404
+ error('task_start_in_parent',
405
+ "The start date (#{a('start')}) of task #{@property.fullId} " +
406
+ "is before the start date of the enclosing task " +
407
+ "#{parent['start', @scenarioIdx]}. ")
408
+ end
409
+ if a('end') > parent['end', @scenarioIdx]
410
+ error('task_end_in_parent',
411
+ "The end date (#{a('end')}) of task #{@property.fullId} " +
412
+ "is after the end date of the enclosing task " +
413
+ "#{parent['end', @scenarioIdx]}. ")
414
+ end
415
+ end
416
+
417
+ # Check that all preceding tasks start/end before this task.
418
+ @property['depends', @scenarioIdx].each do |dependency|
419
+ task = dependency.task
420
+ limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
421
+ next if limit.nil?
422
+ if limit > a('start')
423
+ error('task_pred_before',
424
+ "Task #{@property.fullId} must start after " +
425
+ "#{dependency.onEnd ? 'end' : 'start'} of task " +
426
+ "#{@property.fullId}.")
427
+ end
428
+ end
429
+
430
+ # Check that all following tasks end before this task
431
+ @property['precedes', @scenarioIdx].each do |dependency|
432
+ task = dependency.task
433
+ limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
434
+ next if limit.nil?
435
+ if limit < a('end')
436
+ error('task_succ_after',
437
+ "Task #{@property.fullId} must end before " +
438
+ "#{dependency.onEnd ? 'end' : 'start'} of task #{task.fullId}.")
439
+ end
440
+ end
441
+
442
+ if a('milestone') && a('start') != a('end')
443
+ error('milestone_times_equal',
444
+ "Milestone #{@property.fullId} must have identical start and " +
445
+ "end date.")
446
+ end
447
+
448
+ @errors == 0
449
+ end
450
+
451
+ def resetLoopFlags
452
+ @deadEndFlags = Array.new(4, false)
453
+ end
454
+
455
+ def checkForLoops(path, atEnd, fromOutside)
456
+ # Check if we have been here before on this path.
457
+ if path.include?([ @property, atEnd ])
458
+ error('loop_detected', "Loop detected at #{atEnd ? 'end' : 'start'} " +
459
+ "of task #{@property.fullId}", false)
460
+ skip = true
461
+ path.each do |t, e|
462
+ if t == @property && e == atEnd
463
+ skip = false
464
+ next
465
+ end
466
+ next if skip
467
+ info("loop_at_#{e ? 'end' : 'start'}",
468
+ "Loop ctnd. at #{e ? 'end' : 'start'} of task #{t.fullId}", t)
469
+ end
470
+ error('loop_end', "Aborting")
471
+ end
472
+ # Used for debugging only
473
+ if false
474
+ pathText = ''
475
+ path.each do |t, e|
476
+ pathText += "#{t.fullId}(#{e ? 'end' : 'start'}) -> "
477
+ end
478
+ pathText += "#{@property.fullId}(#{atEnd ? 'end' : 'start'})"
479
+ puts pathText
480
+ end
481
+ return if @deadEndFlags[(atEnd ? 2 : 0) + (fromOutside ? 1 : 0)]
482
+ path << [ @property, atEnd ]
483
+
484
+ # To find loops we have to traverse the graph in a certain order. When we
485
+ # enter a task we can either come from outside or inside. The following
486
+ # graph explains these definitions:
487
+ #
488
+ # | / \ |
489
+ # outside v / \ v outside
490
+ # +------------------------------+
491
+ # | / Task \ |
492
+ # -->| o <--- ---> o |<--
493
+ # |/ Start End \|
494
+ # /+------------------------------+\
495
+ # / ^ ^ \
496
+ # | inside |
497
+ #
498
+ # At the top we have the parent task. At the botton the child tasks.
499
+ # The horizontal arrors are start predecessors or end successors.
500
+ # As the graph is doubly-linked, we need to becareful to only find real
501
+ # loops. When coming from outside, we only continue to the inside and vice
502
+ # versa. Horizontal moves are only made when we are in a leaf task.
503
+ unless atEnd
504
+ if fromOutside
505
+ if @property.container?
506
+ #
507
+ # |
508
+ # v
509
+ # +--------
510
+ # -->| o--+
511
+ # +----|---
512
+ # |
513
+ # V
514
+ #
515
+ @property.children.each do |child|
516
+ child.checkForLoops(@scenarioIdx, path, false, true)
517
+ end
518
+ else
519
+ # |
520
+ # v
521
+ # +--------
522
+ # -->| o---->
523
+ # +--------
524
+ #
525
+ checkForLoops(path, true, false) # if a('forward')
526
+ end
527
+ else
528
+ if a('startpreds').empty?
529
+ #
530
+ # ^
531
+ # |
532
+ # +-|------
533
+ # | o <--
534
+ # +--------
535
+ # ^
536
+ # |
537
+ #
538
+ if @property.parent
539
+ @property.parent.checkForLoops(@scenarioIdx, path, false, false)
540
+ end
541
+ else
542
+
543
+ # +--------
544
+ # <---- o <--
545
+ # +--------
546
+ # ^
547
+ # |
548
+ #
549
+ a('startpreds').each do |task, targetEnd|
550
+ task.checkForLoops(@scenarioIdx, path, targetEnd, true)
551
+ end
552
+ end
553
+ end
554
+ else
555
+ if fromOutside
556
+ if @property.container?
557
+ #
558
+ # |
559
+ # v
560
+ # --------+
561
+ # +--o |<--
562
+ # ---|----+
563
+ # |
564
+ # v
565
+ #
566
+ @property.children.each do |child|
567
+ child.checkForLoops(@scenarioIdx, path, true, true)
568
+ end
569
+ else
570
+ # |
571
+ # v
572
+ # --------+
573
+ # <----o |<--
574
+ # --------+
575
+ #
576
+ checkForLoops(path, false, false) # unless a('forward')
577
+ end
578
+ else
579
+ if a('endsuccs').empty?
580
+ #
581
+ # ^
582
+ # |
583
+ # ------|-+
584
+ # --> o |
585
+ # --------+
586
+ # ^
587
+ # |
588
+ #
589
+ if @property.parent
590
+ @property.parent.checkForLoops(@scenarioIdx, path, true, false)
591
+ end
592
+ else
593
+ # --------+
594
+ # --> o---->
595
+ # --------+
596
+ # ^
597
+ # |
598
+ #
599
+ a('endsuccs').each do |task, targetEnd|
600
+ task.checkForLoops(@scenarioIdx, path, targetEnd, true)
601
+ end
602
+ end
603
+ end
604
+ end
605
+
606
+ path.pop
607
+ @deadEndFlags[(atEnd ? 2 : 0) + (fromOutside ? 1 : 0)] = true
608
+ # puts "Finished with #{@property.fullId} #{atEnd ? 'end' : 'start'} " +
609
+ # "#{fromOutside ? 'outside' : 'inside'}"
610
+ end
611
+
612
+ # This function must be called before prepareScheduling(). It compiles the
613
+ # list of leaf resources that are allocated to this task.
614
+ def candidates
615
+ @candidates = []
616
+ a('allocate').each do |allocation|
617
+ allocation.candidates.each do |candidate|
618
+ candidate.allLeaves.each do |resource|
619
+ @candidates << resource unless @candidates.include?(resource)
620
+ end
621
+ end
622
+ end
623
+ @candidates
624
+ end
625
+
626
+ # This function does some prep work for other functions like
627
+ # calcCriticalness. It compiles a list of all allocated leaf resources and
628
+ # stores it in @candidates. It also adds the allocated effort to
629
+ # the 'alloctdeffort' counter of each resource.
630
+ def countResourceAllocations
631
+ return if @candidates.empty? || a('effort') <= 0
632
+
633
+ avgEffort = a('effort') / @candidates.length
634
+ @candidates.each do |resource|
635
+ resource['alloctdeffort', @scenarioIdx] += avgEffort
636
+ end
637
+ end
638
+
639
+ # Determine the criticalness of the individual task. This is a measure for
640
+ # the likelyhood that this task will get the resources that it needs to
641
+ # complete the effort. Tasks without effort are not cricital. The only
642
+ # exception are milestones which get an arbitrary value of 1.
643
+ def calcCriticalness
644
+ @property['criticalness', @scenarioIdx] = 0.0
645
+ @property['pathcriticalness', @scenarioIdx] = nil
646
+
647
+ # Users feel that milestones are somewhat important. So we use an
648
+ # arbitrary value larger than 0 for them.
649
+ @property['criticalness', @scenarioIdx] = 1.0 if a('milestone')
650
+
651
+ # Task without efforts of allocations are not critical.
652
+ return if a('effort') <= 0 || @candidates.empty?
653
+
654
+ # Determine the average criticalness of all allocated resources.
655
+ criticalness = 0.0
656
+ @candidates.each do |resource|
657
+ criticalness += resource['criticalness', @scenarioIdx]
658
+ end
659
+ criticalness /= @candidates.length
660
+
661
+ # The task criticalness is the product of effort and average resource
662
+ # criticalness.
663
+ @property['criticalness', @scenarioIdx] = a('effort') * criticalness
664
+ end
665
+
666
+ # The path criticalness is a measure for the overall criticalness of the
667
+ # task taking the dependencies into account. The fact that a task is part
668
+ # of a chain of effort-based task raises all the task in the chain to a
669
+ # higher criticalness level than the individual tasks. In fact, the path
670
+ # criticalness of this chain is equal to the sum of the individual
671
+ # criticalnesses of the tasks.
672
+ def calcPathCriticalness(atEnd = false)
673
+ # If we have computed this already, just return the value. If we are only
674
+ # at the end of the task, we do not include the criticalness of this task
675
+ # as it is not really part of the path.
676
+ if a('pathcriticalness')
677
+ return a('pathcriticalness') - (atEnd ? 0 : a('criticalness'))
678
+ end
679
+
680
+ maxCriticalness = 0.0
681
+
682
+ if atEnd
683
+ # At the end, we only care about pathes through the successors of this
684
+ # task or its parent tasks.
685
+ if (criticalness = calcPathCriticalnessEndSuccs) > maxCriticalness
686
+ maxCriticalness = criticalness
687
+ end
688
+ else
689
+ # At the start of the task, we have two options.
690
+ if @property.container?
691
+ # For container tasks, we ignore all dependencies and check the pathes
692
+ # through all the children.
693
+ @property.children.each do |task|
694
+ if (criticalness = task.calcPathCriticalness(@scenarioIdx, false)) >
695
+ maxCriticalness
696
+ maxCriticalness = criticalness
697
+ end
698
+ end
699
+ else
700
+ # For leaf tasks, we check all pathes through the start successors and
701
+ # then the pathes through the end successors of this task and all its
702
+ # parent tasks.
703
+ a('startsuccs').each do |task, onEnd|
704
+ if (criticalness = task.calcPathCriticalness(@scenarioIdx, onEnd)) >
705
+ maxCriticalness
706
+ maxCriticalness = criticalness
707
+ end
708
+ end
709
+
710
+ if (criticalness = calcPathCriticalnessEndSuccs) > maxCriticalness
711
+ maxCriticalness = criticalness
712
+ end
713
+
714
+ maxCriticalness += a('criticalness')
715
+ end
716
+ end
717
+
718
+ @property['pathcriticalness', @scenarioIdx] = maxCriticalness
719
+ end
720
+
721
+ # Check if the task is ready to be scheduled. For this it needs to have at
722
+ # least one specified end date and a duration criteria or the other end
723
+ # date.
724
+ def readyForScheduling?
725
+ return false if a('scheduled') || @isRunAway
726
+
727
+ if a('forward')
728
+ return true if a('start') && (hasDurationSpec? || a('end'))
729
+ else
730
+ return true if a('end') && (hasDurationSpec? || a('start'))
731
+ end
732
+
733
+ false
734
+ end
735
+
736
+ # This function is the entry point for the core scheduling algorithm. It
737
+ # schedules the task to completion. The function returns true if a start
738
+ # or end date has been determined and other tasks may be ready for
739
+ # scheduling now.
740
+ def schedule
741
+ # Is the task scheduled from start to end or vice versa?
742
+ forward = a('forward')
743
+ # The task may not excede the project interval.
744
+ limit = @project[forward ? 'end' : 'start']
745
+ # Number of seconds per time slot.
746
+ slotDuration = @project['scheduleGranularity']
747
+ slot = nextSlot(slotDuration)
748
+
749
+ # Schedule all time slots from slot in the scheduling direction until
750
+ # the task is completed or a problem has been found.
751
+ while !scheduleSlot(slot, slotDuration)
752
+ if forward
753
+ # The task is scheduled from start to end.
754
+ slot += slotDuration
755
+ if slot > limit
756
+ markAsRunaway
757
+ return false
758
+ end
759
+ else
760
+ # The task is scheduled from end to start.
761
+ slot -= slotDuration
762
+ if slot < limit
763
+ markAsRunaway
764
+ return false
765
+ end
766
+ end
767
+ end
768
+
769
+ true
770
+ end
771
+
772
+ # Set a new start or end date and propagate the value to all other
773
+ # task ends that have a direct dependency to this end of the task.
774
+ def propagateDate(date, atEnd)
775
+ thisEnd = atEnd ? 'end' : 'start'
776
+ otherEnd = atEnd ? 'start' : 'end'
777
+
778
+ # These flags are just used to avoid duplicate calls of this function
779
+ # during propagateInitialValues().
780
+ if atEnd
781
+ @endPropagated = true
782
+ else
783
+ @startPropagated = true
784
+ end
785
+
786
+ # For leaf tasks, propagate start may set the date. Container task dates
787
+ # are only set in scheduleContainer().
788
+ @property[thisEnd, @scenarioIdx] = date if @property.leaf?
789
+
790
+ if a('milestone')
791
+ # Start and end date of a milestone are identical.
792
+ @property['scheduled', @scenarioIdx] = true
793
+ if a(otherEnd).nil?
794
+ propagateDate(a(thisEnd), !atEnd)
795
+ end
796
+ Log << "Milestone #{@property.fullId}: #{a('start')} -> #{a('end')}"
797
+ end
798
+
799
+ # Propagate date to all dependent tasks.
800
+ a(thisEnd + 'preds').each do |task, onEnd|
801
+ propagateDateToDep(task, onEnd)
802
+ end
803
+ a(thisEnd + 'succs').each do |task, onEnd|
804
+ propagateDateToDep(task, onEnd)
805
+ end
806
+
807
+ # Propagate date to sub tasks which have only an implicit
808
+ # dependency on the parent task and no other criteria for this end of
809
+ # the task.
810
+ @property.children.each do |task|
811
+ if task.canInheritDate?(@scenarioIdx, atEnd)
812
+ task.propagateDate(@scenarioIdx, date, atEnd)
813
+ end
814
+ end
815
+
816
+ # The date propagation might have completed the date set of the enclosing
817
+ # containter task. If so, we can schedule it as well.
818
+ @property.parent.scheduleContainer(@scenarioIdx) if !@property.parent.nil?
819
+ end
820
+
821
+ # This function determines if a task can inherit the start or end date
822
+ # from a parent task or the project time frame. +atEnd+ specifies whether
823
+ # the check should be done for the task end (true) or task start (false).
824
+ def canInheritDate?(atEnd)
825
+ # Inheriting a start or end date from the enclosing task or the project
826
+ # is allowed for the following scenarios:
827
+ # - --> - inherit start and end when no bookings but allocations
828
+ # present
829
+ # - <-- - dito
830
+ #
831
+ # - x-> - inhS
832
+ # - x-> | inhS
833
+ # - x-> -D inhS
834
+ # - x-> |D inhS
835
+ # - --> | inhS
836
+ # - --> -D inhS
837
+ # - --> |D inhS
838
+ # - <-- | inhS
839
+ # | --> - inhE
840
+ # | <-x - inhE
841
+ # |D <-x - inhE
842
+ # - <-x - inhE
843
+ # -D <-x - inhE
844
+ # | <-- - inhE
845
+ # |D <-- - inhE
846
+ # -D <-- - inhE
847
+ # Return false if we already have a date or if we have a dependency for
848
+ # this end.
849
+ thisEnd = atEnd ? 'end' : 'start'
850
+ hasThisDeps = !a(thisEnd + 'preds').empty? || !a(thisEnd + 'succs').empty?
851
+ hasThisSpec = a(thisEnd) || hasThisDeps
852
+ return false if hasThisSpec
853
+
854
+ # Containter task can inherit the date if they have no dependencies.
855
+ return true if @property.container?
856
+
857
+ thatEnd = atEnd ? 'start' : 'end'
858
+ hasThatDeps = !a(thatEnd + 'preds').empty? || !a(thatEnd + 'succs').empty?
859
+ hasThatSpec = a(thatEnd) || hasThatDeps
860
+
861
+ # Check for tasks that have no start and end spec, no duration spec but
862
+ # allocates. They can inherit the start and end date.
863
+ return true if hasThatSpec && !hasDurationSpec? && !a('allocate').empty?
864
+
865
+ if a('forward') ^ atEnd
866
+ # the scheduling direction is pointing away from this end
867
+ return true if hasDurationSpec? || !a('booking').empty?
868
+
869
+ return hasThatSpec
870
+ else
871
+ # the scheduling direction is pointing towards this end
872
+ return a(thatEnd) && !hasDurationSpec? &&
873
+ a('booking').empty? #&& a('allocate').empty?
874
+ end
875
+ end
876
+
877
+ # Find the smallest possible interval that encloses all child tasks. Abort
878
+ # the operation if any of the child tasks are not yet scheduled.
879
+ def scheduleContainer
880
+ return if a('scheduled') || !@property.container?
881
+
882
+ nStart = nil
883
+ nEnd = nil
884
+
885
+ @property.children.each do |task|
886
+ # Abort if a child has not yet been scheduled.
887
+ return unless task['scheduled', @scenarioIdx]
888
+
889
+ if nStart.nil? || task['start', @scenarioIdx] < nStart
890
+ nStart = task['start', @scenarioIdx]
891
+ end
892
+ if nEnd.nil? || task['end', @scenarioIdx] > nEnd
893
+ nEnd = task['end', @scenarioIdx]
894
+ end
895
+ end
896
+
897
+ @property['scheduled', @scenarioIdx] = true
898
+
899
+ startSet = endSet = false
900
+ # Propagate the dates to other dependent tasks.
901
+ if a('start').nil? || a('start') > nStart
902
+ @property['start', @scenarioIdx] = nStart
903
+ startSet = true
904
+ end
905
+ if a('end').nil? || a('end') < nEnd
906
+ @property['end', @scenarioIdx] = nEnd
907
+ endSet = true
908
+ end
909
+ Log << "Container task #{@property.fullId}: #{a('start')} -> #{a('end')}"
910
+
911
+ # If we have modified the start or end date, we need to communicate this
912
+ # new date to surrounding tasks.
913
+ propagateDate(nStart, false) if startSet
914
+ propagateDate(nEnd, true) if endSet
915
+ end
916
+
917
+ # Return true if the task has a effort, length or duration setting.
918
+ def hasDurationSpec?
919
+ a('length') > 0 || a('duration') > 0 || a('effort') > 0 || a('milestone')
920
+ end
921
+
922
+ # This function checks if the task has a dependency on another task or
923
+ # fixed date for a certain end. If +atEnd+ is true, the task end will be
924
+ # checked. Otherwise the start.
925
+ def hasDependencies(atEnd)
926
+ thisEnd = atEnd ? 'end' : 'start'
927
+ !a(thisEnd + 'succs').empty? || !a(thisEnd + 'preds').empty?
928
+ end
929
+
930
+ def earliestStart
931
+ # puts "Finding earliest start date for #{@property.fullId}"
932
+ startDate = nil
933
+ a('depends').each do |dependency|
934
+ potentialStartDate =
935
+ dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
936
+ return nil if potentialStartDate.nil?
937
+
938
+ dateAfterLengthGap = potentialStartDate
939
+ gapLength = dependency.gapLength
940
+ while gapLength > 0 && dateAfterLengthGap < @project['end'] do
941
+ if @project.isWorkingTime(dateAfterLengthGap)
942
+ gapLength -= 1
943
+ end
944
+ dateAfterLengthGap += @project['scheduleGranularity']
945
+ end
946
+
947
+ if dateAfterLengthGap > potentialStartDate + dependency.gapDuration
948
+ potentialStartDate = dateAfterLengthGap
949
+ else
950
+ potentialStartDate += dependency.gapDuration
951
+ end
952
+
953
+ startDate = potentialStartDate if startDate.nil? ||
954
+ startDate < potentialStartDate
955
+ end
956
+
957
+ # If any of the parent tasks has an explicit start date, the task must
958
+ # start at or after this date.
959
+ task = @property
960
+ while (task = task.parent) do
961
+ if task['start', @scenarioIdx] &&
962
+ (startDate.nil? || task['start', @scenarioIdx] > startDate)
963
+ return task['start', @scenarioIdx]
964
+ end
965
+ end
966
+
967
+ startDate
968
+ end
969
+
970
+ def latestEnd
971
+ # puts "Finding latest end date for #{@property.fullId}"
972
+ endDate = nil
973
+ a('precedes').each do |dependency|
974
+ potentialEndDate =
975
+ dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
976
+ return nil if potentialEndDate.nil?
977
+
978
+ dateBeforeLengthGap = potentialEndDate
979
+ gapLength = dependency.gapLength
980
+ while gapLength > 0 && dateBeforeLengthGap > @project['start'] do
981
+ if @project.isWorkingTime(dateBeforeLengthGap -
982
+ @project['scheduleGranularity'])
983
+ gapLength -= 1
984
+ end
985
+ dateBeforeLengthGap -= @project['scheduleGranularity']
986
+ end
987
+ if dateBeforeLengthGap < potentialEndDate - dependency.gapDuration
988
+ potentialEndDate = dateBeforeLengthGap
989
+ else
990
+ potentialEndDate -= dependency.gapDuration
991
+ end
992
+
993
+ endDate = potentialEndDate if endDate.nil? || endDate > potentialEndDate
994
+ end
995
+
996
+ # If any of the parent tasks has an explicit end date, the task must end
997
+ # at or before this date.
998
+ task = @property
999
+ while (task = task.parent) do
1000
+ if task['end', @scenarioIdx] &&
1001
+ (endDate.nil? || task['end', @scenarioIdx] < endDate)
1002
+ return task['end', @scenarioIdx]
1003
+ end
1004
+ end
1005
+
1006
+ endDate
1007
+ end
1008
+
1009
+ def addBooking(booking)
1010
+ if a('booking').empty?
1011
+ # For the first item use the assignment form so that the 'provided'
1012
+ # attribute is set properly.
1013
+ @property['booking', @scenarioIdx] = [ booking ]
1014
+ else
1015
+ @property['booking', @scenarioIdx] << booking
1016
+ end
1017
+ end
1018
+
1019
+ def markAsRunaway
1020
+ warning('runaway', "Task #{@property.fullId} does not fit into " +
1021
+ "project time frame")
1022
+
1023
+ @isRunAway = true
1024
+ end
1025
+
1026
+ def query_complete(query)
1027
+ if @property.leaf?
1028
+ query.sortableResult = query.numericalResult = a('complete').to_i
1029
+ query.result = "#{query.sortableResult}%"
1030
+ else
1031
+ query.result = ''
1032
+ end
1033
+ end
1034
+
1035
+ # Compute the cost generated by this Task for a given Account during a given
1036
+ # interval. If a Resource is provided as scopeProperty only the cost
1037
+ # directly generated by the resource is taken into account.
1038
+ def query_cost(query)
1039
+ if query.costAccount
1040
+ query.sortableResult = query.numericalResult =
1041
+ turnover(query.startIdx, query.endIdx, query.costAccount,
1042
+ query.scopeProperty)
1043
+ query.result = query.currencyFormat.format(query.sortableResult)
1044
+ else
1045
+ query.result = 'No cost account'
1046
+ end
1047
+ end
1048
+
1049
+ # The duration of the task. After scheduling, it can be determined for
1050
+ # all tasks. Also for those who did not have a 'duration' attribute.
1051
+ def query_duration(query)
1052
+ query.sortableResult = query.numericalResult =
1053
+ (a('end') - a('start')) / (60 * 60 * 24)
1054
+ query.result = query.scaleDuration(query.sortableResult)
1055
+ end
1056
+
1057
+ # The effort allocated for the task in the specified interval. In case a
1058
+ # Resource is given as scope property only the effort allocated for this
1059
+ # resource is taken into account.
1060
+ def query_effort(query)
1061
+ query.sortableResult = query.numericalResult =
1062
+ getEffectiveWork(query.startIdx, query.endIdx, query.scopeProperty)
1063
+ query.result = query.scaleLoad(query.sortableResult)
1064
+ end
1065
+
1066
+ # Compute the revenue generated by this Task for a given Account during a
1067
+ # given interval. If a Resource is provided as scopeProperty only the
1068
+ # revenue directly generated by the resource is taken into account.
1069
+ def query_revenue(query)
1070
+ if query.revenueAccount
1071
+ query.sortableResult = query.numericalResult =
1072
+ turnover(query.startIdx, query.endIdx, query.revenueAccount,
1073
+ query.scopeProperty)
1074
+ query.result = query.currencyFormat.format(query.sortableResult)
1075
+ else
1076
+ query.result = 'No revenue account'
1077
+ end
1078
+ end
1079
+
1080
+ def getEffectiveWork(startIdx, endIdx, resource = nil)
1081
+ return 0.0 if a('milestone')
1082
+
1083
+ workLoad = 0.0
1084
+ if @property.container?
1085
+ @property.children.each do |task|
1086
+ workLoad += task.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
1087
+ resource)
1088
+ end
1089
+ else
1090
+ if resource
1091
+ workLoad += resource.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
1092
+ @property)
1093
+ else
1094
+ a('assignedresources').each do |r|
1095
+ workLoad += r.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
1096
+ @property)
1097
+ end
1098
+ end
1099
+ end
1100
+ workLoad
1101
+ end
1102
+
1103
+ # Return a list of intervals that lay within _iv_ and are at least
1104
+ # minDuration long and contain no working time.
1105
+ def collectTimeOffIntervals(iv, minDuration)
1106
+ if a('shifts')
1107
+ a('shifts').collectTimeOffIntervals(iv, minDuration)
1108
+ else
1109
+ []
1110
+ end
1111
+ end
1112
+
1113
+ private
1114
+ def scheduleSlot(slot, slotDuration)
1115
+ # Tasks must always be scheduled in a single contigous fashion. @lastSlot
1116
+ # indicates the slot that was used for the previous call. Depending on the
1117
+ # scheduling direction the next slot must be scheduled either right before
1118
+ # or after this slot. If the current slot is not directly aligned, we'll
1119
+ # wait for another call with a proper slot. The function returns true
1120
+ # only if a slot could be scheduled.
1121
+ if a('forward')
1122
+ # On first call, the @lastSlot is not set yet. We set it to the slot
1123
+ # before the start slot.
1124
+ if @lastSlot.nil?
1125
+ @lastSlot = a('start') - slotDuration
1126
+ @tentativeEnd = slot + slotDuration
1127
+ end
1128
+
1129
+ return false unless slot == @lastSlot + slotDuration
1130
+ else
1131
+ # On first call, the @lastSlot is not set yet. We set it to the slot
1132
+ # to the end slot.
1133
+ if @lastSlot.nil?
1134
+ @lastSlot = a('end')
1135
+ @tentativeStart = slot
1136
+ end
1137
+
1138
+ return false unless slot == @lastSlot - slotDuration
1139
+ end
1140
+ @lastSlot = slot
1141
+
1142
+ if a('length') > 0 || a('duration') > 0
1143
+ # The doneDuration counts the number of scheduled slots. It is increased
1144
+ # by one with every scheduled slot. The doneLength is only increased for
1145
+ # global working time slots.
1146
+ @doneDuration += 1
1147
+ if @project.isWorkingTime(slot, slot + slotDuration)
1148
+ @doneLength += 1
1149
+ end
1150
+
1151
+ # If we have reached the specified duration or lengths, we set the end
1152
+ # or start date and propagate the value to neighbouring tasks.
1153
+ if (a('length') > 0 && @doneLength >= a('length')) ||
1154
+ (a('duration') > 0 && @doneDuration >= a('duration'))
1155
+ @property['scheduled', @scenarioIdx] = true
1156
+ if a('forward')
1157
+ propagateDate(slot + slotDuration, true)
1158
+ else
1159
+ propagateDate(slot, false)
1160
+ end
1161
+ return true
1162
+ end
1163
+ elsif a('effort') > 0
1164
+ bookResources(slot, slotDuration) if @doneEffort < a('effort')
1165
+ if @doneEffort >= a('effort')
1166
+ # The specified effort has been reached. The has been fully scheduled
1167
+ # now.
1168
+ @property['scheduled', @scenarioIdx] = true
1169
+ if a('forward')
1170
+ propagateDate(@tentativeEnd, true)
1171
+ else
1172
+ propagateDate(@tentativeStart, false)
1173
+ end
1174
+ return true
1175
+ end
1176
+ elsif a('milestone')
1177
+ if a('forward')
1178
+ propagateDate(a('start'), true)
1179
+ else
1180
+ propagateDate(a('end'), false)
1181
+ end
1182
+ return true
1183
+ elsif a('start') && a('end')
1184
+ # Task with start and end date but no duration criteria
1185
+ if a('allocate').empty?
1186
+ # For start-end-tasks without allocation, we don't have to do
1187
+ # anything but to set the 'scheduled' flag.
1188
+ @property['scheduled', @scenarioIdx] = true
1189
+ @property.parent.scheduleContainer(@scenarioIdx) if @property.parent
1190
+ return true
1191
+ end
1192
+
1193
+ bookResources(slot, slotDuration)
1194
+
1195
+ # Depending on the scheduling direction we can mark the task as
1196
+ # scheduled once we have reached the other end.
1197
+ if (a('forward') && slot + slotDuration >= a('end')) ||
1198
+ (!a('forward') && slot <= a('start'))
1199
+ @property['scheduled', @scenarioIdx] = true
1200
+ @property.parent.scheduleContainer(@scenarioIdx) if @property.parent
1201
+ return true
1202
+ end
1203
+ end
1204
+
1205
+ false
1206
+ end
1207
+
1208
+ # Return the date of the next slot this task wants to have scheduled. This
1209
+ # must either be the first slot ever or it must be directly adjecent to the
1210
+ # previous slot. If this task has not yet been scheduled at all, @lastSlot
1211
+ # is still nil. Otherwise it contains the date of the last scheduled slot.
1212
+ def nextSlot(slotDuration)
1213
+ return nil if a('scheduled') || @isRunAway
1214
+
1215
+ if a('forward')
1216
+ @lastSlot.nil? ? a('start') : @lastSlot + slotDuration
1217
+ else
1218
+ @lastSlot.nil? ? a('end') - slotDuration : @lastSlot - slotDuration
1219
+ end
1220
+ end
1221
+
1222
+ def bookResources(date, slotDuration)
1223
+ # In projection mode we do not allow bookings prior to the current date
1224
+ # for any task (in strict mode) or tasks which have user specified
1225
+ # bookings (sloppy mode).
1226
+ if @project.scenario(@scenarioIdx).get('projection') &&
1227
+ date < @project['now'] &&
1228
+ (@project.scenario(@scenarioIdx).get('strict') ||
1229
+ a('assignedresources').empty?)
1230
+ return
1231
+ end
1232
+
1233
+ # If the task has shifts to limit the allocations, we check that we are
1234
+ # within a defined shift interval. If yes, we need to be on shift to
1235
+ # continue.
1236
+ if (shifts = a('shifts')) && shifts.assigned?(date)
1237
+ return if !shifts.onShift?(date)
1238
+ end
1239
+
1240
+ # If the task has allocation limits we need to make sure that none of them
1241
+ # is already exceeded.
1242
+ @limits.each do |limit|
1243
+ return if !limit.ok?(date)
1244
+ end
1245
+
1246
+ sbIdx = @project.dateToIdx(date)
1247
+
1248
+ # We first have to make sure that if there are mandatory resources
1249
+ # that these are all available for the time slot.
1250
+ takenMandatories = []
1251
+ @mandatories.each do |allocation|
1252
+ return unless allocation.onShift?(date)
1253
+
1254
+ # For mandatory allocations with alternatives at least one of the
1255
+ # alternatives must be available.
1256
+ found = false
1257
+ allocation.candidates(@scenarioIdx).each do |candidate|
1258
+ # When a resource group is marked mandatory, all members of the
1259
+ # group must be available.
1260
+ allAvailable = true
1261
+ candidate.allLeaves.each do |resource|
1262
+ if !resource.available?(@scenarioIdx, sbIdx) ||
1263
+ takenMandatories.include?(resource)
1264
+ allAvailable = false
1265
+ break
1266
+ else
1267
+ takenMandatories << resource
1268
+ end
1269
+ end
1270
+ if allAvailable
1271
+ found = true
1272
+ break
1273
+ end
1274
+ end
1275
+
1276
+ # At least one mandatory resource is not available. We cannot continue.
1277
+ return unless found
1278
+ end
1279
+
1280
+ iv = Interval.new(date, date + slotDuration)
1281
+ a('allocate').each do |allocation|
1282
+ next unless allocation.onShift?(date)
1283
+
1284
+ # In case we have a persistent allocation we need to check if there is
1285
+ # already a locked resource and use it.
1286
+ if allocation.persistent && !allocation.lockedResource.nil?
1287
+ bookResource(allocation.lockedResource, sbIdx, date)
1288
+ else
1289
+ # If not, we create a list of candidates in the proper order and
1290
+ # assign the first one available.
1291
+ allocation.candidates(@scenarioIdx).each do |candidate|
1292
+ if bookResource(candidate, sbIdx, date)
1293
+ allocation.lockedResource = candidate
1294
+ break
1295
+ end
1296
+ end
1297
+ end
1298
+ end
1299
+ end
1300
+
1301
+ def bookResource(resource, sbIdx, date)
1302
+ booked = false
1303
+ resource.allLeaves.each do |r|
1304
+ if r.book(@scenarioIdx, sbIdx, @property)
1305
+
1306
+ if a('assignedresources').empty?
1307
+ if a('forward')
1308
+ @property['start', @scenarioIdx] = @project.idxToDate(sbIdx)
1309
+ else
1310
+ @property['end', @scenarioIdx] = @project.idxToDate(sbIdx + 1)
1311
+ end
1312
+ end
1313
+
1314
+ @tentativeStart = @project.idxToDate(sbIdx)
1315
+ @tentativeEnd = @project.idxToDate(sbIdx + 1)
1316
+
1317
+ @doneEffort += r['efficiency', @scenarioIdx]
1318
+ # Limits do not take efficiency into account. Limits are usage limits,
1319
+ # not effort limits.
1320
+ @limits.each do |limit|
1321
+ limit.inc(date)
1322
+ end
1323
+
1324
+ unless a('assignedresources').include?(r)
1325
+ @property['assignedresources', @scenarioIdx] << r
1326
+ end
1327
+ booked = true
1328
+ end
1329
+ end
1330
+
1331
+ booked
1332
+ end
1333
+
1334
+ # Register the user provided bookings with the Resource scoreboards. A
1335
+ # booking describes the assignment of a Resource to a certain Task for a
1336
+ # specified Interval.
1337
+ def bookBookings
1338
+ a('booking').each do |booking|
1339
+ unless booking.resource.leaf?
1340
+ error('booking_resource_not_leaf',
1341
+ "Booked resources may not be group resources", true,
1342
+ booking.sourceFileInfo)
1343
+ end
1344
+ unless a('forward') || a('scheduled')
1345
+ error('booking_forward_only',
1346
+ "Only forward scheduled tasks may have booking statements.")
1347
+ end
1348
+ slotDuration = @project['scheduleGranularity']
1349
+ booking.intervals.each do |interval|
1350
+ startIdx = @project.dateToIdx(interval.start)
1351
+ date = interval.start
1352
+ endIdx = @project.dateToIdx(interval.end)
1353
+ tEnd = nil
1354
+ startIdx.upto(endIdx - 1) do |idx|
1355
+ tEnd = date + slotDuration
1356
+ if booking.resource.bookBooking(@scenarioIdx, idx, booking)
1357
+ # Booking was successful for this time slot.
1358
+ @doneEffort += booking.resource['efficiency', @scenarioIdx]
1359
+
1360
+ # Set start and lastSlot if appropriate. The task start will be
1361
+ # set to the begining of the first booked slot. The lastSlot
1362
+ # will be set to the last booked slot
1363
+ @lastSlot = date if @lastSlot.nil? || date > @lastSlot
1364
+ @tentativeEnd = tEnd if @tentativeEnd.nil? ||
1365
+ @tentativeEnd < tEnd
1366
+ @property['start', @scenarioIdx] = date if a('start').nil? ||
1367
+ date < a('start')
1368
+
1369
+ unless a('assignedresources').include?(booking.resource)
1370
+ @property['assignedresources', @scenarioIdx] << booking.resource
1371
+ end
1372
+ end
1373
+ if a('length') > 0 && @project.isWorkingTime(date, tEnd)
1374
+ # For tasks with a 'length' we track the covered work time and
1375
+ # set the task to 'scheduled' when we have enough length.
1376
+ @doneLength += 1
1377
+ if @doneLength >= a('length')
1378
+ @property['end', @scenarioIdx] = tEnd
1379
+ @property['scheduled', @scenarioIdx] = true
1380
+ end
1381
+ end
1382
+ date = tEnd
1383
+ end
1384
+ if a('duration') > 0
1385
+ # For tasks with a 'duration' we track the covered duration and
1386
+ # set the task to 'scheduled' when we have enough duration.
1387
+ @doneDuration = ((@tentativeEnd - a('start')) /
1388
+ @project['scheduleGranularity']).to_i
1389
+ if @doneDuration >= a('duration')
1390
+ @property['end', @scenarioIdx] = tEnd
1391
+ @property['scheduled', @scenarioIdx] = true
1392
+ end
1393
+ end
1394
+ end
1395
+ end
1396
+ end
1397
+
1398
+ # This function determines if a task is really a milestones and marks them
1399
+ # accordingly.
1400
+ def markMilestone
1401
+ return if @property.container? || hasDurationSpec? ||
1402
+ !a('booking').empty? || !a('allocate').empty?
1403
+
1404
+ # The following cases qualify for an automatic milestone promotion.
1405
+ # - --> -
1406
+ # | --> -
1407
+ # |D --> -
1408
+ # -D --> -
1409
+ # - <-- -
1410
+ # - <-- |
1411
+ # - <-- -D
1412
+ # - <-- |D
1413
+ hasStartSpec = !a('start').nil? || !a('depends').empty?
1414
+ hasEndSpec = !a('end').nil? || !a('precedes').empty?
1415
+
1416
+ @property['milestone', @scenarioIdx] =
1417
+ (hasStartSpec && a('forward') && !hasEndSpec) ||
1418
+ (!hasStartSpec && !a('forward') && hasEndSpec) ||
1419
+ (!hasStartSpec && !hasEndSpec)
1420
+ end
1421
+
1422
+ def checkDependency(dependency, depType)
1423
+ if (depTask = dependency.resolve(@project)).nil?
1424
+ # Remove the broken dependency. It could cause trouble later on.
1425
+ @property[depType, @scenarioIdx].delete(dependency)
1426
+ error('task_depend_unknown',
1427
+ "Task #{@property.fullId} has unknown #{depType} " +
1428
+ "#{dependency.taskId}")
1429
+ end
1430
+
1431
+ if depTask == @property
1432
+ # Remove the broken dependency. It could cause trouble later on.
1433
+ @property[depType, @scenarioIdx].delete(dependency)
1434
+ error('task_depend_self', "Task #{@property.fullId} cannot " +
1435
+ "depend on self")
1436
+ end
1437
+
1438
+ if depTask.isChildOf?(@property)
1439
+ # Remove the broken dependency. It could cause trouble later on.
1440
+ @property[depType, @scenarioIdx].delete(dependency)
1441
+ error('task_depend_child',
1442
+ "Task #{@property.fullId} cannot depend on child " +
1443
+ "#{depTask.fullId}")
1444
+ end
1445
+
1446
+ if @property.isChildOf?(depTask)
1447
+ # Remove the broken dependency. It could cause trouble later on.
1448
+ @property[depType, @scenarioIdx].delete(dependency)
1449
+ error('task_depend_parent',
1450
+ "Task #{@property.fullId} cannot depend on parent " +
1451
+ "#{depTask.fullId}")
1452
+ end
1453
+
1454
+ @property[depType, @scenarioIdx].each do |dep|
1455
+ if dep.task == depTask && dep != dependency
1456
+ # Remove the broken dependency. It could cause trouble later on.
1457
+ @property[depType, @scenarioIdx].delete(dependency)
1458
+ error('task_depend_multi',
1459
+ "No need to specify dependency #{depTask.fullId} multiple " +
1460
+ "times for task #{@property.fullId}.")
1461
+ end
1462
+ end
1463
+
1464
+ depTask
1465
+ end
1466
+
1467
+ # Set @startIsDetermed or @endIsDetermed (depending on _setStart) to
1468
+ # _value_.
1469
+ def setDetermination(setStart, value)
1470
+ setStart ? @startIsDetermed = value : @endIsDetermed = value
1471
+ end
1472
+
1473
+ # This function is called to propagate the start or end date of the
1474
+ # current task to a dependend Task +task+. If +atEnd+ is true, the date
1475
+ # should be propagated to the end of the +task+, otherwise to the start.
1476
+ def propagateDateToDep(task, atEnd)
1477
+ #puts "Propagate #{atEnd ? 'end' : 'start'} to dep. #{task.fullId}"
1478
+ # Don't propagate if the task is already completely scheduled or is a
1479
+ # container.
1480
+ return if task['scheduled', @scenarioIdx] || task.container?
1481
+
1482
+ # Don't propagate if the task already has a date for that end.
1483
+ return unless task[atEnd ? 'end' : 'start', @scenarioIdx].nil?
1484
+
1485
+ # Don't propagate if the task has a duration or is a milestone and the
1486
+ # task end to set is in the scheduling direction.
1487
+ return if task.hasDurationSpec?(@scenarioIdx) &&
1488
+ !(atEnd ^ task['forward', @scenarioIdx])
1489
+
1490
+ # Check if all other dependencies for that task end have been determined
1491
+ # already and use the latest or earliest possible date. Don't propagate
1492
+ # if we don't have all dates yet.
1493
+ return if (nDate = (atEnd ? task.latestEnd(@scenarioIdx) :
1494
+ task.earliestStart(@scenarioIdx))).nil?
1495
+
1496
+ # Looks like it is ok to propagate the date.
1497
+ task.propagateDate(@scenarioIdx, nDate, atEnd)
1498
+ # puts "Propagate #{atEnd ? 'end' : 'start'} to dep. #{task.fullId} done"
1499
+ end
1500
+
1501
+ # This is a helper function for calcPathCriticalness(). It computes the
1502
+ # larges criticalness of the pathes through the end-successors of this task
1503
+ # and all its parent tasks.
1504
+ def calcPathCriticalnessEndSuccs
1505
+ maxCriticalness = 0.0
1506
+ # Gather a list of all end-successors of this task and its parent task.
1507
+ tList = []
1508
+ p = @property
1509
+ while (p)
1510
+ p['endsuccs', @scenarioIdx].each do |task, onEnd|
1511
+ tList << [ task, onEnd ] unless tList.include?([ task, onEnd ])
1512
+ end
1513
+ p = p.parent
1514
+ end
1515
+
1516
+ tList.each do |task, onEnd|
1517
+ if (criticalness = task.calcPathCriticalness(@scenarioIdx, onEnd)) >
1518
+ maxCriticalness
1519
+ maxCriticalness = criticalness
1520
+ end
1521
+ end
1522
+
1523
+ maxCriticalness
1524
+ end
1525
+
1526
+ # Calculate the current completion degree for tasks that have no user
1527
+ # specified completion value.
1528
+ def calcCompletion
1529
+ # If the user provided a completion degree we are not touching it.
1530
+ if property.provided('complete')
1531
+ return
1532
+ end
1533
+
1534
+ if a('start').nil? || a('end').nil?
1535
+ @property['complete', @scenarioIdx] = 0.0
1536
+ return
1537
+ end
1538
+
1539
+ if a('milestone')
1540
+ @property['complete', @scenarioIdx] =
1541
+ @property['end', @scenarioIdx] <= @project['now'] ? 100.0 : 0.0
1542
+ elsif @property.container?
1543
+ #TODO
1544
+ else
1545
+ # Normal leaf task
1546
+ if a('end') <= @project['now']
1547
+ # The task has ended already. It's 100% complete.
1548
+ completion = 100.0
1549
+ elsif @project['now'] <= a('start')
1550
+ # The task has not started yet. Its' 0% complete.
1551
+ completion = 0.0
1552
+ else
1553
+ # The task is in progress. Calculate the current completion degree.
1554
+ completion = ((@project['now'] - a('start')) /
1555
+ (a('end') - a('start'))) * 100.0
1556
+ end
1557
+ @property['complete', @scenarioIdx] = completion
1558
+ end
1559
+ end
1560
+
1561
+ # Compute the turnover generated by this Task for a given Account _account_
1562
+ # during the interval specified by _startIdx_ and _endIdx_. These can either
1563
+ # be TjTime values or Scoreboard indexes. If a Resource _resource_ is given,
1564
+ # only the turnover directly generated by the resource is taken into
1565
+ # account.
1566
+ def turnover(startIdx, endIdx, account, resource = nil)
1567
+ amount = 0.0
1568
+ if @property.container?
1569
+ @property.children.each do |child|
1570
+ amount += child.turnover(@scenarioIdx, startIdx, endIdx, account,
1571
+ resource)
1572
+ end
1573
+ end
1574
+
1575
+ # If there are no chargeset defined for this task, we don't need to
1576
+ # compute the resource related or other cost.
1577
+ unless a('chargeset').empty?
1578
+ resourceCost = 0.0
1579
+ otherCost = 0.0
1580
+
1581
+ # Container tasks don't have resource cost.
1582
+ unless @property.container?
1583
+ if resource
1584
+ resourceCost = resource.cost(@scenarioIdx, startIdx, endIdx,
1585
+ @property)
1586
+ else
1587
+ a('assignedresources').each do |r|
1588
+ resourceCost += r.cost(@scenarioIdx, startIdx, endIdx, @property)
1589
+ end
1590
+ end
1591
+ end
1592
+
1593
+ unless a('charge').empty?
1594
+ # Add one-time and periodic charges to the amount.
1595
+ startDate = startIdx.is_a?(TjTime) ? startIdx :
1596
+ @project.idxToDate(startIdx)
1597
+ endDate = endIdx.is_a?(TjTime) ? endIdx :
1598
+ @project.idxToDate(endIdx)
1599
+ iv = Interval.new(startDate, endDate)
1600
+ a('charge').each do |charge|
1601
+ otherCost += charge.turnover(iv)
1602
+ end
1603
+ end
1604
+
1605
+ totalCost = resourceCost + otherCost
1606
+ # Now weight the total cost by the share of the account
1607
+ a('chargeset').each do |set|
1608
+ set.each do |accnt, share|
1609
+ if share > 0.0 && (accnt == account || accnt.isChildOf?(account))
1610
+ amount += totalCost * share
1611
+ end
1612
+ end
1613
+ end
1614
+ end
1615
+
1616
+ amount
1617
+ end
1618
+
1619
+ end
1620
+
1621
+ end
1622
+