taskjuggler 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +37 -5
- data/README.rdoc +4 -2
- data/benchmarks/UTF-8-Strings.rb +58 -0
- data/benchmarks/css/tjmanual.css +0 -1
- data/benchmarks/htmltaskreport-1.html +47702 -0
- data/benchmarks/htmltaskreport-2.html +23345 -0
- data/benchmarks/htmltaskreport-3.html +23265 -0
- data/benchmarks/htmltaskreport-4.html +22135 -0
- data/benchmarks/profile.clt +36082 -0
- data/benchmarks/tj3.profile +0 -0
- data/benchmarks/tj3.profile.grind +6185 -0
- data/benchmarks/tj3.profile.symbols +432 -0
- data/benchmarks/tj3.profile.txt +448 -0
- data/data/tjp.vim +689 -0
- data/doc/AppConfig.html +26 -2
- data/doc/CHANGELOG.html +60 -8
- data/doc/COPYING.html +26 -2
- data/doc/Diff/Hunk.html +26 -2
- data/doc/Diff.html +26 -2
- data/doc/Diffable.html +26 -2
- data/doc/DiffableString.html +26 -2
- data/doc/Object.html +26 -104
- data/doc/README_rdoc.html +39 -7
- data/doc/RuntimeConfig.html +26 -2
- data/doc/String.html +26 -2
- data/doc/TaskJuggler/Account.html +27 -3
- data/doc/TaskJuggler/AccountAttribute.html +30 -6
- data/doc/TaskJuggler/AccountScenario.html +28 -4
- data/doc/TaskJuggler/Allocation.html +33 -9
- data/doc/TaskJuggler/AllocationAttribute.html +31 -7
- data/doc/TaskJuggler/AttributeBase.html +162 -111
- data/doc/TaskJuggler/AttributeDefinition.html +26 -2
- data/doc/TaskJuggler/AttributeOverwrite.html +26 -2
- data/doc/TaskJuggler/BatchProcessor.html +26 -2
- data/doc/TaskJuggler/Booking.html +29 -5
- data/doc/TaskJuggler/BookingListAttribute.html +29 -5
- data/doc/TaskJuggler/BooleanAttribute.html +30 -6
- data/doc/TaskJuggler/CSVFile.html +26 -2
- data/doc/TaskJuggler/CellSettingPattern.html +26 -2
- data/doc/TaskJuggler/CellSettingPatternList.html +26 -2
- data/doc/TaskJuggler/Charge.html +30 -6
- data/doc/TaskJuggler/ChargeListAttribute.html +29 -5
- data/doc/TaskJuggler/ChargeSet.html +26 -2
- data/doc/TaskJuggler/ChargeSetListAttribute.html +30 -6
- data/doc/TaskJuggler/CollisionDetector.html +26 -2
- data/doc/TaskJuggler/ColumnListAttribute.html +28 -4
- data/doc/TaskJuggler/ColumnTable.html +26 -2
- data/doc/TaskJuggler/Daemon.html +26 -2
- data/doc/TaskJuggler/DataCache.html +26 -2
- data/doc/TaskJuggler/DataCacheEntry.html +26 -2
- data/doc/TaskJuggler/DateAttribute.html +30 -6
- data/doc/TaskJuggler/DefinitionListAttribute.html +28 -4
- data/doc/TaskJuggler/DependencyListAttribute.html +30 -6
- data/doc/TaskJuggler/DurationAttribute.html +31 -7
- data/doc/TaskJuggler/FileList.html +26 -2
- data/doc/TaskJuggler/FileRecord.html +26 -2
- data/doc/TaskJuggler/FixnumAttribute.html +28 -4
- data/doc/TaskJuggler/FlagListAttribute.html +30 -6
- data/doc/TaskJuggler/FloatAttribute.html +29 -5
- data/doc/TaskJuggler/FormatListAttribute.html +29 -5
- data/doc/TaskJuggler/GanttChart.html +26 -2
- data/doc/TaskJuggler/GanttContainer.html +26 -2
- data/doc/TaskJuggler/GanttHeader.html +26 -2
- data/doc/TaskJuggler/GanttHeaderScaleItem.html +26 -2
- data/doc/TaskJuggler/GanttLine.html +27 -3
- data/doc/TaskJuggler/GanttLoadStack.html +26 -2
- data/doc/TaskJuggler/GanttMilestone.html +26 -2
- data/doc/TaskJuggler/GanttRouter.html +26 -2
- data/doc/TaskJuggler/GanttTaskBar.html +26 -2
- data/doc/TaskJuggler/HTMLDocument.html +26 -2
- data/doc/TaskJuggler/HTMLElements.html +623 -0
- data/doc/TaskJuggler/HTMLGraphics.html +26 -2
- data/doc/TaskJuggler/ICalReport.html +864 -0
- data/doc/TaskJuggler/ICalendar/Component.html +969 -0
- data/doc/TaskJuggler/ICalendar/Event.html +742 -0
- data/doc/TaskJuggler/ICalendar/Journal.html +722 -0
- data/doc/TaskJuggler/ICalendar/Person.html +633 -0
- data/doc/TaskJuggler/ICalendar/Todo.html +789 -0
- data/doc/TaskJuggler/ICalendar.html +1035 -0
- data/doc/TaskJuggler/Interval.html +99 -172
- data/doc/TaskJuggler/IntervalList.html +31 -7
- data/doc/TaskJuggler/JobInfo.html +26 -2
- data/doc/TaskJuggler/Journal.html +26 -2
- data/doc/TaskJuggler/JournalEntry.html +26 -2
- data/doc/TaskJuggler/JournalEntryList.html +26 -2
- data/doc/TaskJuggler/KeywordArray.html +26 -2
- data/doc/TaskJuggler/KeywordDocumentation.html +26 -2
- data/doc/TaskJuggler/Limits/Limit.html +124 -49
- data/doc/TaskJuggler/Limits.html +139 -156
- data/doc/TaskJuggler/LimitsAttribute.html +39 -14
- data/doc/TaskJuggler/ListAttributeBase.html +35 -11
- data/doc/TaskJuggler/Log.html +26 -2
- data/doc/TaskJuggler/LogFile.html +26 -2
- data/doc/TaskJuggler/LogicalAttribute.html +26 -2
- data/doc/TaskJuggler/LogicalExpression.html +26 -2
- data/doc/TaskJuggler/LogicalExpressionAttribute.html +35 -11
- data/doc/TaskJuggler/LogicalExpressionListAttribute.html +35 -11
- data/doc/TaskJuggler/LogicalFlag.html +26 -2
- data/doc/TaskJuggler/LogicalFunction.html +28 -4
- data/doc/TaskJuggler/LogicalOperation.html +26 -2
- data/doc/TaskJuggler/ManagerResponsibilities.html +26 -2
- data/doc/TaskJuggler/ManagerStatusRecord.html +26 -2
- data/doc/TaskJuggler/Message.html +26 -2
- data/doc/TaskJuggler/MessageHandler.html +108 -62
- data/doc/TaskJuggler/Navigator.html +26 -2
- data/doc/TaskJuggler/NavigatorElement.html +26 -2
- data/doc/TaskJuggler/NikuProject.html +26 -2
- data/doc/TaskJuggler/NikuReport.html +27 -3
- data/doc/TaskJuggler/NikuResource.html +26 -2
- data/doc/TaskJuggler/NodeListAttribute.html +31 -7
- data/doc/TaskJuggler/PlaceHolderCell.html +26 -2
- data/doc/TaskJuggler/ProcessIntercom.html +26 -2
- data/doc/TaskJuggler/ProcessIntercomIface.html +26 -2
- data/doc/TaskJuggler/Project.html +1236 -973
- data/doc/TaskJuggler/ProjectBroker.html +26 -2
- data/doc/TaskJuggler/ProjectBrokerIface.html +26 -2
- data/doc/TaskJuggler/ProjectFileParser.html +29 -4
- data/doc/TaskJuggler/ProjectFileScanner.html +56 -31
- data/doc/TaskJuggler/ProjectRecord.html +26 -2
- data/doc/TaskJuggler/ProjectServer.html +26 -2
- data/doc/TaskJuggler/ProjectServerIface.html +26 -2
- data/doc/TaskJuggler/PropertyAttribute.html +35 -11
- data/doc/TaskJuggler/PropertyList.html +26 -2
- data/doc/TaskJuggler/PropertySet.html +126 -106
- data/doc/TaskJuggler/PropertyTreeNode.html +595 -551
- data/doc/TaskJuggler/Query.html +127 -100
- data/doc/TaskJuggler/RTFHandlers.html +26 -2
- data/doc/TaskJuggler/RTFNavigator.html +26 -2
- data/doc/TaskJuggler/RTFQuery.html +28 -4
- data/doc/TaskJuggler/RTFReport.html +26 -2
- data/doc/TaskJuggler/RTFReportLink.html +26 -2
- data/doc/TaskJuggler/RTFWithQuerySupport.html +26 -2
- data/doc/TaskJuggler/RealFormat.html +26 -2
- data/doc/TaskJuggler/RealFormatAttribute.html +31 -7
- data/doc/TaskJuggler/ReferenceAttribute.html +58 -34
- data/doc/TaskJuggler/Report.html +427 -284
- data/doc/TaskJuggler/ReportBase.html +28 -102
- data/doc/TaskJuggler/ReportContext.html +26 -2
- data/doc/TaskJuggler/ReportScenario.html +633 -0
- data/doc/TaskJuggler/ReportServer.html +26 -2
- data/doc/TaskJuggler/ReportServerIface.html +26 -2
- data/doc/TaskJuggler/ReportServerRecord.html +26 -2
- data/doc/TaskJuggler/ReportServlet.html +26 -2
- data/doc/TaskJuggler/ReportTable.html +26 -2
- data/doc/TaskJuggler/ReportTableCell.html +26 -2
- data/doc/TaskJuggler/ReportTableColumn.html +26 -2
- data/doc/TaskJuggler/ReportTableLegend.html +26 -2
- data/doc/TaskJuggler/ReportTableLine.html +26 -2
- data/doc/TaskJuggler/Resource.html +225 -163
- data/doc/TaskJuggler/ResourceListAttribute.html +71 -47
- data/doc/TaskJuggler/ResourceListRE.html +26 -2
- data/doc/TaskJuggler/ResourceScenario.html +645 -603
- data/doc/TaskJuggler/RichText.html +26 -2
- data/doc/TaskJuggler/RichTextAttribute.html +53 -29
- data/doc/TaskJuggler/RichTextDocument.html +26 -2
- data/doc/TaskJuggler/RichTextElement.html +26 -2
- data/doc/TaskJuggler/RichTextFunctionExample.html +26 -2
- data/doc/TaskJuggler/RichTextFunctionHandler.html +26 -2
- data/doc/TaskJuggler/RichTextImage.html +26 -2
- data/doc/TaskJuggler/RichTextIntermediate.html +29 -5
- data/doc/TaskJuggler/RichTextParser.html +26 -2
- data/doc/TaskJuggler/RichTextScanner.html +26 -2
- data/doc/TaskJuggler/RichTextSnip.html +26 -2
- data/doc/TaskJuggler/RichTextSyntaxRules.html +26 -2
- data/doc/TaskJuggler/Scenario.html +26 -2
- data/doc/TaskJuggler/ScenarioData.html +95 -28
- data/doc/TaskJuggler/ScenarioListAttribute.html +39 -15
- data/doc/TaskJuggler/Scoreboard.html +137 -73
- data/doc/TaskJuggler/ScoreboardInterval.html +1116 -0
- data/doc/TaskJuggler/SheetHandlerBase.html +26 -2
- data/doc/TaskJuggler/SheetReceiver.html +26 -2
- data/doc/TaskJuggler/SheetSender.html +26 -2
- data/doc/TaskJuggler/Shift.html +27 -3
- data/doc/TaskJuggler/ShiftAssignment.html +27 -3
- data/doc/TaskJuggler/ShiftAssignments.html +174 -149
- data/doc/TaskJuggler/ShiftAssignmentsAttribute.html +41 -16
- data/doc/TaskJuggler/ShiftScenario.html +26 -2
- data/doc/TaskJuggler/SimpleQueryExpander.html +26 -2
- data/doc/TaskJuggler/SortListAttribute.html +35 -11
- data/doc/TaskJuggler/StatusSheetReceiver.html +26 -2
- data/doc/TaskJuggler/StatusSheetReport.html +26 -2
- data/doc/TaskJuggler/StatusSheetSender.html +26 -2
- data/doc/TaskJuggler/StdIoWrapper.html +26 -2
- data/doc/TaskJuggler/StringAttribute.html +39 -15
- data/doc/TaskJuggler/SymbolAttribute.html +35 -11
- data/doc/TaskJuggler/SyntaxReference.html +26 -2
- data/doc/TaskJuggler/TOCEntry.html +26 -2
- data/doc/TaskJuggler/TSResourceRecord.html +26 -2
- data/doc/TaskJuggler/TSTaskRecord.html +26 -2
- data/doc/TaskJuggler/TableColumnDefinition.html +26 -2
- data/doc/TaskJuggler/TableOfContents.html +26 -2
- data/doc/TaskJuggler/TableReport.html +239 -214
- data/doc/TaskJuggler/TagFile/TagFileEntry.html +841 -0
- data/doc/TaskJuggler/TagFile.html +817 -0
- data/doc/TaskJuggler/Task.html +27 -3
- data/doc/TaskJuggler/TaskDepListAttribute.html +47 -23
- data/doc/TaskJuggler/TaskDependency.html +26 -2
- data/doc/TaskJuggler/TaskListAttribute.html +47 -23
- data/doc/TaskJuggler/TaskListRE.html +26 -2
- data/doc/TaskJuggler/TaskScenario.html +2235 -2178
- data/doc/TaskJuggler/TernarySearchTree.html +26 -2
- data/doc/TaskJuggler/TextFormatter.html +26 -2
- data/doc/TaskJuggler/TextParser/Macro.html +26 -2
- data/doc/TaskJuggler/TextParser/MacroTable.html +26 -2
- data/doc/TaskJuggler/TextParser/Pattern.html +26 -2
- data/doc/TaskJuggler/TextParser/Rule.html +26 -2
- data/doc/TaskJuggler/TextParser/Scanner/BufferStreamHandle.html +32 -8
- data/doc/TaskJuggler/TextParser/Scanner/FileStreamHandle.html +39 -15
- data/doc/TaskJuggler/TextParser/Scanner/MacroStackEntry.html +26 -2
- data/doc/TaskJuggler/TextParser/Scanner/StreamHandle.html +119 -86
- data/doc/TaskJuggler/TextParser/Scanner.html +317 -291
- data/doc/TaskJuggler/TextParser/SourceFileInfo.html +26 -2
- data/doc/TaskJuggler/TextParser/StackElement.html +26 -2
- data/doc/TaskJuggler/TextParser/State.html +26 -2
- data/doc/TaskJuggler/TextParser/StateTransition.html +26 -2
- data/doc/TaskJuggler/TextParser/TextParserResultArray.html +26 -2
- data/doc/TaskJuggler/TextParser/TokenDoc.html +26 -2
- data/doc/TaskJuggler/TextParser.html +85 -61
- data/doc/TaskJuggler/TextReport.html +26 -2
- data/doc/TaskJuggler/TimeInterval.html +841 -0
- data/doc/TaskJuggler/{IntervalListAttribute.html → TimeIntervalListAttribute.html} +33 -9
- data/doc/TaskJuggler/TimeSheet.html +26 -2
- data/doc/TaskJuggler/TimeSheetReceiver.html +26 -2
- data/doc/TaskJuggler/TimeSheetRecord.html +27 -3
- data/doc/TaskJuggler/TimeSheetReport.html +29 -5
- data/doc/TaskJuggler/TimeSheetSender.html +26 -2
- data/doc/TaskJuggler/TimeSheetSummary.html +26 -2
- data/doc/TaskJuggler/TimeSheets.html +26 -2
- data/doc/TaskJuggler/Tj3.html +150 -111
- data/doc/TaskJuggler/Tj3AppBase.html +26 -2
- data/doc/TaskJuggler/Tj3Client.html +26 -2
- data/doc/TaskJuggler/Tj3Daemon.html +26 -2
- data/doc/TaskJuggler/Tj3Man.html +26 -2
- data/doc/TaskJuggler/Tj3SheetAppBase.html +26 -2
- data/doc/TaskJuggler/Tj3SsReceiver.html +26 -2
- data/doc/TaskJuggler/Tj3SsSender.html +26 -2
- data/doc/TaskJuggler/Tj3TsReceiver.html +26 -2
- data/doc/TaskJuggler/Tj3TsSender.html +26 -2
- data/doc/TaskJuggler/Tj3TsSummary.html +26 -2
- data/doc/TaskJuggler/TjException.html +26 -2
- data/doc/TaskJuggler/TjRuntimeError.html +26 -2
- data/doc/TaskJuggler/TjTime.html +539 -477
- data/doc/TaskJuggler/TjpExample.html +26 -2
- data/doc/TaskJuggler/TjpExportRE.html +240 -161
- data/doc/TaskJuggler/TjpSyntaxRules.html +4236 -3844
- data/doc/TaskJuggler/URLParameter.html +26 -2
- data/doc/TaskJuggler/UserManual.html +26 -2
- data/doc/TaskJuggler/VimSyntax.html +31 -7
- data/doc/TaskJuggler/WebServer.html +26 -2
- data/doc/TaskJuggler/WelcomePage.html +26 -2
- data/doc/TaskJuggler/WorkingHours.html +227 -152
- data/doc/TaskJuggler/WorkingHoursAttribute.html +62 -38
- data/doc/TaskJuggler/XMLBlob.html +26 -2
- data/doc/TaskJuggler/XMLComment.html +26 -2
- data/doc/TaskJuggler/XMLDocument.html +26 -2
- data/doc/TaskJuggler/XMLElement.html +26 -2
- data/doc/TaskJuggler/XMLNamedText.html +26 -2
- data/doc/TaskJuggler/XMLText.html +26 -2
- data/doc/TaskJuggler.html +486 -427
- data/doc/index.html +801 -659
- data/doc/lib/taskjuggler/AccountScenario_rb.html +1 -1
- data/doc/lib/taskjuggler/Account_rb.html +1 -1
- data/doc/lib/taskjuggler/AlgorithmDiff_rb.html +1 -1
- data/doc/lib/taskjuggler/Allocation_rb.html +1 -1
- data/doc/lib/taskjuggler/AppConfig_rb.html +1 -1
- data/doc/lib/taskjuggler/AttributeBase_rb.html +1 -1
- data/doc/lib/taskjuggler/AttributeDefinition_rb.html +1 -1
- data/doc/lib/taskjuggler/Attributes_rb.html +1 -1
- data/doc/lib/taskjuggler/BatchProcessor_rb.html +1 -1
- data/doc/lib/taskjuggler/Booking_rb.html +1 -1
- data/doc/lib/taskjuggler/ChargeSet_rb.html +1 -1
- data/doc/lib/taskjuggler/Charge_rb.html +1 -1
- data/doc/lib/taskjuggler/DataCache_rb.html +1 -1
- data/doc/lib/taskjuggler/FileList_rb.html +1 -1
- data/doc/lib/taskjuggler/HTMLDocument_rb.html +1 -1
- data/doc/lib/{ruby-signal-bug_rb.html → taskjuggler/HTMLElements_rb.html} +9 -9
- data/doc/lib/{exchangebug_rb.html → taskjuggler/ICalendar_rb.html} +9 -9
- data/doc/lib/taskjuggler/IntervalList_rb.html +1 -1
- data/doc/lib/taskjuggler/Interval_rb.html +1 -1
- data/doc/lib/taskjuggler/Journal_rb.html +1 -1
- data/doc/lib/taskjuggler/KeywordArray_rb.html +1 -1
- data/doc/lib/taskjuggler/KeywordDocumentation_rb.html +1 -1
- data/doc/lib/taskjuggler/Limits_rb.html +1 -1
- data/doc/lib/taskjuggler/LogFile_rb.html +1 -1
- data/doc/lib/taskjuggler/Log_rb.html +1 -1
- data/doc/lib/taskjuggler/LogicalExpression_rb.html +1 -1
- data/doc/lib/taskjuggler/LogicalFunction_rb.html +1 -1
- data/doc/lib/taskjuggler/LogicalOperation_rb.html +1 -1
- data/doc/lib/taskjuggler/MessageHandler_rb.html +1 -1
- data/doc/lib/taskjuggler/ProjectFileParser_rb.html +1 -1
- data/doc/lib/taskjuggler/ProjectFileScanner_rb.html +1 -1
- data/doc/lib/taskjuggler/Project_rb.html +1 -1
- data/doc/lib/taskjuggler/PropertyList_rb.html +1 -1
- data/doc/lib/taskjuggler/PropertySet_rb.html +1 -1
- data/doc/lib/taskjuggler/PropertyTreeNode_rb.html +1 -1
- data/doc/lib/taskjuggler/Query_rb.html +1 -1
- data/doc/lib/taskjuggler/RealFormat_rb.html +1 -1
- data/doc/lib/taskjuggler/ResourceScenario_rb.html +1 -1
- data/doc/lib/taskjuggler/Resource_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/Document_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/Element_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/FunctionExample_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/FunctionHandler_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/Parser_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/RTFHandlers_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/RTFNavigator_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/RTFQuery_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/RTFReportLink_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/RTFReport_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/RTFWithQuerySupport_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/Scanner_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/Snip_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/SyntaxRules_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/TOCEntry_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText/TableOfContents_rb.html +1 -1
- data/doc/lib/taskjuggler/RichText_rb.html +1 -1
- data/doc/lib/taskjuggler/RuntimeConfig_rb.html +1 -1
- data/doc/lib/taskjuggler/ScenarioData_rb.html +1 -1
- data/doc/lib/taskjuggler/Scenario_rb.html +1 -1
- data/doc/lib/taskjuggler/Scoreboard_rb.html +1 -1
- data/doc/lib/taskjuggler/SheetHandlerBase_rb.html +1 -1
- data/doc/lib/taskjuggler/SheetReceiver_rb.html +1 -1
- data/doc/lib/taskjuggler/SheetSender_rb.html +1 -1
- data/doc/lib/taskjuggler/ShiftAssignments_rb.html +1 -1
- data/doc/lib/taskjuggler/ShiftScenario_rb.html +1 -1
- data/doc/lib/taskjuggler/Shift_rb.html +1 -1
- data/doc/lib/taskjuggler/SimpleQueryExpander_rb.html +1 -1
- data/doc/lib/taskjuggler/StatusSheetReceiver_rb.html +1 -1
- data/doc/lib/taskjuggler/StatusSheetSender_rb.html +1 -1
- data/doc/lib/taskjuggler/StdIoWrapper_rb.html +1 -1
- data/doc/lib/taskjuggler/SyntaxReference_rb.html +1 -1
- data/doc/lib/taskjuggler/TableColumnDefinition_rb.html +1 -1
- data/doc/lib/taskjuggler/TaskDependency_rb.html +1 -1
- data/doc/lib/taskjuggler/TaskJuggler_rb.html +1 -1
- data/doc/lib/taskjuggler/TaskScenario_rb.html +1 -1
- data/doc/lib/taskjuggler/Task_rb.html +1 -1
- data/doc/lib/taskjuggler/TernarySearchTree_rb.html +1 -1
- data/doc/lib/taskjuggler/TextFormatter_rb.html +1 -1
- data/doc/lib/taskjuggler/TextParser/MacroTable_rb.html +1 -1
- data/doc/lib/taskjuggler/TextParser/Pattern_rb.html +1 -1
- data/doc/lib/taskjuggler/TextParser/Rule_rb.html +1 -1
- data/doc/lib/taskjuggler/TextParser/Scanner_rb.html +1 -1
- data/doc/lib/taskjuggler/TextParser/SourceFileInfo_rb.html +1 -1
- data/doc/lib/taskjuggler/TextParser/StackElement_rb.html +1 -1
- data/doc/lib/taskjuggler/TextParser/State_rb.html +1 -1
- data/doc/lib/taskjuggler/TextParser/TokenDoc_rb.html +1 -1
- data/doc/lib/taskjuggler/TextParser_rb.html +1 -1
- data/doc/lib/taskjuggler/TimeSheetReceiver_rb.html +1 -1
- data/doc/lib/taskjuggler/TimeSheetSender_rb.html +1 -1
- data/doc/lib/taskjuggler/TimeSheetSummary_rb.html +1 -1
- data/doc/lib/taskjuggler/TimeSheets_rb.html +1 -1
- data/doc/lib/taskjuggler/Tj3AppBase_rb.html +1 -1
- data/doc/lib/taskjuggler/Tj3Config_rb.html +1 -1
- data/doc/lib/taskjuggler/Tj3SheetAppBase_rb.html +1 -1
- data/doc/lib/taskjuggler/TjException_rb.html +1 -1
- data/doc/lib/taskjuggler/TjTime_rb.html +1 -1
- data/doc/lib/taskjuggler/TjpExample_rb.html +1 -1
- data/doc/lib/taskjuggler/TjpSyntaxRules_rb.html +1 -1
- data/doc/lib/taskjuggler/URLParameter_rb.html +1 -1
- data/doc/lib/taskjuggler/UTF8String_rb.html +1 -1
- data/doc/lib/taskjuggler/UserManual_rb.html +1 -1
- data/doc/lib/taskjuggler/VimSyntax_rb.html +1 -1
- data/doc/lib/taskjuggler/WorkingHours_rb.html +1 -1
- data/doc/lib/taskjuggler/XMLDocument_rb.html +1 -1
- data/doc/lib/taskjuggler/XMLElement_rb.html +1 -1
- data/doc/lib/taskjuggler/apps/Tj3Client_rb.html +1 -1
- data/doc/lib/taskjuggler/apps/Tj3Daemon_rb.html +1 -1
- data/doc/lib/taskjuggler/apps/Tj3Man_rb.html +1 -1
- data/doc/lib/taskjuggler/apps/Tj3SsReceiver_rb.html +1 -1
- data/doc/lib/taskjuggler/apps/Tj3SsSender_rb.html +1 -1
- data/doc/lib/taskjuggler/apps/Tj3TsReceiver_rb.html +1 -1
- data/doc/lib/taskjuggler/apps/Tj3TsSender_rb.html +1 -1
- data/doc/lib/taskjuggler/apps/Tj3TsSummary_rb.html +1 -1
- data/doc/lib/taskjuggler/apps/Tj3_rb.html +1 -1
- data/doc/lib/taskjuggler/daemon/Daemon_rb.html +1 -1
- data/doc/lib/taskjuggler/daemon/ProcessIntercom_rb.html +1 -1
- data/doc/lib/taskjuggler/daemon/ProjectBroker_rb.html +1 -1
- data/doc/lib/taskjuggler/daemon/ProjectServer_rb.html +1 -1
- data/doc/lib/taskjuggler/daemon/ReportServer_rb.html +1 -1
- data/doc/lib/taskjuggler/daemon/ReportServlet_rb.html +1 -1
- data/doc/lib/taskjuggler/daemon/WebServer_rb.html +1 -1
- data/doc/lib/taskjuggler/daemon/WelcomePage_rb.html +1 -1
- data/doc/lib/taskjuggler/deep_copy_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/CSVFile_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/CollisionDetector_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/ColumnTable_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/GanttChart_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/GanttContainer_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/GanttHeaderScaleItem_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/GanttHeader_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/GanttLine_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/GanttLoadStack_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/GanttMilestone_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/GanttRouter_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/GanttTaskBar_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/HTMLGraphics_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/ICalReport_rb.html +71 -0
- data/doc/lib/taskjuggler/reports/Navigator_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/NikuReport_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/ReportBase_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/ReportContext_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/ReportTableCell_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/ReportTableColumn_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/ReportTableLegend_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/ReportTableLine_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/ReportTable_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/Report_rb.html +5 -1
- data/doc/lib/taskjuggler/reports/ResourceListRE_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/StatusSheetReport_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/TableReport_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/TagFile_rb.html +71 -0
- data/doc/lib/taskjuggler/reports/TaskListRE_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/TextReport_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/TimeSheetReport_rb.html +1 -1
- data/doc/lib/taskjuggler/reports/TjpExportRE_rb.html +1 -1
- data/doc/lib/tj3_rb.html +1 -1
- data/doc/lib/tj3client_rb.html +1 -1
- data/doc/lib/tj3d_rb.html +1 -1
- data/doc/lib/tj3man_rb.html +1 -1
- data/doc/lib/tj3ss_receiver_rb.html +1 -1
- data/doc/lib/tj3ss_sender_rb.html +1 -1
- data/doc/lib/tj3ts_receiver_rb.html +1 -1
- data/doc/lib/tj3ts_sender_rb.html +1 -1
- data/doc/lib/tj3ts_summary_rb.html +1 -1
- data/lib/taskjuggler/Account.rb +1 -1
- data/lib/taskjuggler/AccountScenario.rb +1 -1
- data/lib/taskjuggler/Allocation.rb +5 -5
- data/lib/taskjuggler/AttributeBase.rb +30 -22
- data/lib/taskjuggler/Attributes.rb +80 -78
- data/lib/taskjuggler/Booking.rb +2 -2
- data/lib/taskjuggler/Charge.rb +3 -3
- data/lib/taskjuggler/ICalendar.rb +251 -0
- data/lib/taskjuggler/Interval.rb +215 -53
- data/lib/taskjuggler/IntervalList.rb +5 -5
- data/lib/taskjuggler/Limits.rb +79 -69
- data/lib/taskjuggler/LogicalFunction.rb +2 -2
- data/lib/taskjuggler/MessageHandler.rb +11 -6
- data/lib/taskjuggler/Project.rb +208 -95
- data/lib/taskjuggler/ProjectFileParser.rb +2 -2
- data/lib/taskjuggler/ProjectFileScanner.rb +7 -6
- data/lib/taskjuggler/PropertySet.rb +1 -5
- data/lib/taskjuggler/PropertyTreeNode.rb +21 -16
- data/lib/taskjuggler/Query.rb +10 -7
- data/lib/taskjuggler/Resource.rb +6 -1
- data/lib/taskjuggler/ResourceScenario.rb +85 -69
- data/lib/taskjuggler/RichText/RTFQuery.rb +2 -2
- data/lib/taskjuggler/ScenarioData.rb +11 -1
- data/lib/taskjuggler/Scoreboard.rb +8 -1
- data/lib/taskjuggler/Shift.rb +1 -1
- data/lib/taskjuggler/ShiftAssignments.rb +22 -20
- data/lib/taskjuggler/Task.rb +1 -1
- data/lib/taskjuggler/TaskJuggler.rb +65 -51
- data/lib/taskjuggler/TaskScenario.rb +486 -419
- data/lib/taskjuggler/TextParser/Scanner.rb +22 -11
- data/lib/taskjuggler/TimeSheets.rb +1 -1
- data/lib/taskjuggler/Tj3Config.rb +1 -1
- data/lib/taskjuggler/TjTime.rb +7 -3
- data/lib/taskjuggler/TjpSyntaxRules.rb +157 -23
- data/lib/taskjuggler/VimSyntax.rb +18 -0
- data/lib/taskjuggler/WorkingHours.rb +20 -4
- data/lib/taskjuggler/apps/Tj3.rb +16 -1
- data/lib/taskjuggler/reports/GanttLine.rb +1 -1
- data/lib/taskjuggler/reports/ICalReport.rb +136 -0
- data/lib/taskjuggler/reports/NikuReport.rb +1 -1
- data/lib/taskjuggler/reports/Report.rb +59 -0
- data/lib/taskjuggler/reports/ReportBase.rb +2 -67
- data/lib/taskjuggler/reports/TableReport.rb +9 -8
- data/lib/taskjuggler/reports/TagFile.rb +120 -0
- data/lib/taskjuggler/reports/TimeSheetReport.rb +3 -3
- data/lib/taskjuggler/reports/TjpExportRE.rb +33 -9
- data/manual/Installation +126 -0
- data/spec/ICalendar_spec.rb +45 -0
- data/spec/IntervalList_spec.rb +11 -11
- data/tasks/changelog.rake +150 -60
- data/tasks/gem.rake +1 -0
- data/tasks/vim.rake +2 -2
- data/test/TestSuite/Export-Reports/refs/Booking.tjp +1 -1
- data/test/TestSuite/Export-Reports/refs/CompletedWork.tji.tjp +40 -0
- data/test/TestSuite/Export-Reports/refs/Macro-2.tjp +10 -1
- data/test/TestSuite/Export-Reports/refs/TaskPrefix.tjp +1 -1
- data/test/TestSuite/HTML-Reports/Alerts.html +160 -412
- data/test/TestSuite/HTML-Reports/CellText.html +758 -0
- data/test/TestSuite/HTML-Reports/ColumnPeriods.html +156 -0
- data/test/TestSuite/HTML-Reports/IsOngoing.html +172 -0
- data/test/TestSuite/HTML-Reports/LogicalFunctions.html +245 -0
- data/test/TestSuite/HTML-Reports/Query.html +31 -0
- data/test/TestSuite/HTML-Reports/css/tjmanual.css +0 -20
- data/test/TestSuite/HTML-Reports/css/tjreport.css +6 -7
- data/test/TestSuite/HTML-Reports/depArrows.html +851 -0
- data/test/TestSuite/HTML-Reports/profile.html +37581 -0
- data/test/TestSuite/{Scheduler/Correct → HTML-Reports/scripts}/scripts/wz_tooltip.js +20 -20
- data/test/TestSuite/Scheduler/Correct/Booking.tjp +3 -3
- data/test/TestSuite/Scheduler/Correct/Duration.tjp +3 -3
- data/test/TestSuite/Scheduler/Errors/R.html +113 -0
- data/test/TestSuite/Scheduler/Errors/allocate_no_assigned.tjp +15 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/css/tjmanual.css +0 -1
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/css/tjreport.css +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/details.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/flag-green.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/flag-red.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/flag-yellow.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/resource.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/resourcegroup.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/task.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/taskgroup.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/trend-down.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/trend-flat.png +0 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/icons/trend-up.png +0 -0
- data/test/TestSuite/Scheduler/Errors/overbooked_duration.tjp +11 -0
- data/test/TestSuite/Scheduler/Errors/overbooked_effort.tjp +11 -0
- data/test/TestSuite/Scheduler/Errors/overbooked_length.tjp +13 -0
- data/test/TestSuite/{ReportGenerator/Correct → Scheduler/Errors}/scripts/wz_tooltip.js +0 -0
- data/test/TestSuite/StatusSheets/dev2.tji +22 -0
- data/test/TestSuite/Syntax/Correct/CompletedWork.tji.tjp +38 -0
- data/test/TestSuite/Syntax/Correct/Macro-2.tjp +5 -2
- data/test/TestSuite/Syntax/Errors/scenario_after_tracking.tjp +8 -0
- data/test/TestSuite/TimeSheets/resrep.tji +7 -0
- data/test/TestSuite/TimeSheets/ts.tji +351 -0
- data/test/TestSuite/TimeSheets/tsdef.tji +2 -0
- data/test/test_Limits.rb +38 -30
- data/test/test_Scheduler.rb +2 -2
- data/test/test_ShiftAssignments.rb +8 -8
- data/test/test_WorkingHours.rb +4 -4
- metadata +1015 -1005
- data/lib/exchangebug.rb +0 -42
- data/lib/ruby-signal-bug.rb +0 -55
- data/test/TestSuite/Export-Reports/refs/DST.tjp +0 -60
- data/test/TestSuite/Export-Reports/refs/ReleasePlan.tjp +0 -80
- data/test/TestSuite/HTML-Reports/TimeSheet.html +0 -79
- data/test/TestSuite/HTML-Reports/reference.html +0 -51
- data/test/TestSuite/ReportGenerator/Correct/Alerts-1.csv +0 -412
- data/test/TestSuite/ReportGenerator/Correct/Alerts-1.html +0 -680
- data/test/TestSuite/ReportGenerator/Correct/DependencyList.csv +0 -4
- data/test/TestSuite/ReportGenerator/Correct/DependencyList.tjp +0 -25
- data/test/TestSuite/ReportGenerator/Correct/Journal-1.csv +0 -8
- data/test/TestSuite/ReportGenerator/Correct/Journal-2.csv +0 -8
- data/test/TestSuite/ReportGenerator/Correct/Journal-3.html +0 -28
- data/test/TestSuite/ReportGenerator/Correct/LogicalFunctions2.csv +0 -3
- data/test/TestSuite/ReportGenerator/Correct/opennodes-1.csv +0 -2
- data/test/TestSuite/ReportGenerator/Correct/opennodes.csv +0 -2
- data/test/TestSuite/ReportGenerator/Correct/opennodes.tjp +0 -26
- data/test/TestSuite/ReportGenerator/Correct/refs/opennodes-1.csv +0 -2
- data/test/TestSuite/Scheduler/' +0 -23
- data/test/TestSuite/Scheduler/Correct/css/tjmanual.css +0 -86
- data/test/TestSuite/Scheduler/Correct/css/tjreport.css +0 -413
- data/test/TestSuite/Scheduler/Correct/icons/details.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/flag-green.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/flag-red.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/flag-yellow.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/resource.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/resourcegroup.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/task.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/taskgroup.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/trend-down.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/trend-flat.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/trend-up.png +0 -0
- data/test/TestSuite/StatusSheetTemplates/project.tji +0 -35
- data/test/TestSuite/StatusSheetTemplates/project.tjp +0 -56
- data/test/TestSuite/StatusSheets/TimeSheets/2002-03-01/resource1_2002-03-01.tji +0 -0
- data/test/TestSuite/Syntax/Correct/DST.tjp +0 -11
- data/test/TestSuite/Syntax/Correct/ReleasePlan.tjp +0 -32
- data/test/TestSuite/TimeSheets/receiver.log.mod +0 -1056
- data/test/TestSuite/TimeSheets/uu.txt +0 -29
|
@@ -23,6 +23,15 @@ class TaskJuggler
|
|
|
23
23
|
# Create a new TaskScenario object.
|
|
24
24
|
def initialize(task, scenarioIdx, attributes)
|
|
25
25
|
super
|
|
26
|
+
# Attributed are only really created when they are accessed the first
|
|
27
|
+
# time. So make sure some needed attributes really exist so we don't
|
|
28
|
+
# have to check for existance each time we access them.
|
|
29
|
+
%w( allocate assignedresources booking charge chargeset complete
|
|
30
|
+
criticalness depends duration effort end forward length
|
|
31
|
+
maxend maxstart minend minstart milestone pathcriticalness
|
|
32
|
+
precedes scheduled shifts start status ).each do |attr|
|
|
33
|
+
@property[attr, @scenarioIdx]
|
|
34
|
+
end
|
|
26
35
|
|
|
27
36
|
# A list of all allocated leaf resources.
|
|
28
37
|
@candidates = []
|
|
@@ -33,59 +42,79 @@ class TaskJuggler
|
|
|
33
42
|
# scheduling.
|
|
34
43
|
def prepareScheduling
|
|
35
44
|
@property['startpreds', @scenarioIdx] = []
|
|
36
|
-
@property['startsuccs', @scenarioIdx] =[]
|
|
45
|
+
@property['startsuccs', @scenarioIdx] = []
|
|
37
46
|
@property['endpreds', @scenarioIdx] = []
|
|
38
47
|
@property['endsuccs', @scenarioIdx] = []
|
|
39
48
|
|
|
40
49
|
@isRunAway = false
|
|
41
50
|
|
|
42
|
-
#
|
|
43
|
-
@
|
|
51
|
+
# And as global scoreboard index
|
|
52
|
+
@currentSlotIdx = nil
|
|
44
53
|
# The 'done' variables count scheduled values in number of time slots.
|
|
45
54
|
@doneDuration = 0
|
|
46
55
|
@doneLength = 0
|
|
47
56
|
# Due to the 'efficiency' factor the effort slots must be a float.
|
|
48
57
|
@doneEffort = 0.0
|
|
49
58
|
|
|
59
|
+
@projectionMode = @project.scenario(@scenarioIdx).get('projection')
|
|
60
|
+
|
|
50
61
|
@startIsDetermed = nil
|
|
51
62
|
@endIsDetermed = nil
|
|
52
|
-
@tentativeStart = @tentativeEnd = nil
|
|
53
63
|
|
|
54
64
|
# To avoid multiple calls to propagateDate() we use these flags to know
|
|
55
65
|
# when we've done it already.
|
|
56
66
|
@startPropagated = false
|
|
57
67
|
@endPropagated = false
|
|
58
68
|
|
|
69
|
+
markMilestone
|
|
59
70
|
# Milestones may only have start or end date even when the 'scheduled'
|
|
60
71
|
# attribute is set. For further processing, we need to add the missing
|
|
61
72
|
# date.
|
|
62
|
-
if
|
|
63
|
-
@
|
|
64
|
-
@
|
|
65
|
-
|
|
73
|
+
if @milestone
|
|
74
|
+
@end = @start if @start && !@end
|
|
75
|
+
@start = @end if !@start && @end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# For start-end-tasks without allocation, we don't have to do
|
|
79
|
+
# anything but to set the 'scheduled' flag.
|
|
80
|
+
if @start && @end && @effort == 0 && @length == 0 && @duration == 0 &&
|
|
81
|
+
@allocate.empty?
|
|
82
|
+
@scheduled = true
|
|
83
|
+
Log << "Task #{@property.fullId}: #{@start} -> #{@end}"
|
|
66
84
|
end
|
|
67
85
|
|
|
68
86
|
# Collect the limits of this task and all parent tasks into a single
|
|
69
87
|
# Array.
|
|
70
|
-
@
|
|
88
|
+
@allLimits = []
|
|
71
89
|
task = @property
|
|
72
90
|
# Reset the counters of all limits of this task.
|
|
73
91
|
task['limits', @scenarioIdx].reset if task['limits', @scenarioIdx]
|
|
74
92
|
until task.nil?
|
|
75
93
|
if task['limits', @scenarioIdx]
|
|
76
|
-
@
|
|
94
|
+
@allLimits << task['limits', @scenarioIdx]
|
|
77
95
|
end
|
|
78
96
|
task = task.parent
|
|
79
97
|
end
|
|
80
98
|
|
|
81
99
|
# Collect the mandatory allocations.
|
|
82
100
|
@mandatories = []
|
|
83
|
-
|
|
101
|
+
@allocate.each do |allocation|
|
|
84
102
|
@mandatories << allocation if allocation.mandatory
|
|
103
|
+
allocation.lockedResource = nil
|
|
85
104
|
end
|
|
86
105
|
|
|
87
106
|
bookBookings
|
|
88
|
-
|
|
107
|
+
|
|
108
|
+
@durationType =
|
|
109
|
+
if @effort > 0
|
|
110
|
+
:effortTask
|
|
111
|
+
elsif @length > 0
|
|
112
|
+
:lengthTask
|
|
113
|
+
elsif @duration > 0
|
|
114
|
+
:durationTask
|
|
115
|
+
else
|
|
116
|
+
:startEndTask
|
|
117
|
+
end
|
|
89
118
|
end
|
|
90
119
|
|
|
91
120
|
# The parser only stores the full task IDs for each of the dependencies.
|
|
@@ -100,16 +129,16 @@ class TaskJuggler
|
|
|
100
129
|
# points to the dependent task and the boolean specifies whether the
|
|
101
130
|
# dependency originates from the end of the task or not.
|
|
102
131
|
def Xref
|
|
103
|
-
@
|
|
132
|
+
@depends.each do |dependency|
|
|
104
133
|
depTask = checkDependency(dependency, 'depends')
|
|
105
|
-
|
|
134
|
+
@startpreds.push([ depTask, dependency.onEnd ])
|
|
106
135
|
depTask[dependency.onEnd ? 'endsuccs' : 'startsuccs', @scenarioIdx].
|
|
107
136
|
push([ @property, false ])
|
|
108
137
|
end
|
|
109
138
|
|
|
110
|
-
@
|
|
139
|
+
@precedes.each do |dependency|
|
|
111
140
|
predTask = checkDependency(dependency, 'precedes')
|
|
112
|
-
|
|
141
|
+
@endsuccs.push([ predTask, dependency.onEnd ])
|
|
113
142
|
predTask[dependency.onEnd ? 'endpreds' : 'startpreds', @scenarioIdx].
|
|
114
143
|
push([@property, true ])
|
|
115
144
|
end
|
|
@@ -123,8 +152,8 @@ class TaskJuggler
|
|
|
123
152
|
|
|
124
153
|
def propagateInitialValues
|
|
125
154
|
unless @startPropagated
|
|
126
|
-
if
|
|
127
|
-
propagateDate(
|
|
155
|
+
if @start
|
|
156
|
+
propagateDate(@start, false)
|
|
128
157
|
elsif @property.parent.nil? &&
|
|
129
158
|
@property.canInheritDate?(@scenarioIdx, false)
|
|
130
159
|
propagateDate(@project['start'], false)
|
|
@@ -132,8 +161,8 @@ class TaskJuggler
|
|
|
132
161
|
end
|
|
133
162
|
|
|
134
163
|
unless @endPropagated
|
|
135
|
-
if
|
|
136
|
-
propagateDate(
|
|
164
|
+
if @end
|
|
165
|
+
propagateDate(@end, true)
|
|
137
166
|
elsif @property.parent.nil? &&
|
|
138
167
|
@property.canInheritDate?(@scenarioIdx, true)
|
|
139
168
|
propagateDate(@project['end'], true)
|
|
@@ -146,7 +175,7 @@ class TaskJuggler
|
|
|
146
175
|
def preScheduleCheck
|
|
147
176
|
# Accounts can have sub accounts added after being used in a chargetset.
|
|
148
177
|
# So we need to re-test here.
|
|
149
|
-
|
|
178
|
+
@chargeset.each do |chargeset|
|
|
150
179
|
chargeset.each do |account, share|
|
|
151
180
|
unless account.leaf?
|
|
152
181
|
error('account_no_leaf',
|
|
@@ -157,36 +186,36 @@ class TaskJuggler
|
|
|
157
186
|
|
|
158
187
|
# Leaf tasks can be turned into containers after bookings have been added.
|
|
159
188
|
# We need to check for this.
|
|
160
|
-
unless @property.leaf? ||
|
|
189
|
+
unless @property.leaf? || @booking.empty?
|
|
161
190
|
error('container_booking',
|
|
162
191
|
"Container task #{@property.fullId} may not have bookings.")
|
|
163
192
|
end
|
|
164
193
|
|
|
165
194
|
# Milestones may not have bookings.
|
|
166
|
-
if
|
|
195
|
+
if @milestone && !@booking.empty?
|
|
167
196
|
error('milestone_booking',
|
|
168
197
|
"Milestone #{@property.fullId} may not have bookings.")
|
|
169
198
|
end
|
|
170
199
|
|
|
171
200
|
# All 'scheduled' tasks must have a fixed start and end date.
|
|
172
|
-
if
|
|
201
|
+
if @scheduled && (@start.nil? || @end.nil?)
|
|
173
202
|
error('not_scheduled',
|
|
174
203
|
"Task #{@property.fullId} is marked as scheduled but does not " +
|
|
175
204
|
'have a fixed start and end date.')
|
|
176
205
|
end
|
|
177
206
|
|
|
178
207
|
# If an effort has been specified resources must be allocated as well.
|
|
179
|
-
if
|
|
208
|
+
if @effort > 0 && @allocate.empty?
|
|
180
209
|
error('effort_no_allocations',
|
|
181
210
|
"Task #{@property.fullId} has an effort but no resource " +
|
|
182
211
|
"allocations.")
|
|
183
212
|
end
|
|
184
213
|
|
|
185
214
|
durationSpecs = 0
|
|
186
|
-
durationSpecs += 1 if
|
|
187
|
-
durationSpecs += 1 if
|
|
188
|
-
durationSpecs += 1 if
|
|
189
|
-
durationSpecs += 1 if
|
|
215
|
+
durationSpecs += 1 if @effort > 0
|
|
216
|
+
durationSpecs += 1 if @length > 0
|
|
217
|
+
durationSpecs += 1 if @duration > 0
|
|
218
|
+
durationSpecs += 1 if @milestone
|
|
190
219
|
|
|
191
220
|
# The rest of this function performs a number of plausibility tests with
|
|
192
221
|
# regards to task start and end critiria. To explain the various cases,
|
|
@@ -207,7 +236,7 @@ class TaskJuggler
|
|
|
207
236
|
"Container task #{@property.fullId} may not have a duration " +
|
|
208
237
|
"or be marked as milestones.")
|
|
209
238
|
end
|
|
210
|
-
elsif
|
|
239
|
+
elsif @milestone
|
|
211
240
|
if durationSpecs > 1
|
|
212
241
|
error('milestone_duration',
|
|
213
242
|
"Milestone task #{@property.fullId} may not have a duration.")
|
|
@@ -223,9 +252,9 @@ class TaskJuggler
|
|
|
223
252
|
# already handled by 'start_undetermed' or 'end_undetermed'
|
|
224
253
|
|
|
225
254
|
# err2: differnt start and end dates
|
|
226
|
-
if
|
|
255
|
+
if @start && @end && @start != @end
|
|
227
256
|
error('milestone_start_end',
|
|
228
|
-
"Start (#{
|
|
257
|
+
"Start (#{@start}) and end (#{@end}) dates of " +
|
|
229
258
|
"milestone task #{@property.fullId} must be identical.")
|
|
230
259
|
end
|
|
231
260
|
else
|
|
@@ -268,8 +297,8 @@ class TaskJuggler
|
|
|
268
297
|
# - <-- -D
|
|
269
298
|
# - <-- |D
|
|
270
299
|
if durationSpecs == 0 &&
|
|
271
|
-
((
|
|
272
|
-
(
|
|
300
|
+
((@forward && @end.nil? && !hasDependencies(true)) ||
|
|
301
|
+
(!@forward && @start.nil? && !hasDependencies(false)))
|
|
273
302
|
error('task_underspecified',
|
|
274
303
|
"Task #{@property.fullId} has too few specifations to be " +
|
|
275
304
|
"scheduled.")
|
|
@@ -296,8 +325,8 @@ class TaskJuggler
|
|
|
296
325
|
startSpeced = @property.provided('start', @scenarioIdx)
|
|
297
326
|
endSpeced = @property.provided('end', @scenarioIdx)
|
|
298
327
|
if ((startSpeced && endSpeced) ||
|
|
299
|
-
(hasDependencies(false) &&
|
|
300
|
-
(hasDependencies(true) &&
|
|
328
|
+
(hasDependencies(false) && @forward && endSpeced) ||
|
|
329
|
+
(hasDependencies(true) && !@forward && startSpeced)) &&
|
|
301
330
|
durationSpecs > 0 && !@property.provided('scheduled', @scenarioIdx)
|
|
302
331
|
error('task_overspecified',
|
|
303
332
|
"Task #{@property.fullId} has a start, an end and a " +
|
|
@@ -305,7 +334,7 @@ class TaskJuggler
|
|
|
305
334
|
end
|
|
306
335
|
end
|
|
307
336
|
|
|
308
|
-
if
|
|
337
|
+
if !@booking.empty? && !@forward && !@scheduled
|
|
309
338
|
error('alap_booking',
|
|
310
339
|
'A task scheduled in ALAP mode may only have bookings if it ' +
|
|
311
340
|
'has been marked as fully scheduled. Keep in mind that ' +
|
|
@@ -313,14 +342,14 @@ class TaskJuggler
|
|
|
313
342
|
'switch the task to ALAP mode.')
|
|
314
343
|
end
|
|
315
344
|
|
|
316
|
-
|
|
345
|
+
@startsuccs.each do |task, onEnd|
|
|
317
346
|
unless task['forward', @scenarioIdx]
|
|
318
347
|
task.data[@scenarioIdx].error(
|
|
319
348
|
'onstart_wrong_direction',
|
|
320
349
|
'Tasks with on-start dependencies must be ASAP scheduled')
|
|
321
350
|
end
|
|
322
351
|
end
|
|
323
|
-
|
|
352
|
+
@endpreds.each do |task, onEnd|
|
|
324
353
|
if task['forward', @scenarioIdx]
|
|
325
354
|
task.data[@scenarioIdx].error(
|
|
326
355
|
'onend_wrong_direction',
|
|
@@ -340,7 +369,7 @@ class TaskJuggler
|
|
|
340
369
|
|
|
341
370
|
if (parent = @property.parent)
|
|
342
371
|
# Add the assigned resources to the parent task list.
|
|
343
|
-
|
|
372
|
+
@assignedresources.each do |resource|
|
|
344
373
|
unless parent['assignedresources', @scenarioIdx].include?(resource)
|
|
345
374
|
parent['assignedresources', @scenarioIdx] << resource
|
|
346
375
|
end
|
|
@@ -371,65 +400,65 @@ class TaskJuggler
|
|
|
371
400
|
end
|
|
372
401
|
|
|
373
402
|
# Make sure the task is marked complete
|
|
374
|
-
unless
|
|
403
|
+
unless @scheduled
|
|
375
404
|
error('not_scheduled',
|
|
376
405
|
"Task #{@property.fullId} has not been marked as scheduled.")
|
|
377
406
|
end
|
|
378
407
|
|
|
379
408
|
# If the task has a follower or predecessor that is a runaway this task
|
|
380
409
|
# is also incomplete.
|
|
381
|
-
(
|
|
410
|
+
(@startsuccs + @endsuccs).each do |task, onEnd|
|
|
382
411
|
return false if task.isRunAway(@scenarioIdx)
|
|
383
412
|
end
|
|
384
|
-
(
|
|
413
|
+
(@startpreds + @endpreds).each do |task, onEnd|
|
|
385
414
|
return false if task.isRunAway(@scenarioIdx)
|
|
386
415
|
end
|
|
387
416
|
|
|
388
417
|
# Check if the start time is ok
|
|
389
|
-
if
|
|
418
|
+
if @start.nil?
|
|
390
419
|
error('task_start_undef',
|
|
391
420
|
"Task #{@property.fullId} has undefined start time")
|
|
392
421
|
end
|
|
393
|
-
if
|
|
422
|
+
if @start < @project['start'] || @start > @project['end']
|
|
394
423
|
error('task_start_range',
|
|
395
|
-
"The start time (#{
|
|
424
|
+
"The start time (#{@start}) of task #{@property.fullId} " +
|
|
396
425
|
"is outside the project interval (#{@project['start']} - " +
|
|
397
426
|
"#{@project['end']})")
|
|
398
427
|
end
|
|
399
|
-
if
|
|
428
|
+
if !@minstart.nil? && @start < @minstart
|
|
400
429
|
warning('minstart',
|
|
401
|
-
"The start time (#{
|
|
402
|
-
"is too early. Must be after #{
|
|
430
|
+
"The start time (#{@start}) of task #{@property.fullId} " +
|
|
431
|
+
"is too early. Must be after #{@minstart}.")
|
|
403
432
|
end
|
|
404
|
-
if
|
|
433
|
+
if !@maxstart.nil? && @start > @maxstart
|
|
405
434
|
warning('maxstart',
|
|
406
|
-
"The start time (#{
|
|
407
|
-
"is too late. Must be before #{
|
|
435
|
+
"The start time (#{@start}) of task #{@property.fullId} " +
|
|
436
|
+
"is too late. Must be before #{@maxstart}.")
|
|
408
437
|
end
|
|
409
438
|
# Check if the end time is ok
|
|
410
439
|
error('task_end_undef',
|
|
411
|
-
"Task #{@property.fullId} has undefined end time") if
|
|
412
|
-
if
|
|
440
|
+
"Task #{@property.fullId} has undefined end time") if @end.nil?
|
|
441
|
+
if @end < @project['start'] || @end > @project['end']
|
|
413
442
|
error('task_end_range',
|
|
414
|
-
"The end time (#{
|
|
443
|
+
"The end time (#{@end}) of task #{@property.fullId} " +
|
|
415
444
|
"is outside the project interval (#{@project['start']} - " +
|
|
416
445
|
"#{@project['end']})")
|
|
417
446
|
end
|
|
418
|
-
if
|
|
447
|
+
if !@minend.nil? && @end < @minend
|
|
419
448
|
warning('minend',
|
|
420
|
-
"The end time (#{
|
|
421
|
-
"is too early. Must be after #{
|
|
449
|
+
"The end time (#{@end}) of task #{@property.fullId} " +
|
|
450
|
+
"is too early. Must be after #{@minend}.")
|
|
422
451
|
end
|
|
423
|
-
if
|
|
452
|
+
if !@maxend.nil? && @end > @maxend
|
|
424
453
|
warning('maxend',
|
|
425
|
-
"The end time (#{
|
|
426
|
-
"is too late. Must be before #{
|
|
454
|
+
"The end time (#{@end}) of task #{@property.fullId} " +
|
|
455
|
+
"is too late. Must be before #{@maxend}.")
|
|
427
456
|
end
|
|
428
457
|
# Make sure the start is before the end
|
|
429
|
-
if
|
|
458
|
+
if @start > @end
|
|
430
459
|
error('start_after_end',
|
|
431
|
-
"The start time (#{
|
|
432
|
-
"is after the end time (#{
|
|
460
|
+
"The start time (#{@start}) of task #{@property.fullId} " +
|
|
461
|
+
"is after the end time (#{@end}).")
|
|
433
462
|
end
|
|
434
463
|
|
|
435
464
|
|
|
@@ -437,33 +466,33 @@ class TaskJuggler
|
|
|
437
466
|
unless (parent = @property.parent).nil? ||
|
|
438
467
|
parent['start', @scenarioIdx].nil? ||
|
|
439
468
|
parent['end', @scenarioIdx].nil?
|
|
440
|
-
if
|
|
469
|
+
if @start < parent['start', @scenarioIdx]
|
|
441
470
|
error('task_start_in_parent',
|
|
442
|
-
"The start date (#{
|
|
443
|
-
"is before the start date
|
|
444
|
-
"
|
|
471
|
+
"The start date (#{@start}) of task #{@property.fullId} " +
|
|
472
|
+
"is before the start date (#{parent['start', @scenarioIdx]}) " +
|
|
473
|
+
"of the enclosing task.")
|
|
445
474
|
end
|
|
446
|
-
if
|
|
475
|
+
if @end > parent['end', @scenarioIdx]
|
|
447
476
|
error('task_end_in_parent',
|
|
448
|
-
"The end date (#{
|
|
449
|
-
"is after the end date
|
|
450
|
-
"
|
|
477
|
+
"The end date (#{@end}) of task #{@property.fullId} " +
|
|
478
|
+
"is after the end date (#{parent['end', @scenarioIdx]}) " +
|
|
479
|
+
"of the enclosing task.")
|
|
451
480
|
end
|
|
452
481
|
end
|
|
453
482
|
|
|
454
483
|
# Check that all preceding tasks start/end before this task.
|
|
455
|
-
@
|
|
484
|
+
@depends.each do |dependency|
|
|
456
485
|
task = dependency.task
|
|
457
486
|
limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
|
|
458
487
|
next if limit.nil?
|
|
459
|
-
if
|
|
488
|
+
if @start < limit
|
|
460
489
|
error('task_pred_before',
|
|
461
|
-
"Task #{@property.fullId} must start after " +
|
|
462
|
-
"#{dependency.onEnd ? 'end' : 'start'} of task " +
|
|
490
|
+
"Task #{@property.fullId} (#{@start}) must start after " +
|
|
491
|
+
"#{dependency.onEnd ? 'end' : 'start'} (#{limit}) of task " +
|
|
463
492
|
"#{task.fullId}.")
|
|
464
493
|
end
|
|
465
494
|
if dependency.gapDuration > 0
|
|
466
|
-
if limit + dependency.gapDuration >
|
|
495
|
+
if limit + dependency.gapDuration > @start
|
|
467
496
|
error('task_pred_before_gd',
|
|
468
497
|
"Task #{@property.fullId} must start " +
|
|
469
498
|
"#{dependency.gapDuration / (60 * 60 * 24)} days after " +
|
|
@@ -474,7 +503,7 @@ class TaskJuggler
|
|
|
474
503
|
end
|
|
475
504
|
end
|
|
476
505
|
if dependency.gapLength > 0
|
|
477
|
-
if calcLength(limit,
|
|
506
|
+
if calcLength(limit, @start) < dependency.gapLength
|
|
478
507
|
error('task_pred_before_gl',
|
|
479
508
|
"Task #{@property.fullId} must start " +
|
|
480
509
|
"#{@project.slotsToDays(dependency.gapLength)} " +
|
|
@@ -488,17 +517,18 @@ class TaskJuggler
|
|
|
488
517
|
end
|
|
489
518
|
|
|
490
519
|
# Check that all following tasks end before this task
|
|
491
|
-
@
|
|
520
|
+
@precedes.each do |dependency|
|
|
492
521
|
task = dependency.task
|
|
493
522
|
limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
|
|
494
523
|
next if limit.nil?
|
|
495
|
-
if limit <
|
|
524
|
+
if limit < @end
|
|
496
525
|
error('task_succ_after',
|
|
497
|
-
"Task #{@property.fullId} must end before " +
|
|
498
|
-
"#{dependency.onEnd ? 'end' : 'start'} of task
|
|
526
|
+
"Task #{@property.fullId} (#{@end}) must end before " +
|
|
527
|
+
"#{dependency.onEnd ? 'end' : 'start'} (#{limit}) of task " +
|
|
528
|
+
"#{task.fullId}.")
|
|
499
529
|
end
|
|
500
530
|
if dependency.gapDuration > 0
|
|
501
|
-
if limit - dependency.gapDuration <
|
|
531
|
+
if limit - dependency.gapDuration < @end
|
|
502
532
|
error('task_succ_after_gd',
|
|
503
533
|
"Task #{@property.fullId} must end " +
|
|
504
534
|
"#{dependency.gapDuration / (60 * 60 * 24)} days before " +
|
|
@@ -509,7 +539,7 @@ class TaskJuggler
|
|
|
509
539
|
end
|
|
510
540
|
end
|
|
511
541
|
if dependency.gapLength > 0
|
|
512
|
-
if calcLength(
|
|
542
|
+
if calcLength(@end, limit) < dependency.gapLength
|
|
513
543
|
error('task_succ_after_gl',
|
|
514
544
|
"Task #{@property.fullId} must end " +
|
|
515
545
|
"#{@project.slotsToDays(dependency.gapLength)} " +
|
|
@@ -522,12 +552,24 @@ class TaskJuggler
|
|
|
522
552
|
end
|
|
523
553
|
end
|
|
524
554
|
|
|
525
|
-
if
|
|
555
|
+
if @milestone && @start != @end
|
|
526
556
|
error('milestone_times_equal',
|
|
527
557
|
"Milestone #{@property.fullId} must have identical start and " +
|
|
528
558
|
"end date.")
|
|
529
559
|
end
|
|
530
560
|
|
|
561
|
+
if @effort == 0 && !@milestone && !@allocate.empty? &&
|
|
562
|
+
@assignedresources.empty?
|
|
563
|
+
# The user used an 'allocate' for the task, but did not specify any
|
|
564
|
+
# 'effort'. Actual allocations will only happen when resources are
|
|
565
|
+
# available by chance. If there are no assigned resources, we generate
|
|
566
|
+
# a warning as this is probably not what the user intended.
|
|
567
|
+
warning('allocate_no_assigned',
|
|
568
|
+
"Task #{@property.id} has resource allocation requested, but " +
|
|
569
|
+
"did not get any resources assigned. Either use 'effort' " +
|
|
570
|
+
"to ensure allocations or use a higher 'priority'.")
|
|
571
|
+
end
|
|
572
|
+
|
|
531
573
|
@errors == 0
|
|
532
574
|
end
|
|
533
575
|
|
|
@@ -618,12 +660,12 @@ class TaskJuggler
|
|
|
618
660
|
# -->| o---->
|
|
619
661
|
# +--------
|
|
620
662
|
#
|
|
621
|
-
if (forward &&
|
|
663
|
+
if (forward && @forward) || @milestone
|
|
622
664
|
checkForLoops(path, true, false, true)
|
|
623
665
|
end
|
|
624
666
|
end
|
|
625
667
|
else
|
|
626
|
-
if
|
|
668
|
+
if @startpreds.empty?
|
|
627
669
|
#
|
|
628
670
|
# ^
|
|
629
671
|
# |
|
|
@@ -645,7 +687,7 @@ class TaskJuggler
|
|
|
645
687
|
# ^
|
|
646
688
|
# |
|
|
647
689
|
#
|
|
648
|
-
|
|
690
|
+
@startpreds.each do |task, targetEnd|
|
|
649
691
|
task.checkForLoops(@scenarioIdx, path, targetEnd, true, forward)
|
|
650
692
|
end
|
|
651
693
|
end
|
|
@@ -672,12 +714,12 @@ class TaskJuggler
|
|
|
672
714
|
# <----o |<--
|
|
673
715
|
# --------+
|
|
674
716
|
#
|
|
675
|
-
if (!forward &&
|
|
717
|
+
if (!forward && !@forward) || @milestone
|
|
676
718
|
checkForLoops(path, false, false, false)
|
|
677
719
|
end
|
|
678
720
|
end
|
|
679
721
|
else
|
|
680
|
-
if
|
|
722
|
+
if @endsuccs.empty?
|
|
681
723
|
#
|
|
682
724
|
# ^
|
|
683
725
|
# |
|
|
@@ -698,7 +740,7 @@ class TaskJuggler
|
|
|
698
740
|
# ^
|
|
699
741
|
# |
|
|
700
742
|
#
|
|
701
|
-
|
|
743
|
+
@endsuccs.each do |task, targetEnd|
|
|
702
744
|
task.checkForLoops(@scenarioIdx, path, targetEnd, true, forward)
|
|
703
745
|
end
|
|
704
746
|
end
|
|
@@ -715,7 +757,7 @@ class TaskJuggler
|
|
|
715
757
|
# list of leaf resources that are allocated to this task.
|
|
716
758
|
def candidates
|
|
717
759
|
@candidates = []
|
|
718
|
-
|
|
760
|
+
@allocate.each do |allocation|
|
|
719
761
|
allocation.candidates.each do |candidate|
|
|
720
762
|
candidate.allLeaves.each do |resource|
|
|
721
763
|
@candidates << resource unless @candidates.include?(resource)
|
|
@@ -730,9 +772,9 @@ class TaskJuggler
|
|
|
730
772
|
# stores it in @candidates. It also adds the allocated effort to
|
|
731
773
|
# the 'alloctdeffort' counter of each resource.
|
|
732
774
|
def countResourceAllocations
|
|
733
|
-
return if @candidates.empty? ||
|
|
775
|
+
return if @candidates.empty? || @effort <= 0
|
|
734
776
|
|
|
735
|
-
avgEffort =
|
|
777
|
+
avgEffort = @effort / @candidates.length
|
|
736
778
|
@candidates.each do |resource|
|
|
737
779
|
resource['alloctdeffort', @scenarioIdx] += avgEffort
|
|
738
780
|
end
|
|
@@ -744,20 +786,20 @@ class TaskJuggler
|
|
|
744
786
|
# exception are milestones which get an arbitrary value between 0 and 2
|
|
745
787
|
# based on their priority.
|
|
746
788
|
def calcCriticalness
|
|
747
|
-
@
|
|
748
|
-
@
|
|
789
|
+
@criticalness = 0.0
|
|
790
|
+
@pathcriticalness = nil
|
|
749
791
|
|
|
750
792
|
# Users feel that milestones are somewhat important. So we use an
|
|
751
793
|
# arbitrary value larger than 0 for them. We make it priority dependent,
|
|
752
794
|
# so the user has some control over it. Priority 0 is 0, 500 is 1.0 and
|
|
753
795
|
# 1000 is 2.0. These values are pretty much randomly picked and probably
|
|
754
796
|
# require some more tuning based on real projects.
|
|
755
|
-
if
|
|
756
|
-
@
|
|
797
|
+
if @milestone
|
|
798
|
+
@criticalness = @priority / 500.0
|
|
757
799
|
end
|
|
758
800
|
|
|
759
801
|
# Task without efforts of allocations are not critical.
|
|
760
|
-
return if
|
|
802
|
+
return if @effort <= 0 || @candidates.empty?
|
|
761
803
|
|
|
762
804
|
# Determine the average criticalness of all allocated resources.
|
|
763
805
|
criticalness = 0.0
|
|
@@ -768,7 +810,7 @@ class TaskJuggler
|
|
|
768
810
|
|
|
769
811
|
# The task criticalness is the product of effort and average resource
|
|
770
812
|
# criticalness.
|
|
771
|
-
@
|
|
813
|
+
@criticalness = @effort * criticalness
|
|
772
814
|
end
|
|
773
815
|
|
|
774
816
|
# The path criticalness is a measure for the overall criticalness of the
|
|
@@ -781,8 +823,8 @@ class TaskJuggler
|
|
|
781
823
|
# If we have computed this already, just return the value. If we are only
|
|
782
824
|
# at the end of the task, we do not include the criticalness of this task
|
|
783
825
|
# as it is not really part of the path.
|
|
784
|
-
if
|
|
785
|
-
return
|
|
826
|
+
if @pathcriticalness
|
|
827
|
+
return @pathcriticalness - (atEnd ? 0 : @criticalness)
|
|
786
828
|
end
|
|
787
829
|
|
|
788
830
|
maxCriticalness = 0.0
|
|
@@ -808,7 +850,7 @@ class TaskJuggler
|
|
|
808
850
|
# For leaf tasks, we check all pathes through the start successors and
|
|
809
851
|
# then the pathes through the end successors of this task and all its
|
|
810
852
|
# parent tasks.
|
|
811
|
-
|
|
853
|
+
@startsuccs.each do |task, onEnd|
|
|
812
854
|
if (criticalness = task.calcPathCriticalness(@scenarioIdx, onEnd)) >
|
|
813
855
|
maxCriticalness
|
|
814
856
|
maxCriticalness = criticalness
|
|
@@ -819,23 +861,23 @@ class TaskJuggler
|
|
|
819
861
|
maxCriticalness = criticalness
|
|
820
862
|
end
|
|
821
863
|
|
|
822
|
-
maxCriticalness +=
|
|
864
|
+
maxCriticalness += @criticalness
|
|
823
865
|
end
|
|
824
866
|
end
|
|
825
867
|
|
|
826
|
-
@
|
|
868
|
+
@pathcriticalness = maxCriticalness
|
|
827
869
|
end
|
|
828
870
|
|
|
829
871
|
# Check if the task is ready to be scheduled. For this it needs to have at
|
|
830
872
|
# least one specified end date and a duration criteria or the other end
|
|
831
873
|
# date.
|
|
832
874
|
def readyForScheduling?
|
|
833
|
-
return false if
|
|
875
|
+
return false if @scheduled || @isRunAway
|
|
834
876
|
|
|
835
|
-
if
|
|
836
|
-
return true if
|
|
877
|
+
if @forward
|
|
878
|
+
return true if @start && (hasDurationSpec? || @end)
|
|
837
879
|
else
|
|
838
|
-
return true if
|
|
880
|
+
return true if @end && (hasDurationSpec? || @start)
|
|
839
881
|
end
|
|
840
882
|
|
|
841
883
|
false
|
|
@@ -846,31 +888,39 @@ class TaskJuggler
|
|
|
846
888
|
# or end date has been determined and other tasks may be ready for
|
|
847
889
|
# scheduling now.
|
|
848
890
|
def schedule
|
|
849
|
-
#
|
|
850
|
-
|
|
851
|
-
#
|
|
852
|
-
|
|
853
|
-
#
|
|
854
|
-
|
|
855
|
-
|
|
891
|
+
# Compute the date of the next slot this task wants to have scheduled.
|
|
892
|
+
# This must either be the first slot ever or it must be directly
|
|
893
|
+
# adjecent to the previous slot. If this task has not yet been scheduled
|
|
894
|
+
# at all, @currentSlotIdx is still nil. Otherwise it contains the index
|
|
895
|
+
# of the last scheduled slot.
|
|
896
|
+
if @forward
|
|
897
|
+
# On first call, the @currentSlotIdx is not set yet. We set it to the
|
|
898
|
+
# start slot index or the 'now' slot if we are in projection mode and
|
|
899
|
+
# the tasks has allocations.
|
|
900
|
+
if @currentSlotIdx.nil?
|
|
901
|
+
@currentSlotIdx = @project.dateToIdx(
|
|
902
|
+
@projectionMode && (@project['now'] > @start) && !@allocate.empty? ?
|
|
903
|
+
@project['now'] : @start)
|
|
904
|
+
end
|
|
905
|
+
else
|
|
906
|
+
# On first call, the @currentSlotIdx is not set yet. We set it to the
|
|
907
|
+
# slot index of the slot before the end slot.
|
|
908
|
+
if @currentSlotIdx.nil?
|
|
909
|
+
@currentSlotIdx = @project.dateToIdx(@end) - 1
|
|
910
|
+
end
|
|
911
|
+
end
|
|
856
912
|
|
|
857
913
|
# Schedule all time slots from slot in the scheduling direction until
|
|
858
914
|
# the task is completed or a problem has been found.
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
# The task is scheduled from end to start.
|
|
869
|
-
slot -= slotDuration
|
|
870
|
-
if slot < limit
|
|
871
|
-
markAsRunaway
|
|
872
|
-
return false
|
|
873
|
-
end
|
|
915
|
+
# The task may not excede the project interval.
|
|
916
|
+
lowerLimit = @project.dateToIdx(@project['start'])
|
|
917
|
+
upperLimit = @project.dateToIdx(@project['end'])
|
|
918
|
+
delta = @forward ? 1 : -1
|
|
919
|
+
while scheduleSlot
|
|
920
|
+
@currentSlotIdx += delta
|
|
921
|
+
if @currentSlotIdx < lowerLimit || upperLimit < @currentSlotIdx
|
|
922
|
+
markAsRunaway
|
|
923
|
+
return false
|
|
874
924
|
end
|
|
875
925
|
end
|
|
876
926
|
|
|
@@ -896,21 +946,21 @@ class TaskJuggler
|
|
|
896
946
|
# For leaf tasks, propagate start may set the date. Container task dates
|
|
897
947
|
# are only set in scheduleContainer().
|
|
898
948
|
if @property.leaf?
|
|
899
|
-
@
|
|
900
|
-
Log << "Task #{@property.fullId}: #{
|
|
949
|
+
instance_variable_set(('@' + thisEnd).intern, date)
|
|
950
|
+
Log << "Task #{@property.fullId}: #{@start} -> #{@end}"
|
|
901
951
|
end
|
|
902
952
|
|
|
903
|
-
if
|
|
953
|
+
if @milestone
|
|
904
954
|
# Start and end date of a milestone are identical.
|
|
905
|
-
@
|
|
955
|
+
@scheduled = true
|
|
906
956
|
if a(otherEnd).nil?
|
|
907
957
|
propagateDate(a(thisEnd), !atEnd)
|
|
908
958
|
end
|
|
909
|
-
Log << "Milestone #{@property.fullId}: #{
|
|
910
|
-
elsif
|
|
911
|
-
!(
|
|
912
|
-
|
|
913
|
-
@
|
|
959
|
+
Log << "Milestone #{@property.fullId}: #{@start} -> #{@end}"
|
|
960
|
+
elsif !@scheduled && @start && @end &&
|
|
961
|
+
!(@length == 0 && @duration == 0 && @effort == 0 &&
|
|
962
|
+
!@allocate.empty?)
|
|
963
|
+
@scheduled = true
|
|
914
964
|
Log << "Task #{@property.fullId} has been scheduled"
|
|
915
965
|
end
|
|
916
966
|
|
|
@@ -920,21 +970,21 @@ class TaskJuggler
|
|
|
920
970
|
# allocation. In these cases, bookResource() has to propagate the final
|
|
921
971
|
# date.
|
|
922
972
|
if atEnd
|
|
923
|
-
if ignoreEffort ||
|
|
924
|
-
|
|
973
|
+
if ignoreEffort || @effort == 0
|
|
974
|
+
@endpreds.each do |task, onEnd|
|
|
925
975
|
propagateDateToDep(task, onEnd)
|
|
926
976
|
end
|
|
927
977
|
end
|
|
928
|
-
|
|
978
|
+
@endsuccs.each do |task, onEnd|
|
|
929
979
|
propagateDateToDep(task, onEnd)
|
|
930
980
|
end
|
|
931
981
|
else
|
|
932
|
-
if ignoreEffort ||
|
|
933
|
-
|
|
982
|
+
if ignoreEffort || @effort == 0
|
|
983
|
+
@startsuccs.each do |task, onEnd|
|
|
934
984
|
propagateDateToDep(task, onEnd)
|
|
935
985
|
end
|
|
936
986
|
end
|
|
937
|
-
|
|
987
|
+
@startpreds.each do |task, onEnd|
|
|
938
988
|
propagateDateToDep(task, onEnd)
|
|
939
989
|
end
|
|
940
990
|
end
|
|
@@ -988,24 +1038,24 @@ class TaskJuggler
|
|
|
988
1038
|
|
|
989
1039
|
# Check for tasks that have no start and end spec, no duration spec but
|
|
990
1040
|
# allocates. They can inherit the start and end date.
|
|
991
|
-
return true if hasThatSpec && !hasDurationSpec &&
|
|
1041
|
+
return true if hasThatSpec && !hasDurationSpec && !@allocate.empty?
|
|
992
1042
|
|
|
993
|
-
if
|
|
1043
|
+
if @forward ^ atEnd
|
|
994
1044
|
# the scheduling direction is pointing away from this end
|
|
995
|
-
return true if hasDurationSpec ||
|
|
1045
|
+
return true if hasDurationSpec || !@booking.empty?
|
|
996
1046
|
|
|
997
1047
|
return hasThatSpec
|
|
998
1048
|
else
|
|
999
1049
|
# the scheduling direction is pointing towards this end
|
|
1000
1050
|
return a(thatEnd) && !hasDurationSpec &&
|
|
1001
|
-
|
|
1051
|
+
@booking.empty? #&& @allocate.empty?
|
|
1002
1052
|
end
|
|
1003
1053
|
end
|
|
1004
1054
|
|
|
1005
1055
|
# Find the smallest possible interval that encloses all child tasks. Abort
|
|
1006
1056
|
# the operation if any of the child tasks are not yet scheduled.
|
|
1007
1057
|
def scheduleContainer
|
|
1008
|
-
return if
|
|
1058
|
+
return if @scheduled || !@property.container?
|
|
1009
1059
|
|
|
1010
1060
|
nStart = nil
|
|
1011
1061
|
nEnd = nil
|
|
@@ -1028,19 +1078,19 @@ class TaskJuggler
|
|
|
1028
1078
|
|
|
1029
1079
|
startSet = endSet = false
|
|
1030
1080
|
# Propagate the dates to other dependent tasks.
|
|
1031
|
-
if
|
|
1032
|
-
@
|
|
1081
|
+
if @start.nil? || @start > nStart
|
|
1082
|
+
@start = nStart
|
|
1033
1083
|
startSet = true
|
|
1034
1084
|
end
|
|
1035
|
-
if
|
|
1036
|
-
@
|
|
1085
|
+
if @end.nil? || @end < nEnd
|
|
1086
|
+
@end = nEnd
|
|
1037
1087
|
endSet = true
|
|
1038
1088
|
end
|
|
1039
|
-
unless
|
|
1040
|
-
raise "Start (#{
|
|
1089
|
+
unless @start && @end
|
|
1090
|
+
raise "Start (#{@start}) and end (#{@end}) must be set"
|
|
1041
1091
|
end
|
|
1042
|
-
@
|
|
1043
|
-
Log << "Container task #{@property.fullId} completed: #{
|
|
1092
|
+
@scheduled = true
|
|
1093
|
+
Log << "Container task #{@property.fullId} completed: #{@start} -> #{@end}"
|
|
1044
1094
|
|
|
1045
1095
|
# If we have modified the start or end date, we need to communicate this
|
|
1046
1096
|
# new date to surrounding tasks.
|
|
@@ -1050,7 +1100,7 @@ class TaskJuggler
|
|
|
1050
1100
|
|
|
1051
1101
|
# Return true if the task has a effort, length or duration setting.
|
|
1052
1102
|
def hasDurationSpec?
|
|
1053
|
-
|
|
1103
|
+
@length > 0 || @duration > 0 || @effort > 0 || @milestone
|
|
1054
1104
|
end
|
|
1055
1105
|
|
|
1056
1106
|
# Find the earliest possible start date for the task. This date must be
|
|
@@ -1059,7 +1109,7 @@ class TaskJuggler
|
|
|
1059
1109
|
def earliestStart
|
|
1060
1110
|
# This is the date that we will return.
|
|
1061
1111
|
startDate = nil
|
|
1062
|
-
|
|
1112
|
+
@depends.each do |dependency|
|
|
1063
1113
|
potentialStartDate =
|
|
1064
1114
|
dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
|
|
1065
1115
|
return nil if potentialStartDate.nil?
|
|
@@ -1101,7 +1151,7 @@ class TaskJuggler
|
|
|
1101
1151
|
# task B depends on A and they are specified this way:
|
|
1102
1152
|
# task A: | --> D-
|
|
1103
1153
|
# task B: -D <-- |
|
|
1104
|
-
if
|
|
1154
|
+
if @end && startDate > @end
|
|
1105
1155
|
error('weak_start_dep',
|
|
1106
1156
|
"Task #{@property.fullId} has a too weak start dependencies " +
|
|
1107
1157
|
"to be scheduled properly.")
|
|
@@ -1116,7 +1166,7 @@ class TaskJuggler
|
|
|
1116
1166
|
def latestEnd
|
|
1117
1167
|
# This is the date that we will return.
|
|
1118
1168
|
endDate = nil
|
|
1119
|
-
|
|
1169
|
+
@precedes.each do |dependency|
|
|
1120
1170
|
potentialEndDate =
|
|
1121
1171
|
dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
|
|
1122
1172
|
return nil if potentialEndDate.nil?
|
|
@@ -1158,7 +1208,7 @@ class TaskJuggler
|
|
|
1158
1208
|
# task A precedes B and they are specified this way:
|
|
1159
1209
|
# task A: | --> D-
|
|
1160
1210
|
# task B: -D <-- |
|
|
1161
|
-
if
|
|
1211
|
+
if @start && (endDate.nil? || endDate > @start)
|
|
1162
1212
|
error('weak_end_dep',
|
|
1163
1213
|
"Task #{@property.fullId} has a too weak end dependencies " +
|
|
1164
1214
|
"to be scheduled properly.")
|
|
@@ -1170,14 +1220,14 @@ class TaskJuggler
|
|
|
1170
1220
|
def addBooking(booking)
|
|
1171
1221
|
# This append operation will not trigger a copy to sub-scenarios.
|
|
1172
1222
|
# Bookings are only valid for the scenario they are defined in.
|
|
1173
|
-
@
|
|
1223
|
+
@booking << booking
|
|
1174
1224
|
end
|
|
1175
1225
|
|
|
1176
1226
|
def query_complete(query)
|
|
1177
1227
|
# If we haven't calculated the value yet, calculate it first.
|
|
1178
|
-
unless (complete =
|
|
1228
|
+
unless (complete = @complete)
|
|
1179
1229
|
calcCompletion
|
|
1180
|
-
complete =
|
|
1230
|
+
complete = @complete
|
|
1181
1231
|
end
|
|
1182
1232
|
|
|
1183
1233
|
query.sortable = query.numerical = complete
|
|
@@ -1195,7 +1245,7 @@ class TaskJuggler
|
|
|
1195
1245
|
query.scopeProperty)
|
|
1196
1246
|
query.string = query.currencyFormat.format(cost)
|
|
1197
1247
|
else
|
|
1198
|
-
query.string = 'No
|
|
1248
|
+
query.string = 'No \'balance\' defined!'
|
|
1199
1249
|
end
|
|
1200
1250
|
end
|
|
1201
1251
|
|
|
@@ -1203,7 +1253,7 @@ class TaskJuggler
|
|
|
1203
1253
|
# all tasks. Also for those who did not have a 'duration' attribute.
|
|
1204
1254
|
def query_duration(query)
|
|
1205
1255
|
query.sortable = query.numerical = duration =
|
|
1206
|
-
(
|
|
1256
|
+
(@end - @start) / (60 * 60 * 24)
|
|
1207
1257
|
query.string = query.scaleDuration(duration)
|
|
1208
1258
|
end
|
|
1209
1259
|
|
|
@@ -1245,7 +1295,7 @@ class TaskJuggler
|
|
|
1245
1295
|
list = []
|
|
1246
1296
|
|
|
1247
1297
|
# First gather the task that depend on the start of this task.
|
|
1248
|
-
|
|
1298
|
+
@startsuccs.each do |task, onEnd|
|
|
1249
1299
|
if onEnd
|
|
1250
1300
|
date = task['end', query.scenarioIdx].to_s(query.timeFormat)
|
|
1251
1301
|
dep = "[->]"
|
|
@@ -1256,7 +1306,7 @@ class TaskJuggler
|
|
|
1256
1306
|
list << generateDepencyListItem(query, task, dep, date)
|
|
1257
1307
|
end
|
|
1258
1308
|
# Than add the tasks that depend on the end of this task.
|
|
1259
|
-
|
|
1309
|
+
@endsuccs.each do |task, onEnd|
|
|
1260
1310
|
if onEnd
|
|
1261
1311
|
date = task['end', query.scenarioIdx].to_s(query.timeFormat)
|
|
1262
1312
|
dep = "]->]"
|
|
@@ -1274,7 +1324,7 @@ class TaskJuggler
|
|
|
1274
1324
|
list = []
|
|
1275
1325
|
|
|
1276
1326
|
# First gather the task that depend on the start of this task.
|
|
1277
|
-
|
|
1327
|
+
@startpreds.each do |task, onEnd|
|
|
1278
1328
|
if onEnd
|
|
1279
1329
|
date = task['end', query.scenarioIdx].to_s(query.timeFormat)
|
|
1280
1330
|
dep = "]->["
|
|
@@ -1285,7 +1335,7 @@ class TaskJuggler
|
|
|
1285
1335
|
list << generateDepencyListItem(query, task, dep, date)
|
|
1286
1336
|
end
|
|
1287
1337
|
# Than add the tasks that depend on the end of this task.
|
|
1288
|
-
|
|
1338
|
+
@endpreds.each do |task, onEnd|
|
|
1289
1339
|
if onEnd
|
|
1290
1340
|
date = task['end', query.scenarioIdx].to_s(query.timeFormat)
|
|
1291
1341
|
dep = "]->]"
|
|
@@ -1305,9 +1355,10 @@ class TaskJuggler
|
|
|
1305
1355
|
return '' unless @property.leaf?
|
|
1306
1356
|
|
|
1307
1357
|
list = []
|
|
1308
|
-
|
|
1358
|
+
@assignedresources.each do |resource|
|
|
1309
1359
|
if resource.allocated?(@scenarioIdx,
|
|
1310
|
-
|
|
1360
|
+
TimeInterval.new(query.start, query.end),
|
|
1361
|
+
@property)
|
|
1311
1362
|
if query.listItem
|
|
1312
1363
|
rti = RichText.new(query.listItem, RTFHandlers.create(@project),
|
|
1313
1364
|
@project.messageHandler).
|
|
@@ -1334,15 +1385,15 @@ class TaskJuggler
|
|
|
1334
1385
|
query.scopeProperty)
|
|
1335
1386
|
query.string = query.currencyFormat.format(revenue)
|
|
1336
1387
|
else
|
|
1337
|
-
query.string = 'No
|
|
1388
|
+
query.string = 'No \'balance\' defined!'
|
|
1338
1389
|
end
|
|
1339
1390
|
end
|
|
1340
1391
|
|
|
1341
1392
|
def query_status(query)
|
|
1342
1393
|
# If we haven't calculated the completion yet, calculate it first.
|
|
1343
|
-
if (status =
|
|
1394
|
+
if (status = @status).empty?
|
|
1344
1395
|
calcCompletion
|
|
1345
|
-
status =
|
|
1396
|
+
status = @status
|
|
1346
1397
|
end
|
|
1347
1398
|
|
|
1348
1399
|
query.string = status
|
|
@@ -1374,8 +1425,8 @@ class TaskJuggler
|
|
|
1374
1425
|
# Compute the total time _resource_ or all resources are allocated during
|
|
1375
1426
|
# interval specified by _startIdx_ and _endIdx_.
|
|
1376
1427
|
def getAllocatedTime(startIdx, endIdx, resource = nil)
|
|
1377
|
-
return 0.0 if
|
|
1378
|
-
(resource &&
|
|
1428
|
+
return 0.0 if @milestone || startIdx >= endIdx ||
|
|
1429
|
+
(resource && !@assignedresources.include?(resource))
|
|
1379
1430
|
|
|
1380
1431
|
key = [ self, :TaskScenarioAllocatedTime, startIdx, endIdx, resource ].hash
|
|
1381
1432
|
@dCache.cached(key) do
|
|
@@ -1391,7 +1442,7 @@ class TaskJuggler
|
|
|
1391
1442
|
startIdx, endIdx,
|
|
1392
1443
|
@property)
|
|
1393
1444
|
else
|
|
1394
|
-
|
|
1445
|
+
@assignedresources.each do |r|
|
|
1395
1446
|
allocatedTime += r.getAllocatedTime(@scenarioIdx, startIdx, endIdx,
|
|
1396
1447
|
@property)
|
|
1397
1448
|
end
|
|
@@ -1405,8 +1456,8 @@ class TaskJuggler
|
|
|
1405
1456
|
# interval specified by _startIdx_ and _endIdx_. The effective work is the
|
|
1406
1457
|
# actual work multiplied by the efficiency of the resource.
|
|
1407
1458
|
def getEffectiveWork(startIdx, endIdx, resource = nil)
|
|
1408
|
-
return 0.0 if
|
|
1409
|
-
(resource &&
|
|
1459
|
+
return 0.0 if @milestone || startIdx >= endIdx ||
|
|
1460
|
+
(resource && !@assignedresources.include?(resource))
|
|
1410
1461
|
|
|
1411
1462
|
key = [ self, :TaskScenarioEffectiveWork, startIdx, endIdx, resource ].hash
|
|
1412
1463
|
@dCache.cached(key) do
|
|
@@ -1421,7 +1472,7 @@ class TaskJuggler
|
|
|
1421
1472
|
workLoad += resource.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
|
|
1422
1473
|
@property)
|
|
1423
1474
|
else
|
|
1424
|
-
|
|
1475
|
+
@assignedresources.each do |r|
|
|
1425
1476
|
workLoad += r.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
|
|
1426
1477
|
@property)
|
|
1427
1478
|
end
|
|
@@ -1441,9 +1492,9 @@ class TaskJuggler
|
|
|
1441
1492
|
@dCache.cached(key) do
|
|
1442
1493
|
workLoad = @dCache.load(key)
|
|
1443
1494
|
il = IntervalList.new
|
|
1444
|
-
il <<
|
|
1495
|
+
il << TimeInterval.new(@project['start'], @project['end'])
|
|
1445
1496
|
if @property.leaf?
|
|
1446
|
-
unless (resources =
|
|
1497
|
+
unless (resources = @assignedresources).empty?
|
|
1447
1498
|
# The task has assigned resources, so we can use their common time
|
|
1448
1499
|
# off intervals.
|
|
1449
1500
|
resources.each do |resource|
|
|
@@ -1480,7 +1531,7 @@ class TaskJuggler
|
|
|
1480
1531
|
end
|
|
1481
1532
|
|
|
1482
1533
|
|
|
1483
|
-
|
|
1534
|
+
@startsuccs.each do |dep|
|
|
1484
1535
|
unless dep[1]
|
|
1485
1536
|
# must be a start->start dependency
|
|
1486
1537
|
return true if dep[0].isDependencyOf(@scenarioIdx, task, depth)
|
|
@@ -1489,7 +1540,7 @@ class TaskJuggler
|
|
|
1489
1540
|
|
|
1490
1541
|
return false if depth == 1
|
|
1491
1542
|
|
|
1492
|
-
|
|
1543
|
+
@endsuccs.each do |dep|
|
|
1493
1544
|
unless dep[1]
|
|
1494
1545
|
# must be an end->start dependency
|
|
1495
1546
|
return true if dep[0].isDependencyOf(@scenarioIdx, task, depth - 1)
|
|
@@ -1517,7 +1568,7 @@ class TaskJuggler
|
|
|
1517
1568
|
# Returns true of the _resource_ is assigned to this task or any of its
|
|
1518
1569
|
# children.
|
|
1519
1570
|
def hasResourceAllocated?(interval, resource)
|
|
1520
|
-
return false unless
|
|
1571
|
+
return false unless @assignedresources.include?(resource)
|
|
1521
1572
|
|
|
1522
1573
|
if @property.leaf?
|
|
1523
1574
|
return resource.allocated?(@scenarioIdx, interval, @property)
|
|
@@ -1531,147 +1582,99 @@ class TaskJuggler
|
|
|
1531
1582
|
end
|
|
1532
1583
|
|
|
1533
1584
|
private
|
|
1534
|
-
def scheduleSlot(slot, slotDuration)
|
|
1535
|
-
# Tasks must always be scheduled in a single contigous fashion. @lastSlot
|
|
1536
|
-
# indicates the slot that was used for the previous call. Depending on the
|
|
1537
|
-
# scheduling direction the next slot must be scheduled either right before
|
|
1538
|
-
# or after this slot. If the current slot is not directly aligned, we'll
|
|
1539
|
-
# wait for another call with a proper slot. The function returns true
|
|
1540
|
-
# only if a slot could be scheduled.
|
|
1541
|
-
if a('forward')
|
|
1542
|
-
# On first call, the @lastSlot is not set yet. We set it to the slot
|
|
1543
|
-
# before the start slot.
|
|
1544
|
-
if @lastSlot.nil?
|
|
1545
|
-
@lastSlot = a('start') - slotDuration
|
|
1546
|
-
@tentativeEnd = slot + slotDuration
|
|
1547
|
-
end
|
|
1548
|
-
|
|
1549
|
-
return false unless slot == @lastSlot + slotDuration
|
|
1550
|
-
else
|
|
1551
|
-
# On first call, the @lastSlot is not set yet. We set it to the slot
|
|
1552
|
-
# to the end slot.
|
|
1553
|
-
if @lastSlot.nil?
|
|
1554
|
-
@lastSlot = a('end')
|
|
1555
|
-
@tentativeStart = slot
|
|
1556
|
-
end
|
|
1557
|
-
|
|
1558
|
-
return false unless slot == @lastSlot - slotDuration
|
|
1559
|
-
end
|
|
1560
|
-
@lastSlot = slot
|
|
1561
1585
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1586
|
+
def scheduleSlot
|
|
1587
|
+
# Tasks must always be scheduled in a single contigous fashion.
|
|
1588
|
+
# Depending on the scheduling direction the next slot must be scheduled
|
|
1589
|
+
# either right before or after this slot. If the current slot is not
|
|
1590
|
+
# directly aligned, we'll wait for another call with a proper slot. The
|
|
1591
|
+
# function returns false if the task has been completely scheduled.
|
|
1592
|
+
case @durationType
|
|
1593
|
+
when :effortTask
|
|
1594
|
+
bookResources if @doneEffort < @effort
|
|
1595
|
+
if @doneEffort >= @effort
|
|
1596
|
+
# The specified effort has been reached. The task has been fully
|
|
1597
|
+
# scheduled now.
|
|
1598
|
+
if @forward
|
|
1599
|
+
propagateDate(@project.idxToDate(@currentSlotIdx + 1), true, true)
|
|
1600
|
+
else
|
|
1601
|
+
propagateDate(@project.idxToDate(@currentSlotIdx), false, true)
|
|
1602
|
+
end
|
|
1603
|
+
return false
|
|
1570
1604
|
end
|
|
1605
|
+
when :lengthTask
|
|
1606
|
+
bookResources
|
|
1607
|
+
# The doneLength is only increased for global working time slots.
|
|
1608
|
+
@doneLength += 1 if @project.isWorkingTime(@currentSlotIdx)
|
|
1571
1609
|
|
|
1572
1610
|
# If we have reached the specified duration or lengths, we set the end
|
|
1573
1611
|
# or start date and propagate the value to neighbouring tasks.
|
|
1574
|
-
if
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
propagateDate(slot + slotDuration, true)
|
|
1612
|
+
if @doneLength >= @length
|
|
1613
|
+
if @forward
|
|
1614
|
+
propagateDate(@project.idxToDate(@currentSlotIdx + 1), true)
|
|
1578
1615
|
else
|
|
1579
|
-
propagateDate(
|
|
1616
|
+
propagateDate(@project.idxToDate(@currentSlotIdx), false)
|
|
1580
1617
|
end
|
|
1581
|
-
return
|
|
1618
|
+
return false
|
|
1582
1619
|
end
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1620
|
+
when :durationTask
|
|
1621
|
+
# The doneDuration counts the number of scheduled slots. It is increased
|
|
1622
|
+
# by one with every scheduled slot.
|
|
1623
|
+
bookResources
|
|
1624
|
+
@doneDuration += 1
|
|
1625
|
+
|
|
1626
|
+
# If we have reached the specified duration or lengths, we set the end
|
|
1627
|
+
# or start date and propagate the value to neighbouring tasks.
|
|
1628
|
+
if @doneDuration >= @duration
|
|
1629
|
+
if @forward
|
|
1630
|
+
propagateDate(@project.idxToDate(@currentSlotIdx + 1), true)
|
|
1590
1631
|
else
|
|
1591
|
-
propagateDate(@
|
|
1632
|
+
propagateDate(@project.idxToDate(@currentSlotIdx), false)
|
|
1592
1633
|
end
|
|
1593
|
-
return
|
|
1594
|
-
end
|
|
1595
|
-
elsif a('milestone')
|
|
1596
|
-
if a('forward')
|
|
1597
|
-
propagateDate(a('start'), true)
|
|
1598
|
-
else
|
|
1599
|
-
propagateDate(a('end'), false)
|
|
1634
|
+
return false
|
|
1600
1635
|
end
|
|
1601
|
-
|
|
1602
|
-
elsif a('start') && a('end')
|
|
1636
|
+
when :startEndTask
|
|
1603
1637
|
# Task with start and end date but no duration criteria
|
|
1604
|
-
|
|
1605
|
-
# For start-end-tasks without allocation, we don't have to do
|
|
1606
|
-
# anything but to set the 'scheduled' flag.
|
|
1607
|
-
@property['scheduled', @scenarioIdx] = true
|
|
1608
|
-
@property.parents.each do |parent|
|
|
1609
|
-
parent.scheduleContainer(@scenarioIdx)
|
|
1610
|
-
end
|
|
1611
|
-
return true
|
|
1612
|
-
end
|
|
1613
|
-
|
|
1614
|
-
bookResources(slot, slotDuration)
|
|
1638
|
+
bookResources
|
|
1615
1639
|
|
|
1616
1640
|
# Depending on the scheduling direction we can mark the task as
|
|
1617
1641
|
# scheduled once we have reached the other end.
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1642
|
+
currentSlot = @project.idxToDate(@currentSlotIdx)
|
|
1643
|
+
if (@forward && currentSlot + @project['scheduleGranularity'] >= @end) ||
|
|
1644
|
+
(!@forward && currentSlot <= @start)
|
|
1645
|
+
@scheduled = true
|
|
1621
1646
|
@property.parents.each do |parent|
|
|
1622
1647
|
parent.scheduleContainer(@scenarioIdx)
|
|
1623
1648
|
end
|
|
1624
|
-
return
|
|
1649
|
+
return false
|
|
1625
1650
|
end
|
|
1651
|
+
else
|
|
1652
|
+
raise "Unknown task duration type #{@durationType}"
|
|
1626
1653
|
end
|
|
1627
1654
|
|
|
1628
|
-
|
|
1655
|
+
true
|
|
1629
1656
|
end
|
|
1630
1657
|
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
# is still nil. Otherwise it contains the date of the last scheduled slot.
|
|
1635
|
-
def nextSlot(slotDuration)
|
|
1636
|
-
return nil if a('scheduled') || @isRunAway
|
|
1658
|
+
def bookResources
|
|
1659
|
+
# First check if there is any resource at all for this slot.
|
|
1660
|
+
return true unless @project.anyResourceAvailable?(@currentSlotIdx)
|
|
1637
1661
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
@lastSlot.nil? ? a('end') - slotDuration : @lastSlot - slotDuration
|
|
1642
|
-
end
|
|
1643
|
-
end
|
|
1644
|
-
|
|
1645
|
-
def bookResources(date, slotDuration)
|
|
1646
|
-
# If there are no allocations defined, we can't do any bookings.
|
|
1647
|
-
# In projection mode we do not allow allocations prior to the current
|
|
1648
|
-
# date. If the scenario is not scheduled in projection mode, this
|
|
1649
|
-
# restriction only applies to tasks with bookings.
|
|
1650
|
-
if a('allocate').empty? ||
|
|
1651
|
-
((@project.scenario(@scenarioIdx).get('projection') ||
|
|
1652
|
-
!a('booking').empty?) &&
|
|
1653
|
-
date < @project['now'])
|
|
1654
|
-
return
|
|
1655
|
-
end
|
|
1662
|
+
# If the task has resource independent allocation limits we need to make
|
|
1663
|
+
# sure that none of them is already exceeded.
|
|
1664
|
+
return unless limitsOk?(@currentSlotIdx)
|
|
1656
1665
|
|
|
1657
1666
|
# If the task has shifts to limit the allocations, we check that we are
|
|
1658
1667
|
# within a defined shift interval. If yes, we need to be on shift to
|
|
1659
1668
|
# continue.
|
|
1660
|
-
if
|
|
1661
|
-
return if
|
|
1669
|
+
if @shifts && @shifts.assigned?(@currentSlotIdx)
|
|
1670
|
+
return if !@shifts.onShift?(@currentSlotIdx)
|
|
1662
1671
|
end
|
|
1663
1672
|
|
|
1664
|
-
# If the task has resource independent allocation limits we need to make
|
|
1665
|
-
# sure that none of them is already exceeded.
|
|
1666
|
-
return unless limitsOk?(date)
|
|
1667
|
-
|
|
1668
|
-
sbIdx = @project.dateToIdx(date, false)
|
|
1669
|
-
|
|
1670
1673
|
# We first have to make sure that if there are mandatory resources
|
|
1671
1674
|
# that these are all available for the time slot.
|
|
1672
1675
|
takenMandatories = []
|
|
1673
1676
|
@mandatories.each do |allocation|
|
|
1674
|
-
return unless allocation.onShift?(
|
|
1677
|
+
return unless allocation.onShift?(@currentSlotIdx)
|
|
1675
1678
|
|
|
1676
1679
|
# For mandatory allocations with alternatives at least one of the
|
|
1677
1680
|
# alternatives must be available.
|
|
@@ -1681,8 +1684,8 @@ class TaskJuggler
|
|
|
1681
1684
|
# group must be available.
|
|
1682
1685
|
allAvailable = true
|
|
1683
1686
|
candidate.allLeaves.each do |resource|
|
|
1684
|
-
if !limitsOk?(
|
|
1685
|
-
!resource.available?(@scenarioIdx,
|
|
1687
|
+
if !limitsOk?(@currentSlotIdx, resource) ||
|
|
1688
|
+
!resource.available?(@scenarioIdx, @currentSlotIdx) ||
|
|
1686
1689
|
takenMandatories.include?(resource)
|
|
1687
1690
|
# We've found a mandatory resource that is not available for
|
|
1688
1691
|
# the slot.
|
|
@@ -1702,19 +1705,19 @@ class TaskJuggler
|
|
|
1702
1705
|
return unless found
|
|
1703
1706
|
end
|
|
1704
1707
|
|
|
1705
|
-
|
|
1706
|
-
next unless allocation.onShift?(
|
|
1708
|
+
@allocate.each do |allocation|
|
|
1709
|
+
next unless allocation.onShift?(@currentSlotIdx)
|
|
1707
1710
|
|
|
1708
|
-
# In case we have a persistent allocation we need to check if there
|
|
1709
|
-
# already a locked resource and use it.
|
|
1710
|
-
if allocation.
|
|
1711
|
-
bookResource(allocation.lockedResource
|
|
1711
|
+
# In case we have a persistent allocation we need to check if there
|
|
1712
|
+
# is already a locked resource and use it.
|
|
1713
|
+
if allocation.lockedResource
|
|
1714
|
+
bookResource(allocation.lockedResource)
|
|
1712
1715
|
else
|
|
1713
1716
|
# If not, we create a list of candidates in the proper order and
|
|
1714
1717
|
# assign the first one available.
|
|
1715
1718
|
allocation.candidates(@scenarioIdx).each do |candidate|
|
|
1716
|
-
if bookResource(candidate
|
|
1717
|
-
allocation.lockedResource = candidate
|
|
1719
|
+
if bookResource(candidate)
|
|
1720
|
+
allocation.lockedResource = candidate if allocation.persistent
|
|
1718
1721
|
break
|
|
1719
1722
|
end
|
|
1720
1723
|
end
|
|
@@ -1722,49 +1725,46 @@ class TaskJuggler
|
|
|
1722
1725
|
end
|
|
1723
1726
|
end
|
|
1724
1727
|
|
|
1725
|
-
def bookResource(resource
|
|
1728
|
+
def bookResource(resource)
|
|
1726
1729
|
booked = false
|
|
1727
1730
|
resource.allLeaves.each do |r|
|
|
1728
1731
|
# Prevent overbooking when multiple resources are allocated and
|
|
1729
1732
|
# available. If the task has allocation limits we need to make sure
|
|
1730
1733
|
# that none of them is already exceeded.
|
|
1731
|
-
break if
|
|
1732
|
-
!limitsOk?(
|
|
1734
|
+
break if @effort > 0 && @doneEffort >= @effort ||
|
|
1735
|
+
!limitsOk?(@currentSlotIdx, resource)
|
|
1733
1736
|
|
|
1734
|
-
if r.book(@scenarioIdx,
|
|
1737
|
+
if r.book(@scenarioIdx, @currentSlotIdx, @property)
|
|
1735
1738
|
# For effort based task we adjust the the start end (as defined by
|
|
1736
1739
|
# the scheduling direction) to align with the first booked time
|
|
1737
1740
|
# slot.
|
|
1738
|
-
if
|
|
1739
|
-
if
|
|
1740
|
-
@
|
|
1741
|
+
if @effort > 0 && @assignedresources.empty?
|
|
1742
|
+
if @forward
|
|
1743
|
+
@start = @project.idxToDate(@currentSlotIdx)
|
|
1741
1744
|
Log << "Task #{@property.fullId} first assignment: " +
|
|
1742
|
-
"#{
|
|
1743
|
-
|
|
1744
|
-
task.propagateDate(@scenarioIdx,
|
|
1745
|
+
"#{@start} -> #{@end}"
|
|
1746
|
+
@startsuccs.each do |task, onEnd|
|
|
1747
|
+
task.propagateDate(@scenarioIdx, @start, false, true)
|
|
1745
1748
|
end
|
|
1746
1749
|
else
|
|
1747
|
-
@
|
|
1750
|
+
@end = @project.idxToDate(@currentSlotIdx + 1)
|
|
1748
1751
|
Log << "Task #{@property.fullId} last assignment: " +
|
|
1749
|
-
"#{
|
|
1750
|
-
|
|
1751
|
-
task.propagateDate(@scenarioIdx,
|
|
1752
|
+
"#{@start} -> #{@end}"
|
|
1753
|
+
@endpreds.each do |task, onEnd|
|
|
1754
|
+
task.propagateDate(@scenarioIdx, @end, true, true)
|
|
1752
1755
|
end
|
|
1753
1756
|
end
|
|
1754
1757
|
end
|
|
1755
1758
|
|
|
1756
|
-
@tentativeStart = @project.idxToDate(sbIdx)
|
|
1757
|
-
@tentativeEnd = @project.idxToDate(sbIdx + 1)
|
|
1758
|
-
|
|
1759
1759
|
@doneEffort += r['efficiency', @scenarioIdx]
|
|
1760
1760
|
# Limits do not take efficiency into account. Limits are usage limits,
|
|
1761
1761
|
# not effort limits.
|
|
1762
|
-
@
|
|
1763
|
-
limit.inc(
|
|
1762
|
+
@allLimits.each do |limit|
|
|
1763
|
+
limit.inc(@currentSlotIdx, resource)
|
|
1764
1764
|
end
|
|
1765
1765
|
|
|
1766
|
-
unless
|
|
1767
|
-
@
|
|
1766
|
+
unless @assignedresources.include?(r)
|
|
1767
|
+
@assignedresources << r
|
|
1768
1768
|
end
|
|
1769
1769
|
booked = true
|
|
1770
1770
|
end
|
|
@@ -1773,13 +1773,13 @@ class TaskJuggler
|
|
|
1773
1773
|
booked
|
|
1774
1774
|
end
|
|
1775
1775
|
|
|
1776
|
-
# Check if all of the task limits are not exceded at the given
|
|
1776
|
+
# Check if all of the task limits are not exceded at the given _sbIdx_. If
|
|
1777
1777
|
# a _resource_ is provided, the limit for that particular resource is
|
|
1778
1778
|
# checked. If no resource is provided, only non-resource-specific limits
|
|
1779
1779
|
# are checked.
|
|
1780
|
-
def limitsOk?(
|
|
1781
|
-
@
|
|
1782
|
-
return false
|
|
1780
|
+
def limitsOk?(sbIdx, resource = nil)
|
|
1781
|
+
@allLimits.each do |limit|
|
|
1782
|
+
return false unless limit.ok?(sbIdx, true, resource)
|
|
1783
1783
|
end
|
|
1784
1784
|
true
|
|
1785
1785
|
end
|
|
@@ -1797,67 +1797,137 @@ class TaskJuggler
|
|
|
1797
1797
|
|
|
1798
1798
|
# Register the user provided bookings with the Resource scoreboards. A
|
|
1799
1799
|
# booking describes the assignment of a Resource to a certain Task for a
|
|
1800
|
-
# specified
|
|
1800
|
+
# specified TimeInterval.
|
|
1801
1801
|
def bookBookings
|
|
1802
|
-
|
|
1803
|
-
|
|
1802
|
+
firstSlotIdx = nil
|
|
1803
|
+
lastSlotIdx = nil
|
|
1804
1804
|
findBookings.each do |booking|
|
|
1805
1805
|
unless booking.resource.leaf?
|
|
1806
1806
|
error('booking_resource_not_leaf',
|
|
1807
1807
|
"Booked resources may not be group resources",
|
|
1808
1808
|
booking.sourceFileInfo)
|
|
1809
1809
|
end
|
|
1810
|
-
unless
|
|
1810
|
+
unless @forward || @scheduled
|
|
1811
1811
|
error('booking_forward_only',
|
|
1812
1812
|
"Only forward scheduled tasks may have booking statements.")
|
|
1813
1813
|
end
|
|
1814
1814
|
booking.intervals.each do |interval|
|
|
1815
1815
|
startIdx = @project.dateToIdx(interval.start, false)
|
|
1816
|
-
date = interval.start
|
|
1817
1816
|
endIdx = @project.dateToIdx(interval.end, false)
|
|
1818
|
-
tEnd = nil
|
|
1819
1817
|
startIdx.upto(endIdx - 1) do |idx|
|
|
1820
|
-
tEnd = date + slotDuration
|
|
1821
1818
|
if booking.resource.bookBooking(@scenarioIdx, idx, booking)
|
|
1822
1819
|
# Booking was successful for this time slot.
|
|
1823
1820
|
@doneEffort += booking.resource['efficiency', @scenarioIdx]
|
|
1824
1821
|
|
|
1825
|
-
#
|
|
1826
|
-
#
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
@tentativeEnd = tEnd if @tentativeEnd.nil? || @tentativeEnd < tEnd
|
|
1830
|
-
if !scheduled && (a('start').nil? || date < a('start'))
|
|
1831
|
-
@property['start', @scenarioIdx] = date
|
|
1832
|
-
Log << "Task #{@property.fullId} first booking: " +
|
|
1833
|
-
"#{a('start')} -> #{a('end')}"
|
|
1834
|
-
end
|
|
1822
|
+
# Store the indexes of the first slot and the slot after the
|
|
1823
|
+
# last slot.
|
|
1824
|
+
firstSlotIdx = idx if !firstSlotIdx || firstSlotIdx > idx
|
|
1825
|
+
lastSlotIdx = idx if !lastSlotIdx || lastSlotIdx < idx
|
|
1835
1826
|
|
|
1836
|
-
unless
|
|
1837
|
-
@
|
|
1827
|
+
unless @assignedresources.include?(booking.resource)
|
|
1828
|
+
@assignedresources << booking.resource
|
|
1838
1829
|
end
|
|
1839
1830
|
end
|
|
1840
|
-
if a('length') > 0 && @project.isWorkingTime(date)
|
|
1841
|
-
# For tasks with a 'length' we track the covered work time and
|
|
1842
|
-
# set the task to 'scheduled' when we have enough length.
|
|
1843
|
-
@doneLength += 1
|
|
1844
|
-
if !scheduled && @doneLength >= a('length')
|
|
1845
|
-
@property['end', @scenarioIdx] = tEnd
|
|
1846
|
-
@property['scheduled', @scenarioIdx] = true
|
|
1847
|
-
end
|
|
1848
|
-
end
|
|
1849
|
-
date = tEnd
|
|
1850
1831
|
end
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1832
|
+
end
|
|
1833
|
+
end
|
|
1834
|
+
|
|
1835
|
+
# For effort based tasks, or tasks without a start date, with bookings
|
|
1836
|
+
# that have not yet been marked as scheduled we set the start date to
|
|
1837
|
+
# the date of the first booked slot.
|
|
1838
|
+
if (@start.nil? || (@doneEffort > 0 && @effort > 0)) &&
|
|
1839
|
+
!@scheduled && firstSlotIdx
|
|
1840
|
+
firstSlotDate = @project.idxToDate(firstSlotIdx)
|
|
1841
|
+
if @start.nil? || firstSlotDate > @start
|
|
1842
|
+
@start = firstSlotDate
|
|
1843
|
+
Log << "Task #{@property.fullId} first booking: " +
|
|
1844
|
+
"#{@start} -> #{@end}"
|
|
1845
|
+
end
|
|
1846
|
+
end
|
|
1847
|
+
|
|
1848
|
+
# Check if the the duration criteria has already been reached by the
|
|
1849
|
+
# supplied bookings and set the task end to the last booked slot.
|
|
1850
|
+
# Also the task is marked as scheduled.
|
|
1851
|
+
if lastSlotIdx && !@scheduled
|
|
1852
|
+
tentativeEnd = @project.idxToDate(lastSlotIdx + 1)
|
|
1853
|
+
slotDuration = @project['scheduleGranularity']
|
|
1854
|
+
|
|
1855
|
+
if @effort > 0
|
|
1856
|
+
if @doneEffort >= @effort
|
|
1857
|
+
@end = tentativeEnd
|
|
1858
|
+
@scheduled = true
|
|
1859
|
+
end
|
|
1860
|
+
elsif @length > 0
|
|
1861
|
+
@doneLength = 0
|
|
1862
|
+
startIdx = @project.dateToIdx(date = @start)
|
|
1863
|
+
endIdx = @project.dateToIdx(@project['now'])
|
|
1864
|
+
startIdx.upto(endIdx) do |idx|
|
|
1865
|
+
@doneLength += 1 if @project.isWorkingTime(idx)
|
|
1866
|
+
date += slotDuration
|
|
1867
|
+
# Continue not only until the @length has been reached, but also
|
|
1868
|
+
# the tentativeEnd date. This allows us to detect overbookings.
|
|
1869
|
+
if @doneLength >= @length && date >= tentativeEnd
|
|
1870
|
+
endDate = @project.idxToDate(idx + 1)
|
|
1871
|
+
@end = [ endDate, tentativeEnd ].max
|
|
1872
|
+
@scheduled = true
|
|
1873
|
+
break
|
|
1857
1874
|
end
|
|
1858
1875
|
end
|
|
1876
|
+
elsif @duration > 0
|
|
1877
|
+
@doneDuration = ((tentativeEnd - @start) / slotDuration).to_i
|
|
1878
|
+
if @doneDuration >= @duration
|
|
1879
|
+
@end = tentativeEnd
|
|
1880
|
+
@scheduled = true
|
|
1881
|
+
elsif @duration * slotDuration < (@project['now'] - @start)
|
|
1882
|
+
# This handles the case where the bookings don't provide enough
|
|
1883
|
+
# @doneDuration to reach @duration, but the now date would be
|
|
1884
|
+
# after the @start + @duration date.
|
|
1885
|
+
@end = @start + @duration * slotDuration
|
|
1886
|
+
@scheduled = true
|
|
1887
|
+
end
|
|
1888
|
+
end
|
|
1889
|
+
end
|
|
1890
|
+
|
|
1891
|
+
# If the task has bookings, we assume that the bookings describe all
|
|
1892
|
+
# work up to the 'now' date.
|
|
1893
|
+
if @doneEffort > 0
|
|
1894
|
+
@currentSlotIdx = @project.dateToIdx(@project['now'])
|
|
1895
|
+
end
|
|
1896
|
+
|
|
1897
|
+
# Finally, we check if the bookings caused more effort, length or
|
|
1898
|
+
# duration than was requested by the user. This is only flagged as a
|
|
1899
|
+
# warning.
|
|
1900
|
+
if @effort > 0
|
|
1901
|
+
effort = @project.slotsToDays(@doneEffort)
|
|
1902
|
+
effortHours = effort * @project['dailyworkinghours']
|
|
1903
|
+
requestedEffort = @project.slotsToDays(@effort)
|
|
1904
|
+
requestedEffortHours = requestedEffort * @project['dailyworkinghours']
|
|
1905
|
+
if effort > requestedEffort
|
|
1906
|
+
warning('overbooked_effort',
|
|
1907
|
+
"The total effort (#{effort}d or #{effortHours}h) of the " +
|
|
1908
|
+
"provided bookings for task #{@property.fullId} exceeds " +
|
|
1909
|
+
"the specified effort of #{requestedEffort}d or " +
|
|
1910
|
+
"#{requestedEffortHours}h.")
|
|
1859
1911
|
end
|
|
1860
1912
|
end
|
|
1913
|
+
if @length > 0 && @doneLength > @length
|
|
1914
|
+
length = @project.slotsToDays(@doneLength)
|
|
1915
|
+
requestedLength = @project.slotsToDays(@length)
|
|
1916
|
+
warning('overbooked_length',
|
|
1917
|
+
"The total length (#{length}d) of the provided bookings " +
|
|
1918
|
+
"for task #{@property.fullId} exceeds the specified length of " +
|
|
1919
|
+
"#{requestedLength}d.")
|
|
1920
|
+
end
|
|
1921
|
+
if @duration > 0 && @doneDuration > @duration
|
|
1922
|
+
duration = @doneDuration * @project['scheduleGranularity'] /
|
|
1923
|
+
(60.0 * 60 * 24)
|
|
1924
|
+
requestedDuration = @duration * @project['scheduleGranularity'] /
|
|
1925
|
+
(60.0 * 60 * 24)
|
|
1926
|
+
warning('overbooked_duration',
|
|
1927
|
+
"The total duration (#{duration}d) of the provided bookings " +
|
|
1928
|
+
"for task #{@property.fullId} exceeds the specified duration " +
|
|
1929
|
+
"of #{requestedDuration}d.")
|
|
1930
|
+
end
|
|
1861
1931
|
end
|
|
1862
1932
|
|
|
1863
1933
|
# This function checks if the task has a dependency on another task or
|
|
@@ -1943,23 +2013,23 @@ class TaskJuggler
|
|
|
1943
2013
|
# dependency no matter how it was set.
|
|
1944
2014
|
unless atEnd
|
|
1945
2015
|
# Row 1
|
|
1946
|
-
|
|
2016
|
+
@startpreds.each do |task, onEnd|
|
|
1947
2017
|
if (onEnd && (task['forward', @scenarioIdx] ||
|
|
1948
2018
|
task['end', @scenarioIdx])) || !onEnd
|
|
1949
2019
|
return true
|
|
1950
2020
|
end
|
|
1951
2021
|
end
|
|
1952
2022
|
# Row 2
|
|
1953
|
-
|
|
2023
|
+
@startsuccs.each do |task, onEnd|
|
|
1954
2024
|
return true if task[onEnd ? 'end' : 'start', @scenarioIdx]
|
|
1955
2025
|
end
|
|
1956
2026
|
else
|
|
1957
2027
|
# Row 3
|
|
1958
|
-
|
|
2028
|
+
@endpreds.each do |task, onEnd|
|
|
1959
2029
|
return true if task[onEnd ? 'end' : 'start', @scenarioIdx]
|
|
1960
2030
|
end
|
|
1961
2031
|
# Row 4
|
|
1962
|
-
|
|
2032
|
+
@endsuccs.each do |task, onEnd|
|
|
1963
2033
|
if (!onEnd && (!task['forward', @scenarioIdx] ||
|
|
1964
2034
|
task['start', @scenarioIdx])) || onEnd
|
|
1965
2035
|
return true
|
|
@@ -1977,11 +2047,11 @@ class TaskJuggler
|
|
|
1977
2047
|
@isRunAway = true
|
|
1978
2048
|
end
|
|
1979
2049
|
|
|
1980
|
-
# This function determines if a task is
|
|
2050
|
+
# This function determines if a task is a milestones and marks it
|
|
1981
2051
|
# accordingly.
|
|
1982
2052
|
def markMilestone
|
|
1983
2053
|
return if @property.container? || hasDurationSpec? ||
|
|
1984
|
-
|
|
2054
|
+
!@booking.empty? || !@allocate.empty?
|
|
1985
2055
|
|
|
1986
2056
|
# The following cases qualify for an automatic milestone promotion.
|
|
1987
2057
|
# - --> -
|
|
@@ -1992,19 +2062,19 @@ class TaskJuggler
|
|
|
1992
2062
|
# - <-- |
|
|
1993
2063
|
# - <-- -D
|
|
1994
2064
|
# - <-- |D
|
|
1995
|
-
hasStartSpec =
|
|
1996
|
-
hasEndSpec =
|
|
2065
|
+
hasStartSpec = !@start.nil? || !@depends.empty?
|
|
2066
|
+
hasEndSpec = !@end.nil? || !@precedes.empty?
|
|
1997
2067
|
|
|
1998
|
-
@
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
(!hasStartSpec && !hasEndSpec)
|
|
2068
|
+
@milestone = (hasStartSpec && @forward && !hasEndSpec) ||
|
|
2069
|
+
(!hasStartSpec && !@forward && hasEndSpec) ||
|
|
2070
|
+
(!hasStartSpec && !hasEndSpec)
|
|
2002
2071
|
end
|
|
2003
2072
|
|
|
2004
2073
|
def checkDependency(dependency, depType)
|
|
2074
|
+
depList = instance_variable_get(('@' + depType).intern)
|
|
2005
2075
|
if (depTask = dependency.resolve(@project)).nil?
|
|
2006
2076
|
# Remove the broken dependency. It could cause trouble later on.
|
|
2007
|
-
|
|
2077
|
+
depList.delete(dependency)
|
|
2008
2078
|
error('task_depend_unknown',
|
|
2009
2079
|
"Task #{@property.fullId} has unknown #{depType} " +
|
|
2010
2080
|
"#{dependency.taskId}")
|
|
@@ -2012,14 +2082,14 @@ class TaskJuggler
|
|
|
2012
2082
|
|
|
2013
2083
|
if depTask == @property
|
|
2014
2084
|
# Remove the broken dependency. It could cause trouble later on.
|
|
2015
|
-
|
|
2085
|
+
depList.delete(dependency)
|
|
2016
2086
|
error('task_depend_self', "Task #{@property.fullId} cannot " +
|
|
2017
2087
|
"depend on self")
|
|
2018
2088
|
end
|
|
2019
2089
|
|
|
2020
2090
|
if depTask.isChildOf?(@property)
|
|
2021
2091
|
# Remove the broken dependency. It could cause trouble later on.
|
|
2022
|
-
|
|
2092
|
+
depList.delete(dependency)
|
|
2023
2093
|
error('task_depend_child',
|
|
2024
2094
|
"Task #{@property.fullId} cannot depend on child " +
|
|
2025
2095
|
"#{depTask.fullId}")
|
|
@@ -2027,16 +2097,16 @@ class TaskJuggler
|
|
|
2027
2097
|
|
|
2028
2098
|
if @property.isChildOf?(depTask)
|
|
2029
2099
|
# Remove the broken dependency. It could cause trouble later on.
|
|
2030
|
-
|
|
2100
|
+
depList.delete(dependency)
|
|
2031
2101
|
error('task_depend_parent',
|
|
2032
2102
|
"Task #{@property.fullId} cannot depend on parent " +
|
|
2033
2103
|
"#{depTask.fullId}")
|
|
2034
2104
|
end
|
|
2035
2105
|
|
|
2036
|
-
|
|
2106
|
+
depList.each do |dep|
|
|
2037
2107
|
if dep.task == depTask && !dep.equal?(dependency)
|
|
2038
2108
|
# Remove the broken dependency. It could cause trouble later on.
|
|
2039
|
-
|
|
2109
|
+
depList.delete(dependency)
|
|
2040
2110
|
error('task_depend_multi',
|
|
2041
2111
|
"No need to specify dependency #{depTask.fullId} multiple " +
|
|
2042
2112
|
"times for task #{@property.fullId}.")
|
|
@@ -2111,23 +2181,21 @@ class TaskJuggler
|
|
|
2111
2181
|
# If the user provided a completion degree we are not touching it.
|
|
2112
2182
|
if @property.provided('complete', @scenarioIdx)
|
|
2113
2183
|
calcStatus
|
|
2114
|
-
return
|
|
2184
|
+
return @complete
|
|
2115
2185
|
end
|
|
2116
2186
|
|
|
2117
2187
|
# We cannot compute a completion degree without a start or end date.
|
|
2118
|
-
if
|
|
2119
|
-
@
|
|
2120
|
-
@
|
|
2188
|
+
if @start.nil? || @end.nil?
|
|
2189
|
+
@complete = 0.0
|
|
2190
|
+
@status = 'unknown'
|
|
2121
2191
|
return nil
|
|
2122
2192
|
end
|
|
2123
2193
|
|
|
2124
2194
|
completion = 0.0
|
|
2125
|
-
if
|
|
2195
|
+
if @milestone
|
|
2126
2196
|
# Milestones are either 0% or 100% complete.
|
|
2127
|
-
@
|
|
2128
|
-
|
|
2129
|
-
@property['status', @scenarioIdx] =
|
|
2130
|
-
a('end') <= @project['now'] ? 'done' : 'not reached'
|
|
2197
|
+
@complete = completion = @end <= @project['now'] ? 100.0 : 0.0
|
|
2198
|
+
@status = @end <= @project['now'] ? 'done' : 'not reached'
|
|
2131
2199
|
else
|
|
2132
2200
|
# The task is in progress. Calculate the current completion
|
|
2133
2201
|
# degree.
|
|
@@ -2140,26 +2208,26 @@ class TaskJuggler
|
|
|
2140
2208
|
completion += comp
|
|
2141
2209
|
end
|
|
2142
2210
|
completion /= @property.children.length
|
|
2143
|
-
elsif
|
|
2211
|
+
elsif @end <= @project['now']
|
|
2144
2212
|
# The task has ended already. It's 100% complete.
|
|
2145
2213
|
completion = 100.0
|
|
2146
|
-
elsif @project['now'] <=
|
|
2214
|
+
elsif @project['now'] <= @start
|
|
2147
2215
|
# The task has not started yet. Its' 0% complete.
|
|
2148
2216
|
completion = 0.0
|
|
2149
|
-
elsif
|
|
2217
|
+
elsif @effort > 0
|
|
2150
2218
|
# Effort based leaf tasks. The completion degree is the percentage
|
|
2151
2219
|
# of effort that has been done already.
|
|
2152
|
-
done = getEffectiveWork(@project.dateToIdx(
|
|
2220
|
+
done = getEffectiveWork(@project.dateToIdx(@start, false),
|
|
2153
2221
|
@project.dateToIdx(@project['now']))
|
|
2154
2222
|
total = @project.convertToDailyLoad(
|
|
2155
|
-
|
|
2223
|
+
@effort * @project['scheduleGranularity'])
|
|
2156
2224
|
completion = done / total * 100.0
|
|
2157
2225
|
else
|
|
2158
2226
|
# Length/duration leaf tasks.
|
|
2159
|
-
completion = ((@project['now'] -
|
|
2160
|
-
(
|
|
2227
|
+
completion = ((@project['now'] - @start) /
|
|
2228
|
+
(@end - @start)) * 100.0
|
|
2161
2229
|
end
|
|
2162
|
-
@
|
|
2230
|
+
@complete = completion
|
|
2163
2231
|
calcStatus
|
|
2164
2232
|
end
|
|
2165
2233
|
|
|
@@ -2168,14 +2236,13 @@ class TaskJuggler
|
|
|
2168
2236
|
|
|
2169
2237
|
# Calculate the status of the task based on the 'complete' attribute.
|
|
2170
2238
|
def calcStatus
|
|
2171
|
-
@
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
end
|
|
2239
|
+
@status = if @complete == 0.0
|
|
2240
|
+
'not started'
|
|
2241
|
+
elsif @complete >= 100.0
|
|
2242
|
+
'done'
|
|
2243
|
+
else
|
|
2244
|
+
'in progress'
|
|
2245
|
+
end
|
|
2179
2246
|
end
|
|
2180
2247
|
|
|
2181
2248
|
# Recursively compile a list of Task properties which depend on the
|
|
@@ -2187,12 +2254,12 @@ class TaskJuggler
|
|
|
2187
2254
|
# A target must be a leaf function that has no direct or indirect
|
|
2188
2255
|
# (through parent) following tasks. Only milestones are recognized as
|
|
2189
2256
|
# targets.
|
|
2190
|
-
if @property.leaf? && !hasPredecessors &&
|
|
2257
|
+
if @property.leaf? && !hasPredecessors && @milestone
|
|
2191
2258
|
list << @property
|
|
2192
2259
|
return
|
|
2193
2260
|
end
|
|
2194
2261
|
|
|
2195
|
-
|
|
2262
|
+
@startpreds.each do |t, onEnd|
|
|
2196
2263
|
t.inputs(@scenarioIdx, list, false)
|
|
2197
2264
|
end
|
|
2198
2265
|
|
|
@@ -2220,12 +2287,12 @@ class TaskJuggler
|
|
|
2220
2287
|
# A target must be a leaf function that has no direct or indirect
|
|
2221
2288
|
# (through parent) following tasks. Only milestones are recognized as
|
|
2222
2289
|
# targets.
|
|
2223
|
-
if @property.leaf? && !hasSuccessors &&
|
|
2290
|
+
if @property.leaf? && !hasSuccessors && @milestone
|
|
2224
2291
|
list << @property
|
|
2225
2292
|
return
|
|
2226
2293
|
end
|
|
2227
2294
|
|
|
2228
|
-
|
|
2295
|
+
@endsuccs.each do |t, onEnd|
|
|
2229
2296
|
t.targets(@scenarioIdx, list, false)
|
|
2230
2297
|
end
|
|
2231
2298
|
|
|
@@ -2260,7 +2327,7 @@ class TaskJuggler
|
|
|
2260
2327
|
|
|
2261
2328
|
# If there are no chargeset defined for this task, we don't need to
|
|
2262
2329
|
# compute the resource related or other cost.
|
|
2263
|
-
unless
|
|
2330
|
+
unless @chargeset.empty?
|
|
2264
2331
|
resourceCost = 0.0
|
|
2265
2332
|
otherCost = 0.0
|
|
2266
2333
|
|
|
@@ -2270,27 +2337,27 @@ class TaskJuggler
|
|
|
2270
2337
|
resourceCost = resource.cost(@scenarioIdx, startIdx, endIdx,
|
|
2271
2338
|
@property)
|
|
2272
2339
|
else
|
|
2273
|
-
|
|
2340
|
+
@assignedresources.each do |r|
|
|
2274
2341
|
resourceCost += r.cost(@scenarioIdx, startIdx, endIdx, @property)
|
|
2275
2342
|
end
|
|
2276
2343
|
end
|
|
2277
2344
|
end
|
|
2278
2345
|
|
|
2279
|
-
unless
|
|
2346
|
+
unless @charge.empty?
|
|
2280
2347
|
# Add one-time and periodic charges to the amount.
|
|
2281
2348
|
startDate = startIdx.is_a?(TjTime) ? startIdx :
|
|
2282
2349
|
@project.idxToDate(startIdx)
|
|
2283
2350
|
endDate = endIdx.is_a?(TjTime) ? endIdx :
|
|
2284
2351
|
@project.idxToDate(endIdx)
|
|
2285
|
-
iv =
|
|
2286
|
-
|
|
2352
|
+
iv = TimeInterval.new(startDate, endDate)
|
|
2353
|
+
@charge.each do |charge|
|
|
2287
2354
|
otherCost += charge.turnover(iv)
|
|
2288
2355
|
end
|
|
2289
2356
|
end
|
|
2290
2357
|
|
|
2291
2358
|
totalCost = resourceCost + otherCost
|
|
2292
2359
|
# Now weight the total cost by the share of the account
|
|
2293
|
-
|
|
2360
|
+
@chargeset.each do |set|
|
|
2294
2361
|
set.each do |accnt, share|
|
|
2295
2362
|
if share > 0.0 && (accnt == account || accnt.isChildOf?(account))
|
|
2296
2363
|
amount += totalCost * share
|