taskjuggler 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+