Service sums

Use of service sums, groupServices OCL Operator

Accelerated summation and grouping in the form of an OCL operator is available for optimizing custom evaluations or for use in custom reports.

Operators

The groupServices operator exists in different variants, which differ mainly in that they are applicable to different types of objects:

Operator Description
groupServices

Global service sum operator. Refer to all services in the system. Values are output in key currency.

Since OCL operators must always be based on a specific data type, the operator is registered to Session.

groupServicesP
Project sum operator. Can be applied to a single project or to a list of projects. Values are output in key currency.
groupServicesPC

Project sum operator, but values are output in project currency. Can be applied to a single project or to a list of projects.

If this operator is applied to a list of projects, it is necessary to make sure that all projects in the list have the same currency. Otherwise, the amounts in the different currencies will simply be added together, which would result in nonsensical numbers.

groupServicesPCG

Global operator which calculates all project sums in project currency, without currency conversion. A global operator is not called on a single object, but on Session.

If you work with several currencies, you must group the service subs by project, otherwise values in different currencies will be summed up.

Session.allInstances->first->groupServicesPCG('01.01.2024', '31.12.2024', 'PROJECT’)

groupServicesU
User sum operator. Can be executed on a single user or a list of users. Values are output in key currency.
groupServicesPh
Phase sum operator. Can be applied to a single phase or to a list of phases. Values are output in key currency.
groupServicesPhC
Phase sum operator, but values are output in project currency.
groupServicesC

Service sum operator, expects a string argument for currency:

groupServicesC(from, to, group, currency) 

Can be executed on projects, phases and users and returns the values in the specified currency, calculated according to the indicated exchange rates. If an empty string is given as currency argument, results are output in key currency.

self->groupServicesC('01.01.2024','31.12.2024', '', 'EUR’)->collect(feeextopen+feeextcharged)->sum

Syntax

The syntax for all these operators is as follows:

obj->groupServicesOperator(from, to, group)

obj

Object to which the operator is applied. Can be of type project, user or phase, single or list.

Global operators are called on the current session (Session.allInstances->first).

from, to

From, to date,  passed as a string in the format dd.mm.yyyy (use dateToStrGerman).

Services are summed for the specified period. If an empty string is given, all services are used.

<obj>->groupServicesOperator(encodeDate(2025,01,01).dateToStrGerman, date.dateToStrGerman, 'PROJECT')

group

Grouping statement. Controls the grouping and sorting of the resulting sums.

Can contain various grouping terms, separated by commas. If an empty string is given, the sums are not grouped.

Possible grouping terms:

  • PROJECT: groups the service sums by projects
  • USERS: grouped by users
  • PHASE: grouped by phases
  • TYPE: grouped by service type
  • INVOICE: groups the sums according to invoices
  • YEAR: grouped by years
  • MONTH: grouped by months
  • DAY: grouped by days

The result is a list of objects of type ServiceSum, which are sorted according to the specified grouping.

ServiceSum

A service sum object has different attributes and a value for each grouping term:

Member Description
minutesExtOpen Total of external minutes not yet charged.
minutesExtOpenUnprod Total of external minutes not yet charged of services entered on projects with unproductive project types.
minutesExtCharged Total of external minutes charged
minutesExtChargedUnprod Total of external minutes charged of services entered on projects with unproductive project types.
minutesIntOpen Total of internal minutes not yet charged.
minutesIntOpenUnprod Total of internal minutes not yet charged of services entered on projects with unproductive project types.
minutesIntCharged Total of internal minutes charged
minutesIntChargedUnprod Total internal minutes charged of services entered on projects with unproductive project types.
servicesCount Quantity of services in this service sum.
project Project of this service sum, if grouped by project.
user User, if grouped by user.
phase Phase, if grouped by phases.
type Service type, if grouped by service type.
invoice Invoice, if grouped by invoices.
entrydate Start date of the date group, if grouped by year, month or day.
feeUserOpen Total fee according to the user rate, not charged.
feeUserCharged Total fee according to the user rate, charged.
feeExtOpen Total of external fee not yet charged.
feeExtOpenUnprod Total of external fee not yet charged, for services entered on projects with unproductive project types.
feeExtCharged Total of the external fee charged.
feeExtChargedUnprod Total of external fee charged, for services entered on projects with unproductive project types.
feeIntOpen Total of internal fee not yet charged.
feeIntOpenUnprod Total of internal fee not yet charged, for services entered on projects with unproductive project types.
feeIntCharged Total of internal fee charged.
feeIntChargedUnprod Total internal fee charged, for services entered on projects with unproductive project types.
costOpen Total of fee according to the cost rate, not yet charged.
costCharged Total of fee according to the cost rate, charged.

Example

On a project (or a list of projects) the groupServicesP operator is called with the following arguments:

  • Date period is January to December
  • Sums are grouped by months and projects

->groupServicesP('01.01.2024','31.12.2024','MONTH,PROJECT’)

As a result, we get a list of LeistSum objects, including the following attributes:

  • Month: entrydate.formatDateTime('mm.yyyy')
  • Project: project
  • Internal hours: minutesintOpen + minutesIntCharged
  • External fee: feeExtOpen + feeExtCharged

Special Cases

Special cases for the calculation arise, for example, in the case of fixed-price invoices without services or in the case of invoice discounts. In this case, the external value of the services cannot simply be used for the calculation of the service sums. How these special cases are dealt with in detail is explained below:

Special case Calculation

Fixed-price phases without services

(if the phase is on a fixed-price invoice without services, see there)

Is displayed as a service sum as follows:

  • EntryDate: End date of the phase
  • User: Project manager of the project. If on charged invoice, fixed project manager of the invoice (chrgProjectmanager)
  • FeeExt: planFeeServicesExt of the phase. If there is a discount on the invoice, it will be deducted pro rata (phase.planfeeservicesext / invoice.totalFeeServicesExt).
  • FeeInt: planFeeServicesInt of the phase.
  • Cost: planCostServices of the phase.
This applies to fixed-price phases without services that have no subphases. As soon as a fixed-price phase without services has subphases, the calculation is carried out on the subphases. For the parent phase, there is then no calculation and therefore no service sum.
Fixed-price invoice without services

Is displayed as a service sum as follows:

  • Entrydate: Value date of the invoice
  • User: Project manager of the project. If on charged invoice, fixed project manager of the invoice (chrgProjectmanager)
  • FeeExt: fixedPriceServices of the invoice. If there is a discount on the invoice, it will be deducted (-totaldiscountamount).
  • FeeInt: fixedPriceServices of the invoice.
Invoices with discount

If there is a discount on an invoice, it is deducted pro rata from the feeExt of the service sum. The percentage is calculated as follows:

feeExt – (invoice.totalDiscountAmount * feeExt / invoice.totalFeeServicesExt)

Fixed-price phases and invoices with fixed services

In the case of fixed-price invoices or fixed-price phases, the fixed price is distributed proportionally to the external fee of the services that are not fixed (see also fee for fixed-price phases and invoices), i.e. have not checked the fix external values checkbox. However, if ALL services of an invoice are fixed, then the fixed price on the phase or on the invoice cannot be allocated to the services. In this case, the service sum cannot reflect the fixed price.

Total service sums <> total services

As a result of these special cases, deviations may arise between the sum of the services and the service sums. If, for example:

openServices->union(charged)->select((entrydate >= encodedate(2024,1,1)) 
and (entrydate < encodedate(2025,1,1))).wertExt->sum

returns a different value than:

self->groupServicesP('01.01.2024','31.12.2024','')->collect(feeExtOpen + feeExtCharged)->sum

check whether it has any of the above-mentioned special cases in the period analysed.

In the case of fixed services on fixed-price phases or invoices, no difference can be found here, since neither the services nor the service sums include the fixed amount. In this case, you will only see the difference if you compare the sums of the services with the sums of the invoices.

Restriction for large amounts of data and firebird < 5

Vertec ships with Firebird 5 as of version 6.7.0.9 and later, this issue no longer exists. However, if a groupServicesOperator is called on a list of projects, phases, or users with an earlier version of Firebird, there is the following limitation:

If the list of calling objects (i.e. not the quantity of services in the result, but the projects, phases or users on which the operator is called) is greater than 1500 entries, there is an error from the Firebird server due to invalid SQL:

General SQL Error.
Implementation limit exceeded
too many values (more than 1500) in member list
to match against.

One way to work around this problem is to use the global groupServices operator, which applies to all services.

Example: If there are more than 1500 projects and you want to call project->groupServicesP(...), you can work around this:

Session.allInstances->first->groupServices('01.01.2025', '30.06.2025', 'PROJECT')

Returns a list of service sums for all services, grouped by projects.

Performance behavior of service sums

Since the service sums are calculated optimally via SQL, the behavior of changes to basic data is slightly different than otherwise in Vertec.

Service sums are always recalculated as soon as any service is changed in the system. However, the recalculation only takes place if the service sum values are somehow visible. It is therefore not advisable to have a list of service sums on the screen at the same time as entering new services, as all sums would be recalculated with each new entry. Calculating service sums is relatively fast, but if you recalculate unnecessarily often, the delay is still noticeable.

In particular, it is not recommended to use groupServicesOperators in column expressions of lists, since the performance gain in calculating the sum is usually overcompensated by the large quantity of individual SQL queries.

Shadow Attributes

Various values in Vertec are derived, which means that they are calculated each time they are accessed. This means that they are always up-to-date, but not retrievable in the database.

To ensure that these values are still available for the accelerated summation, there are so-called shadow attributes, which are written when calculating the derived attributes. These shadow attributes are persistent, i.e. they are stored in the database and can be queried directly.

The following shadow attributes are available. They are prefixed with sql.

Attribute Class

sqlFeeExt

Service, Expense, Outlay

sqlMinutesExt

Service

sqlFeeInt, sqlDiscountAmount

Expense and Outlay

sqlRabattBetrag (only versions before 6.4.0.17)

Invoice

sqlValutaDatum (only versions before 6.4.0.17)

Invoice

Netherlands

United Kingdom