OCL, Python, custom renderer and list controller for resource planning

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

Created: 27.03.2023
Machine translated
Updated: 14.12.2023 | New renderers vtcplanning.CustomNetCapacityRenderer and vtcplanning.CustomRemainingCapacityRenderer with Vertec 6.7.0.5.

Content

Custom renderer for resource planning tables Target-actual comparisons / show other kpis

In lists within resource planning

In lists outside of resource planning

Python method for setting resource plan values
Ocl variables for resource planning
List controller in resource planning
Evaluations using business intelligence (bi) module
In a nutshell

Custom renderer for resource planning tables

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 (repeated per column)

    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.

    • Expression: %col%
    • Dynamic: Yes
  • Static (as total per row)

    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.

    • Expression: empty
    • 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.

Python method for setting resource plan values

For resource planning, the method setresourceplanvalue is available, with which resource plan values can be set via Python code:

  • On Project Editor , PlanningWorker (planning editor) and AbstractWorker (basic class of both):

    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)
  • On project or phase :

    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).

OCL variables for resource planning

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 :
  • Time table: Start date of the first interval in period
  • Pivot table: Interval start date
varEndDate The end date of the Resource Planning View :
  • Time table: End date of last interval in period
  • Pivot table: End date of interval
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.

List controller in resource planning

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 row_objects_type be indicated.

Only in combination with get_row_objects useful. Supported only for Resource Planning Views .

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 .

Target-actual comparisons / Show other KPIs

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.

In lists within resource planning

Inheriting from an existing list controller

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:

  1. Time already served in this interval.
  2. Percentage of time already worked compared to planned time.

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).

Write your own renderer

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)

Creating an Additional ResourcePlanningProvider

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:

  • self.provider: The default available provider is created by calling vtcplanning.SingleObjectTimeTableController.setup_provider(self, subscriber) as in the original method.
  • self.total_provider: This is the additional provider that we created ourselves with a date interval that certainly includes all data.

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:

In lists outside of resource planning

Using list controller and ResourcePlanningProvider

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 select the period we want to cover as the start and end date.
  • The entries in the list are retrieved using the controller method self.get_context_entries().
  • Then we create a provider for the entries in the list and the selected date range.

We can now add this controller to our project list as List Controller save:

  • If it is a folder, we define the list controller in the folder properties:
  • If it is a link container, we store the list controller on the link type on the corresponding page:

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:

Via OCL Methods

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:

  • Available resources: self.getResRsrcMinuten(varVon, varBis)
  • Of which planned: self.getResPlanMinuten(varVon, varBis)
  • Already achieved: self.getArbeitszeit(varVon, varBis)
  • Remaining availability: 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.

Via legacy controller and standard custom renderers

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)
Explanation

The controller inherits from vtcplanning.TimeTableControllerBase, because we want to use the normal renderers without having to calculate everything again.

  • In initialize(self, subscriber) we pass the date period.
  • In the setup_provider(self, subscriber) we pass the up-to-date editor list.
  • The get_capacity_method_arguments method must be present when accessing the capacity renderers.
  • The get_planned_minutes_aggregated method must be present when the scheduling renderer is accessed.

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:

  • Available resources: vtcplanning.NetCapacityRenderer
  • Custom available resources: vtcplanning.CustomNetCapacityRenderer
  • Of which planned: vtcplanning.PlannedMinutesRenderer
  • Remaining availability: vtcplanning.RemainingCapacityRenderer
  • Custom availability: vtcplanning.CustomRemainingCapacityRenderer

Capacity list

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)
Explanation
  • Since I want to use the normal capacity renderers, I inherit from an existing controller, in this example the TimeTableControllerBase.
  • I start from the planning interval month (firstofmonth) and show 5 planning intervals (incMonth 5).
  • I also leave the creation of the provider to the standard method, pass only the corresponding list entries get_context_entries().
  • If I want to use capacity renderers, the method must 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:

  • Available total: vtcplanning.RemainingCapacityRenderer or vtcplanning.CustomRemainingCapacityRenderer.
  • Available (individual intervals): vtcplanning.RemainingCapacityRenderer or vtcplanning.CustomRemainingCapacityRenderer, add column as Dynamisch mark, expression %col%.

Evaluations using business intelligence (BI) module

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 .

In a Nutshell

Important basics of working with resource planning data at a glance:

  1. Copy out the code of the vtcplanning module and use it as a reference tool. You can do this by clicking on the three dots in the Resource Planning View field in any resource planning view. This list contains all the code of vtcplanning.
  2. Whenever inherited from one of the included resource planning controllers, the method must initialize_with_period(self, start, end, subscriber) be implemented.
  3. If a separate list controller is created that does not inherit from a resource planning controller, the method must initialize(self, subscriber) be implemented.
  4. Find interval of a column: The TimeTableControllerBase (and all controllers that inherit from it) have the method get_date_by_column_index(column_index), see example Capacity List .
  5. In Timetable :
    • column_index is always the date (as an integer), otherwise None
    • rowobj is always the row object
    • source_entry is always the object on which the table is called.
Within resource planning
  1. If you want to expand a table within the resource planning, you can simply inherit it from the corresponding controller and then insert your enhancement, see Inheriting from an existing list controller .
  2. The included controllers are generic, so they can be used on users, projects, and project phases alike. If you want to extend your controller as well, you need to make sure that the expressions you use are usable for all possible cases. However, since the planning level and planning interval will hardly change in your operation, for simplicity, you can also skip this and implement only the cases you need, such as Inheriting from an existing list controller .
  3. Your own ResourcePlanningProvider is only needed if you want to evaluate data that goes beyond the default values, e.g. a different date range, users, projects or project phases that are not shown in the list or are not the object on which an evaluation is shown, see section Creating an additional resourceplanningprovider .
Outside of resource planning
  1. Assigning a list controller to a list (link container, folder) outside of resource planning does not mean that the entire list will then access this list controller. It only means that you can access this list controller and the values calculated in it in the custom renderers.
  2. You can also inherit from an existing resource planning controller outside of resource planning. Do this if you want to use the default renderers for resource planning, see Via legacy controller and standard custom renderers . In this case, you usually inherit from the TimeTableControllerBase, which contains everything you need.
  3. If you create a ResourcePlanningProvider with its own date interval, the method 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 .
  4. If you want to use the default capacity renderers outside of resource planning (vtcplanning.NetCapacityRenderer, vtcplanning.CustomNetCapacityRenderer, vtcplanning.GrossCapacityRenderer, vtcplanning.RemainingCapacityRenderer, vtcplanning.CustomRemainingCapacityRenderer), the method must be 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 .
  5. If you want to use the default planning renderer (vtcplanning.PlannedMinutesRenderer) outside of resource planning, the method must 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 .
  6. With the method 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
  7. You can also use a ResourcePlanningProvider in a regular controller (that does not inherit from a resource planning controller). This is particularly useful if you want to show resource planning data in an existing list outside of the resource planning, see Using list controller and resourceplanningprovider .
Renderer
  1. You can use all custom renderers via self.controller access the list controller stored in the list.
  2. If you want to show a planned value in the list, which refers to a specific date interval, it is best to inherit it from vtcplanning.MinutesRendererBase, which has the method get_column_index_from_expression(expression), which can be used to determine the interval of the column, see Writing Write your own renderer .