OCL, Python, renderer and list controller for resource planning
Product line
Standard
|Expert
Operating mode
CLOUD ABO
|ON-PREMISES
Modules
Services & CRM
Budget & Phases
Purchases
Resource Planning
Business Intelligence
For use in Time and pivot tables of resource planning, the following Custom Renderer are included for the List Settings :
Custom renderer | Value |
---|---|
vtcplanning.PlannedMinutesRenderer | Entering (and displaying) the budgeted values in the individual cells |
vtcplanning.NetCapacityRenderer | Net availability of users |
vtcplanning.CustomNetCapacityRenderer | From Vertec 6.7.0.5. Net availability of users taking into account the system setting . |
vtcplanning.GrossCapacityRenderer | Gross availability of users |
vtcplanning.RemainingCapacityRenderer | Residual availability of users |
vtcplanning.CustomRemainingCapacityRenderer | From Vertec 6.7.0.5. Adjusted Net Capacity (%) availability of users taking into account the system setting Adjusted net capacity (percent). |
All of these renderers can be used in two ways:
Dynamic means that the column repeats to the right for each entry, i.e. in time tables per interval, in pivot tables per user, project or phase.
In this case, the cell displays the value that matches the row/column combination.
%col%
If the column is not marked as dynamic, it is shown only once per list.
The displayed value refers to the entire list and shows the summed value across all columns.
Dynamic: No
These renderers are based on the list controllers supplied in the List Controllers module and can therefore be easily inserted into the lists within the resource planning.
To use them outside of resource planning, one of these controllers must inherit them and implement certain methods, as described in the Outside of resource planning section.
For resource planning, the method setresourceplanvalue is available, with which resource plan values can be set via Python code:
setresourceplanvalue(project_or_phase, date, value)
project / phase | Depending on the set planning level a project or a project phase must be specified here. |
date | The period in which the date is located is filled with the value. |
value | Value in minutes. |
bearb = vtcapp.currentlogin() projekt = argobject bearb.setresourceplanvalue(projekt, vtcapp.strtodate('15.05.2023'), 180)
setresourceplanvalue(worker, date, value)
worker | A planning editor or a project editor is passed as worker. |
date | The period in which the date is located is filled with the value. |
value | Value in minutes. |
bearb = vtcapp.currentlogin() projekt = argobject projekt.setresourceplanvalue(bearb, vtcapp.strtodate('15.05.2023'), 180)
The method already available in versions before 6.6 on vtcapp : vtcapp.setresourceplanvalue(bearbeiter, projekt, phase, date, intervalType, value)
still works, but the intervalType is no longer considered (but still needs to be specified).
The specially available Ocl variables are also available in resource planning, among other things. Here the following are especially important:
varStartDate | The start date of the Resource Planning View
:
|
varEndDate | The end date of the Resource Planning View
:
|
varContext | Contains the currently selected entry in the tree (see image below). In resource planning, the variable allows lists to be customized depending on whether a Resource Planning View is shown for individual objects or lists. |
varParent | Contains the object to which the container selected in the tree belongs (see screenshot below). On individual objects, this variable is empty. |
These variables can be accessed in list columns of resource planning tables, as well as in List Controllers and Custom Renderers contained therein, because the self.evalocl() method uses a specific OCL evaluator in which these variables are available.
In contrast to the other lists in Vertec, where List Controller can be accessed from the columns, the representations in resource planning are calculated directly by the list controllers.
When using list controllers in resource planning, the following additional methods are available:
initialize_with_period(self, start, end, subscriber) |
Special variant of initialize for resource planning, which additionally receives start and end of the entire period and initializes the ResourcePlanningProvider. |
get_row_objects(self, subscriber) |
Calculates the row objects that are displayed in the list. In this way, all rows for which a budgeted value has been entered are determined for resource planning views. |
get_row_objects_type(self) |
Calculates the type of row objects. If it is constant, it can also be used as an attribute Only in combination with |
add_row_object(self, obj) |
Used for the star row functionality to add more scheduled entries to the list. This method must be present in order for lists with List controller and “Show Add row” option turned on to show an asterisk. Supported for Resource Planning Views only. |
All Resource Planning Views and Utilization Dimensions are based on a list controller. These are supplied in the vtcplanning module.
To create or extend tables or graphs in resource planning, you can inherit from these controllers. Depending on the controller, other methods are available, see Example Inheriting from an existing list controller .
For performance reasons, we do not recommend showing actual values directly in resource tables where scheduling is also performed, especially not for each interval.
There are several options for evaluations with resource plan values.
We recommend that you do not show analysis columns directly in resource tables, which are also planned in order to keep these lists performing at all times.
However, it is easy to create additional Resource Planning Views , which can be shown for evaluation purposes. For this purpose, the List Controller can inherit the table that you want to expand.
First, we find out the name of the list controller we want to inherit from. In the example we want to equip a time table with more values.
Under Einstellungen > Ressourcenplanung > Ressourcenplanungsansichten
I find the table I want to inherit from. Here, for example, the projects – user time table:
The List Controller field specifies the controller from which we want to inherit: vtcplanning.SingeObjectTimeTableController
. The corresponding code can be viewed via the button with the three dots. A list of all supplied controllers can be found in the description of the Module “vtcplanning”
.
Now we create a Script for our list controller and inherit from the above controller:
import vtcplanning class Controller(vtcplanning.SingleObjectTimeTableController): pass
So we now have a full-fledged controller that does the same thing and provides the same renderers as the original.
We can already enter this as a list controller in our view. To do this, we create a new Resource Planning View and add it, in our example:
For testing purposes, the table can now be shown in the resource planning. It should look exactly like the one we assumed.
All Methods and attributes of a list controller as well as the Resourceplanningprovider are now available here.
Now let’s expand our controller with the desired evaluation values. In our example we want to show the following additional columns:
The values should be pre-calculated, i.e. not evaluated individually in each cell. To do this, we extend the initialize_with_period
method of our controller and pre-calculate the values.
As soon as a method is overwritten, the original method will no longer be executed. So if you want to extend a method, you have to fire what the original method does. To do this, we first call it again (simply copy 1:1):
import vtcplanning class Controller(vtcplanning.SingleObjectTimeTableController): def initialize_with_period(self, start, end, subscriber): vtcplanning.SingleObjectTimeTableController.initialize_with_period(self, start, end, subscriber)
Now we are even and can pre-calculate our values.
Note the following: The supplied controllers such as the vtcplanning.SingleObjectTimeTableController
are general, so they can be used on users, projects, and project phases alike. If we want to extend our controller in the same way, we need to make sure that the expressions we use are usable for all cases.
In this example, we start from the Planning Level planning level. We therefore cover projects and users. We evaluate our actual values by means of Sums Of Services .
import vtcplanning class Controller(vtcplanning.SingleObjectTimeTableController): def initialize_with_period(self, start, end, subscriber): vtcplanning.SingleObjectTimeTableController.initialize_with_period(self, start, end, subscriber) # Calculation of the leistsum objects. Consider the different Object types obj = self.context if obj.istypeof("Projekt"): self.leistSumList = obj.evalocl("self->groupleistungenP('{}','{}','MONTH, BEARBEITER')".format(vtcapp.datetostrgerman(start), vtcapp.datetostrgerman(end))) self.oclMember = "bearbeiter" if obj.istypeof("Projektbearbeiter"): self.leistSumList = obj.evalocl("self->groupleistungenB('{}','{}','MONTH, PROJEKT')".format(vtcapp.datetostrgerman(start), vtcapp.datetostrgerman(end))) self.oclMember = "projekt"
Now we have a list of values over the entire period, grouped by interval (in the example month).
So the values are pre-calculated and available in the controller. Now we need a Custom Renderer
for the actual column. For this we inherit from vtcplanning.MinutesRendererBase
to determine its method get_column_index_from_expression
to be able to apply.
We evaluate the start date of the up-to-date column and then filter the list of service sums preloaded in the controller by date and by the up-to-date object:
class AufwandExtRenderer(vtcplanning.MinutesRendererBase): is_readonly = True # Summiert MinutenExt für den Zeitraum def get_value(self, rowobj, expression, subscriber): column_index = self.get_column_index_from_expression(expression) start_date = vtcapp.datetostr(self.controller.get_date_by_column_index(column_index)) return self.controller.leistSumList.evalocl("self->select(datum.asstring = '{}')->select({}.objid={})->collect(minutenextoffen+minutenextverrechnet)->sum".format(start_date, self.controller.oclMember, rowobj.objid),subscriber)
Now we can insert the actual column into the list:
Now we want to output the percentage of the time already worked to the planned time. To do this, we create a new renderer, in which we access both the preloaded list of performance sums and the planned values.
class AufwandAnteilRenderer(vtcplanning.MinutesRendererBase): is_readonly = True converter=None value_type="float" # Berechnet den Anteil der geleisteten an den geplanten Zeiten def get_value(self, rowobj, expression, subscriber): column_index = self.get_column_index_from_expression(expression) start_date = vtcapp.datetostr(self.controller.get_date_by_column_index(column_index)) planwert = self.controller.get_planned_minutes(column_index, rowobj, subscriber) aufwandext = self.controller.leistSumList.evalocl("self->select(datum.asstring = '{}')->select({}.objid={})->collect(minutenextoffen+minutenextverrechnet)->sum".format(start_date, self.controller.oclMember, rowobj.objid),subscriber) if planwert: return float(aufwandext)/float(planwert)
As long as the values in the list are calculated per row, column and interval, there is no need for a special provider. In that case, the accesses are sufficient as shown in the previous section.
However, if you want to show data in the same list that, for example, goes beyond the time period or otherwise includes values that are not shown in the list, you need your own provider.
In our example, we want to expand the table above and show the total of scheduled values per row, regardless of the interval selected or the period displayed, but in total.
To do this, we create a separate provider. First, we call the setup_provider
Method of our controller on:
import vtcplanning import vtcplanningcore import datetime class Controller(vtcplanning.SingleObjectTimeTableController): def setup_provider(self, subscriber): vtcplanning.SingleObjectTimeTableController.setup_provider(self, subscriber) # Zusätzlichen Provider erstellen source_entry = self.get_source_entry() source_entries = vtcplanning.get_as_list(source_entry) self.total_start = datetime.date(2000, 1, 1) self.total_end = datetime.datetime(2100, 1, 1) self.total_provider = vtcplanningcore.ResourcePlanningProvider(source_entries, self.total_start, self.total_end)
We now have two providers:
Now we create another custom renderer to display the total value in the list. In it we can now access the additional provider:
class TotalGeplantRenderer(vtcplanning.MinutesRendererBase): is_readonly = True # Summiert die Planzeiten ohne Einschränkung des Zeitraums def get_value(self, rowobj, expression, subscriber): return self.controller.total_provider.get_planned_minutes_aggregated( self.controller.context, rowobj, self.controller.total_start, self.controller.total_end, subscriber)
Now we can insert the total column in the list:
Outside of resource planning, i.e. in the normal Vertec lists, we have to create the List Controller ourselves and access the plan data via Resourceplanningprovider .
As a first example, we would like to show in a project list per project how much time has been planned overall for that project.
To do this, we create the following List Controller :
import vtcplanningcore import datetime class Controller: def initialize(self, subscriber): self.startdatum = datetime.date(2000, 1, 1) self.enddatum = datetime.datetime(2100, 1, 1) source_entries = self.get_context_entries() self.rscprovider = vtcplanningcore.ResourcePlanningProvider(source_entries, self.startdatum, self.enddatum)
We can now add this controller to our project list as List Controller
save:
Now we need a custom renderer for the column, in which we access the provider created above:
class ProjektPlanwertRenderer: value_type = "integer" converter = "minutes" def get_value(self, rowobj, expression, subscriber): return self.controller.rscprovider.get_planned_minutes_aggregated(rowobj, None, self.controller.startdatum, self.controller.enddatum, subscriber)
We then insert this in the list settings of our project list as a renderer in the column:
There are the following OCL methods that can be used by project and planning editors:
.getResPlanMinutes(from, to) |
Returns the scheduled time for the user for the specified period (from, to date) in minutes. Does the same as the ResourcePlanningProvider.get_planned_minutes (see previous section). |
.getResRsrcMinutes(from, to) |
Returns the user’s resource time for the specified period (from, to date) in minutes. This is not the time still available, but the total standard time relevant to the resource schedule. The difference to standard time is that only absences of type “free” affect standard time, whereas all types of absences (free, vacation, compensation) are deducted from the resource time (see also the table on working time exceptions in the article Absences). Does the same as the ResourcePlanningProvider.get_net_capacity_minutes (see previous section). |
Then you can still show the actual time for the same period and work with it. Example:
The example shows a Sql folder for the class project user. As a query, there are two date fields From, To.
These date fields are accessible in the list (varVon
, varBis
The individual expressions are thus:
self.getResRsrcMinuten(varVon, varBis)
self.getResPlanMinuten(varVon, varBis)
self.getArbeitszeit(varVon, varBis)
self.getResPlanMinuten(varVon, varBis)-self.getArbeitszeit(varVon, varBis)
Note: This is just an example. For performance reasons, not all project users should be shown in the list, but a filtered list should be shown. But here it is clear why this list is not performing: All values must be loaded separately for each user and filtered by date, sometimes even several times.
In any case, the variant via the ResourcePlanningProvider is more recommended, because firstly more values are available there (e.g. also for projects or phases) and furthermore you can benefit from a better caching. In particular, plan data, i.e. the ResourceLinks directly, must always be queried via the ResourcePlanningProvider for performance reasons.
For the same SQL folder, you need the following controllers:
import vtcplanning class Controller(vtcplanning.TimeTableControllerBase): def initialize(self, subscriber): start = self.evalocl("varVon") end = self.evalocl("varBis") vtcplanning.TimeTableControllerBase.initialize_with_period(self, start, end, subscriber) def setup_provider(self, subscriber): source_entries = self.get_context_entries() self.create_and_set_provider(source_entries, self.period_start, self.period_end, subscriber) def get_capacity_method_arguments(self, column_index, rowobj, subscriber): return (rowobj, self.period_start, self.period_end) def get_planned_minutes_aggregated(self, rowobj, subscriber): return self.get_provider().get_planned_minutes_aggregated(rowobj, None, self.period_start, self.period_end, subscriber)
The controller inherits from vtcplanning.TimeTableControllerBase, because we want to use the normal renderers without having to calculate everything again.
If this is stored in the SQL folder as a list controller, the Custom renderer of resource planning can be used in the following columns:
vtcplanning.NetCapacityRenderer
vtcplanning.CustomNetCapacityRenderer
vtcplanning.PlannedMinutesRenderer
vtcplanning.RemainingCapacityRenderer
vtcplanning.CustomRemainingCapacityRenderer
Again in this example, we use an SQL folder with users. This time, we only pass a date, which serves as the start date. In the list, we show both the still available capacity of the individual users per interval as well as the total capacity.
For this you need the following controllers:
import vtcplanning class Controller(vtcplanning.TimeTableControllerBase): def initialize(self, subscriber): date = self.evalocl("varDate") start = vtcapp.firstdayofmonth(date) end = vtcapp.incmonth(start, 5) vtcplanning.TimeTableControllerBase.initialize_with_period(self, start, end, subscriber) def setup_provider(self, subscriber): source_entries = self.get_context_entries() self.create_and_set_provider(source_entries, self.period_start, self.period_end, subscriber) def get_capacity_method_arguments(self, column_index, rowobj, subscriber): if column_index is None: date_from = self.period_start date_to = self.period_end else: date_from = self.get_date_by_column_index(column_index) date_to = None return (rowobj, date_from, date_to)
get_context_entries()
.get_capacity_method_arguments
be implemented. I just copy the code from the original function.Now you can use the usual Renderer in the list:
vtcplanning.RemainingCapacityRenderer
or vtcplanning.CustomRemainingCapacityRenderer
.vtcplanning.RemainingCapacityRenderer
or vtcplanning.CustomRemainingCapacityRenderer
, add column as Dynamisch
mark, expression %col%
.The Business Intelligence module is also suitable for evaluations with resource plan values, where you can create your own evaluations.
Such an evaluation is available for download as a Plug-in: bi revenue forecast from resource planning .
Important basics of working with resource planning data at a glance:
initialize_with_period(self, start, end, subscriber)
be implemented.initialize(self, subscriber)
be implemented.get_date_by_column_index(column_index)
, see example Capacity List
.column_index
is always the date (as an integer), otherwise Nonerowobj
is always the row objectsource_entry
is always the object on which the table is called.setup_provider(self, subscriber)
implemented and in it the method create_and_set_provider(self, source_entries, start_date, end_date, subscriber)
with the appropriate arguments, see Via legacy controller and standard custom renderers
.get_capacity_method_arguments
implemented in the controller. To do this, you can simply copy and paste the standard method from the vtcplanning module 1:1, see Example Via legacy controller and standard custom renderers
.planned_minutes_aggregated(source_entry, rowobj, self.period_start, self.period_end, subscriber)
implemented in the controller. Here it is best to copy the standard method from the vtcplanning module and pass, depending on the list, the corresponding arguments. These are described in detail in the article about the Vertec Python Features
. See Example Via legacy controller and standard custom renderers
.get_planned_minutes_aggregated(source_entry, rowobj, self.period_start, self.period_end, subscriber)
any budget value can be queried on a ResourcePlanningProvider, see Using list controller and resourceplanningprovider
self.controller
access the list controller stored in the list.get_column_index_from_expression(expression)
, which can be used to determine the interval of the column, see Writing Write your own renderer
.