﻿<?xml version="1.0" encoding="utf-8"?>
<configset name="VacationBalanceInDays" author="UHE">
  <comment>2026-05-12 UHE: v1</comment>
  <requirements>
    <version-requirement>6.8.0.17</version-requirement>
  </requirements>
  <update />
  <references />
  <objects>
    <object class="Script" alias="Script_module_setlocale4">
      <member name="entryID">ScriptModuleSetLocale</member>
      <member name="designation">module_setlocale</member>
      <member name="containerScript">false</member>
      <member name="scriptText">#
# coding: windows-1252
#
# Name: module_setlocale
# Class: -
# ObjectScript: -
# ContainerScript:
# EventType: -
# EventClass: -
# EventMembers: -


import locale


def get_required_locale(current_language):
    """
    Gets required locale according to the user's language or default language
    when user's language is not set.
    If no language is set, setting default to Swiss German.
    """

    if current_language == "DD":
        required_locale = "deu_deu"
    elif current_language == "EN":
        required_locale = "eng_UK"
    elif current_language == "FR":
        required_locale = "fra_fra"
    elif current_language == "IT":
        required_locale = "ita_ita"
    elif current_language == "NL":
        required_locale = "nld_nld"
    else:
        # fallback to CH
        required_locale = "deu_che"

    return required_locale


def set_locale_by_language(language=""):
    """
    Sets locale according to:
    1. given language, or when not given, then according to
    2. the user's language or 
    3. default language
    If no language is set, setting default to Swiss German.
    """

    if language:
        required_locale = get_required_locale(language)
    else:
        # user's language
        current_language = vtcapp.currentlogin().language

        if not current_language:
            # fallback to default language when not set
            current_language = vtcapp.getpropertyvalue("Language", "")

        required_locale = get_required_locale(current_language)

    locale.setlocale(locale.LC_ALL, required_locale)
</member>
      <member name="eventType">None</member>
      <member name="objectScript">false</member>
      <member name="extendedRights">false</member>
      <member name="selectedObjectsScript">false</member>
      <member name="platform">Python3</member>
    </object>
    <object class="Script" alias="Script_module_vacation_balance_in_days4">
      <member name="entryID">ScriptModuleVacationBalanceInDays</member>
      <member name="designation">module_vacation_balance_in_days</member>
      <member name="containerScript">false</member>
      <member name="scriptText"># coding: windows-1252
#
# Designation: module_vacation_balance_in_days
# Show in Vertec menus:
# Classes:
# Display condition:
# Event script for:
# Event classes:
# Event fields:
# Extended rights:
  
# Contains functions to display vacations as days
  
# 2026-05-12 UHE: Initial version


import vtcapp # type:ignore
import locale
from module_setlocale import set_locale_by_language

BUTTON_INFO = "info"
NO_BUTTON = ""
LABEL_DAYS = "days"
LABEL_DAY = "day_pv"
LABEL_HOURS = "h"
MAX_LINES = 12


class Vacation():
    """
    Helper class to store the vacations for display in dialog. 
    We cannot use the Vertec objects as for vacations with 
    intersection in two periods we need two entries.
    """

    def __init__(self, date_string, time_string, balance_string, is_selected):
        self.date_string = date_string
        self.time_string = time_string
        self.balance_string = balance_string
        self.is_selected = is_selected
        

class VacationPeriod():
    """
    Helper class to store the vacation periods
    (periods, without a change in the vacation settings).
    """

    def __init__(self, user, start_date, end_date, working_time_per_day, working_days_per_week):
        self.user = user
        self.start_date = start_date
        self.end_date = end_date
        self.working_time_per_day = working_time_per_day
        self.working_days_per_week = working_days_per_week

    def _format_days_hours_string(self, minutes):
        """
        Formats minutes as a combined days/hours display string 'x Days / hh:mm h'.
        """
        
        return "{} / {}".format(
            _get_days_string_from_minutes(minutes, self.working_time_per_day),
            _get_hours_string(minutes),
        )

    def get_balance_start_string(self, date):
        """
        Returns the vacation balance at the start of the given date as a display string.
        """

        return self._format_days_hours_string(get_vacation_balance_start(self.user, date))

    def get_employment_level_string(self, date):
        """
        Returns the employment level in the period as a display string.
        """
        
        return "{} %".format(_format_number(get_employment_level(self.user, date)*100.0))
    
    def get_working_time_string(self):
        """
        Returns the average working time in the period as a display string.
        """

        if self.working_days_per_week == 5:
            result = vtcapp.translate("({} per day)").format(_get_hours_string(self.working_time_per_day))
        else:
            result = vtcapp.translate("({} per day on {} days)").format(_get_hours_string(self.working_time_per_day), _format_number(self.working_days_per_week))

        return result

    def get_taken_string(self, start_date, end_date):
        """
        Returns the vacation taken in the given period as a display string.
        """

        return self._format_days_hours_string(get_vacation_taken(self.user, start_date, end_date))

    def get_balance_string(self, date):
        """
        Returns the vacation balance at the end of the given date as a display string.
        """

        return self._format_days_hours_string(get_vacation_balance(self.user, date))

    def get_vacation_credit_string(self, start_date, end_date):
        """
        Returns the vacation credit for the given period as a display string.
        """

        return self._format_days_hours_string(get_vacation_credit(self.user, start_date, end_date))

    def get_vacation_entries(self, selected_id):
        """
        Returns all vacation entries overlapping with this period.

        Entries that span across period boundaries are clipped to fit within
        start_date and end_date, so each entry reflects only the time within
        this period.
        """

        start_date_ocl = vtcapp.ocldate(self.start_date)
        end_date_ocl = vtcapp.ocldate(self.end_date)
        ocl_query = (
            "absences-&gt;select(type=1)"
            "-&gt;select((endDate.notNull and (endDate&gt;={0})) or (endDate.isNull and (entryDate&gt;={0})))"
            "-&gt;select(entryDate&lt;={1})-&gt;orderby(entryDate)"
        ).format(start_date_ocl, end_date_ocl)

        vacations = []
        for vacation in self.user.evalocl(ocl_query):
            vacation_start_date = max(vacation.entryDate.date(), self.start_date.date())
            vacation_end_date = (
                min(vacation.endDate.date(), self.end_date.date())
                if vacation.endDate
                else vacation_start_date
            )
            date_string = " - ".join([
                vtcapp.datetostr(vacation_start_date),
                vtcapp.datetostr(vacation_end_date)
            ])
            time_string = self.get_taken_string(vacation_start_date, vacation_end_date)
            balance_string = self.get_balance_string(vacation_end_date)
            is_selected = (vacation.objid == selected_id)
            vacations.append(Vacation(date_string, time_string, balance_string, is_selected))

        return vacations
        
        
def show_vacation_balance(user, vacation=None):
    """
    Show a dialog with vacation periods.
    If a vacation is given, it is marked as bold in the dialog.
    The displayed period is the one with the vacation inside or - if no vacation 
    is given - for the current date.
    """
   
    day = vacation.evalocl("entryDate") if vacation else vtcapp.currentdatetime()

    start_date, end_date = get_relevant_period(user, day)
    if not start_date or not end_date:
        return 
    
    # Get all settings for the user in the specified period + the first period
    ocl_query = ("self-&gt;union(groups).orderedSettings-&gt;select(weeklyStdhours.notNull or vacationCarryover.notNull)"
                "-&gt;collect(s|s-&gt;select(entryDate&gt;={0})-&gt;select(entryDate&lt;={1})-&gt;orderdescending(entryDate)"
                "-&gt;union(s-&gt;select(entryDate&lt;={0})-&gt;last))"
                ).format(vtcapp.ocldate(start_date), vtcapp.ocldate(end_date))

    settings_list = user.evalocl(ocl_query) 
    period_end_date = end_date
    periods = []

    # We need to find all periods with different target time. 
    # If a to-date is given, this might be different from the settings given.
    while period_end_date.date() &gt; start_date.date():

        # Get the (in time) last setting before end_date
        setting = get_target_time_setting(user, period_end_date, settings_list)

        # We set the start date as period start.
        if setting:
            period_start_date = setting.entryDate
        else:
            # The very first entry for a setting is not included in the settings_list. 
            period_start_date = start_date
            setting = get_target_time_setting(user, start_date, settings_list)

        # We have to consider settings, that are lying completely inside another period.
        # In this case we divide into three periods:
        # before intersecting period, intersecting period, after intersecting period
        intersecting_setting = _get_intersecting_setting(period_start_date,
                                                        period_end_date,
                                                        settings_list)
        if intersecting_setting:
            period_start_date = vtcapp.incday(intersecting_setting.endDate, 1)

        # This is the very first period. We set the start date to the first of the
        # year/ entry date of user/ date of last manual vacation setting
        if period_start_date &lt; start_date:
            period_start_date = start_date

        # This is the setting with the end_date inside. If the end_date is not the period_end_date, we divide into two periods.
        # One from start_date until day and one from day+1 until end_date
        if period_end_date &gt; end_date and period_start_date &lt;= end_date:
            period_start_date = vtcapp.incday(end_date, 1)

        if not setting:
            break

        working_time_per_day, working_days_per_week = get_working_time_information(setting)

        vacation_period = VacationPeriod(user, 
                                        period_start_date,
                                        period_end_date,
                                        working_time_per_day,
                                        working_days_per_week                                        
                                        )
                                      
        # Update lists with current values
        periods.append(vacation_period)
        period_end_date = vtcapp.incday(period_start_date, -1)
    
    show_dialog(user, periods, vacation)
    return 


def _format_number(value):
    """
    Format days as integer with locale grouping.
    """

    if value is None:
        return ""
    return locale.format_string("%.2f", float(value), grouping=True)


def _get_days_string(days):
    """
    Format days as localized string.
    """

    if days == 1:
        label = vtcapp.translate(LABEL_DAY)
    else:
        label = vtcapp.translate(LABEL_DAYS)
    days_formatted = _format_number(days)
    return vtcapp.translate("{} {}").format(days_formatted, label)


def _get_days_string_from_minutes(minutes, minutes_per_day):
    """
    Convert minutes to days and format.
    """

    days = get_ratio(minutes, minutes_per_day)
    return _get_days_string(days)


def _get_hours_string(minutes):
    """
    Formats minutes as a localized hours string, e.g. '20:00 h'.
    """

    hours = vtcapp.formatminutes(int(minutes))
    return vtcapp.translate("{} {}").format(hours, vtcapp.translate(LABEL_HOURS))
                    

def show_dialog(user, periods, selected_vacation):
    """
    Renders and displays the paginated vacation balance dialog.
    Supports navigation and highlights a selected vacation if provided.
    """

    set_locale_by_language()

    if not periods:
        return

    # Determine overall period range
    start_period = periods[-1]
    end_period = periods[0]
    overall_start_date = start_period.start_date
    overall_end_date = end_period.end_date.date()

    selected_vacation_id = selected_vacation.objid if selected_vacation else None

    # Translated labels
    overall_start_date_str = vtcapp.datetostr(overall_start_date)
    overall_end_date_str = vtcapp.datetostr(overall_end_date)

    label_balance_brought_forward = vtcapp.translate("{} {}").format(
        vtcapp.translate("Vacation bfw. per"),
        overall_start_date_str
    )

    label_vacation_credit = vtcapp.translate("Vacation credit {} - {}").format(
        overall_start_date_str,
        overall_end_date_str
    )

    credit_explanation = vtcapp.translate(
        "The vacation credit is calculated pro rata on the basis of calendar days per period and multiplied by the annual entitlement. The number of hours of vacation days depends on the employment level."
    )

    # Totals
    total_vacation_credit = get_vacation_credit(user, overall_start_date, overall_end_date)

    total_vacation_days = sum(
        get_ratio(
            get_vacation_credit(user, period.start_date, period.end_date),
            period.working_time_per_day
        )
        for period in periods
    )

    is_only_one_period = (len(periods) == 1)

    # Prepare periods
    prepared_periods = []
    for period in reversed(periods):
        prepared_periods.append({
            "start": vtcapp.datetostr(period.start_date),
            "end": vtcapp.datetostr(period.end_date),
            "employment_level": period.get_employment_level_string(period.start_date),
            "avg_work_time": period.get_working_time_string(),
            "balance_start": period.get_balance_start_string(period.start_date),
            "taken": period.get_taken_string(period.start_date, period.end_date),
            "balance_end": period.get_balance_string(period.end_date),
            "credit": period.get_vacation_credit_string(period.start_date, period.end_date),
            "vacations": period.get_vacation_entries(selected_vacation_id)
        })

    # Build pages
    pages = []
    current_page = []
    current_line_count = 0

    for period_entry in prepared_periods:
        vacations = period_entry["vacations"]

        if not vacations:
            if current_line_count + 1 &gt; MAX_LINES:
                pages.append(current_page)
                current_page = []
                current_line_count = 0

            current_page.append({**period_entry, "vacations": []})
            current_line_count += 1
            continue

        vacation_index = 0
        while vacation_index &lt; len(vacations):
            remaining_space = MAX_LINES - current_line_count

            if remaining_space == 0:
                pages.append(current_page)
                current_page = []
                current_line_count = 0
                remaining_space = MAX_LINES

            vacation_chunk = vacations[vacation_index: vacation_index + remaining_space]

            current_page.append({
                **period_entry,
                "vacations": vacation_chunk
            })

            current_line_count += len(vacation_chunk)
            vacation_index += len(vacation_chunk)

    if current_page:
        pages.append(current_page)

    # Determine initial page containing the selected vacation
    initial_step = 0
    if selected_vacation_id:
        for page_index, page in enumerate(pages):
            if any(
                getattr(vacation_entry, "is_selected", False)
                for period_entry in page
                for vacation_entry in period_entry.get("vacations", [])
            ):
                initial_step = page_index
                break

    # Summary
    summary = {
        "balance_brought_forward": _get_hours_string(
            get_vacation_carryforwardbalance(user, overall_start_date)
        ),
        "total_days": _get_days_string(total_vacation_days),
        "total_hours": _get_hours_string(total_vacation_credit),
        "balance_brought_forward_days": _get_days_string_from_minutes(
            get_vacation_carryforwardbalance(user, overall_start_date),
            start_period.working_time_per_day
        )
    }

    dialog_definition = """
&lt;Dialog Title="{Translate 'Vacation balance'}" {% if show_credit_details %} Width="1250" {% else %}Width="1030" {% endif %} &gt;
    &lt;Group&gt;
        &lt;Group Orientation="Vertical" FlexWidth="7"&gt;
            &lt;TextBlock ShowLabel="True" LabelWidth="220" Label="{{label_balance_brought_forward}}" Text="{% if is_only_one_period %}{{summary.balance_brought_forward_days}} / {% endif %}{{summary.balance_brought_forward}}" ContentAlignment="Right" /&gt;
            &lt;TextBlock ShowLabel="True" LabelWidth="220" Label="{{label_vacation_credit}}" Text="{% if is_only_one_period %} {{summary.total_days}} / {% endif %}{{summary.total_hours}}" ContentAlignment="Right" /&gt;
        &lt;/Group &gt;
        &lt;TextBlock Visible="{{show_credit_details}}" Text="{{credit_explanation}}" FitMode="Wrap" VerticalAlignment="Bottom" Appearance="Info" FlexWidth="7" /&gt;
        &lt;Spacer Visible="{{not show_credit_details}}" FlexWidth="6" /&gt;
    &lt;/Group &gt;
    &lt;Button Name="ShowCreditDetailsButton" Text="{Translate 'Show details'}" Visible="{{not is_only_one_period and not show_credit_details}}" IsAccept="True" Command="{Binding CloseCommand}" Appearance="Input" Width="125" HorizontalAlignment="Left" /&gt;
    &lt;Button Name="HideCreditDetailsButton" Text="{Translate 'Hide details'}" Visible="{{not is_only_one_period and show_credit_details}}" IsAccept="True" Command="{Binding CloseCommand}" Appearance="Input" Width="125" HorizontalAlignment="Left" /&gt;
    &lt;Spacer Height="10" /&gt;
    &lt;Group&gt;
        &lt;TextBlock Text="{Translate 'Period'}" Appearance="Info" FlexWidth="2" /&gt;
        &lt;TextBlock Text="{Translate 'Level of capacity'}" Appearance="Info" FlexWidth="3" /&gt;
        &lt;TextBlock Visible="{{show_credit_details}}" Text="{Translate 'Vacation credit in period'}" Appearance="Info" ContentAlignment="Right" IsBold="True" FlexWidth="3" /&gt;
        &lt;TextBlock Text="{Translate 'Opening balance'}" Appearance="Info" ContentAlignment="Right" FlexWidth="2" /&gt;
        &lt;TextBlock Text="{Translate 'Vacation taken'}" Appearance="Info" ContentAlignment="Left" FlexWidth="2" /&gt;
        &lt;Spacer FlexWidth="2" /&gt;
        &lt;TextBlock Text="{Translate 'Remaining balance'}" Appearance="Info" ContentAlignment="Right" FlexWidth="2" /&gt;
    &lt;/Group&gt;
    {% for period in periods %}
        &lt;Group Separator="Always"&gt;
            &lt;TextBlock Text="{{period.start}} - {{period.end}}" Appearance="Info" FlexWidth="2" /&gt;
            &lt;TextBlock Text="{{period.employment_level}} {{period.avg_work_time}}" Appearance="Info" FlexWidth="3" /&gt;
            &lt;TextBlock Visible="{{show_credit_details}}" Text="{{period.credit}}" Appearance="Info" ContentAlignment="Right" IsBold="True" FlexWidth="3" /&gt;
            &lt;TextBlock Text="{{period.balance_start}}" Appearance="Info" ContentAlignment="Right" FlexWidth="2" /&gt;
        {% if period.vacations %}
            {% for vacation in period.vacations %}
                &lt;TextBlock Text="{{vacation.date_string}}" {% if vacation.is_selected %}IsBold="True"{% endif %} Appearance="Info" FlexWidth="2" /&gt;
                &lt;TextBlock Text="{{vacation.time_string}}" {% if vacation.is_selected %}IsBold="True"{% endif %} Appearance="Info" ContentAlignment="Right" FlexWidth="2" /&gt;
                &lt;TextBlock Text="{{vacation.balance_string}}" {% if vacation.is_selected %}IsBold="True"{% endif %} Appearance="Info" ContentAlignment="Right" FlexWidth="2" /&gt;
            &lt;/Group&gt;
            &lt;Group&gt;
                &lt;Spacer FlexWidth="2" /&gt;
                &lt;Spacer FlexWidth="3" /&gt;
                &lt;Spacer FlexWidth="2"/&gt;
                &lt;Spacer Visible="{{show_credit_details}}" FlexWidth="3" /&gt;
            {% endfor %}
            &lt;Spacer FlexWidth="2" /&gt;
            &lt;Spacer FlexWidth="2" /&gt;
            &lt;Spacer FlexWidth="2"/&gt;
        {% else %}
            &lt;Spacer FlexWidth="2" /&gt;
            &lt;Spacer FlexWidth="2" /&gt;
            &lt;TextBlock Text="{{period.balance_end}}" Appearance="Info" ContentAlignment="Right" FlexWidth="2" /&gt;
        {% endif %}
        &lt;/Group&gt;
    {% endfor %}
    {% if last_step %}
    &lt;Group Separator="Always" Visible="{{show_credit_details}}"&gt;
    &lt;TextBlock IsBold="True" Label="{{label_vacation_credit}}" LabelWidth="300" ShowLabel="True" IsLabelBold="True"
        Text="{{summary.total_days}} / {{summary.total_hours}}" Appearance="Info" ContentAlignment="Right" /&gt;
        &lt;Spacer /&gt;
    &lt;/Group&gt;
    {% endif %}
    &lt;Spacer Height="10" /&gt;
    &lt;Dialog.Buttons&gt;
        {% if not step == 0 %}
            &lt;Button Name="BackButton" Text="{Translate 'Back'}" IsAccept="True" Command="{Binding CloseCommand}" /&gt;
        {% endif %}
        {% if not last_step %}
            &lt;Button Name="NextButton" Text="{Translate 'Next'}" IsAccept="True" Command="{Binding CloseCommand}" /&gt;
        {% endif %}
        {% if total_pages &gt; 1 %}
            &lt;TextBlock Text="{{label_page}}" Width="100"/&gt;
        {% endif %}        
        &lt;Button Name="CloseButton" Text="{Translate 'Close'}" IsAccept="False" Command="{Binding CancelCommand}" HorizontalAlignment="Left" /&gt;
    &lt;/Dialog.Buttons&gt;
&lt;/Dialog&gt;
"""

    dialog_values = {}
    play_dialog = True
    step = initial_step
    show_credit_details = False

    while play_dialog:

        label_page = vtcapp.translate("{} {} {} {}").format(
            vtcapp.translate("Page"), 
            step + 1, 
            vtcapp.translate("of"), 
            len(pages)
        )

        dlg_rnd = vtcapp.rendertemplate(
            dialog_definition,
            periods=pages[step],
            summary=summary,
            label_balance_brought_forward=label_balance_brought_forward,
            label_vacation_credit=label_vacation_credit,
            is_only_one_period=is_only_one_period,
            step=step,
            last_step=(step == len(pages) - 1),
            show_credit_details=show_credit_details,
            credit_explanation=credit_explanation,
            label_page=label_page,
            total_pages=len(pages),
        )

        ok, values = vtcapp.showcustomdialog(dlg_rnd, dialog_values)
        if not ok:
            return

        if values.get("ShowCreditDetailsButton"):
            show_credit_details = True
            values.pop("ShowCreditDetailsButton", None)
            continue

        if values.get("HideCreditDetailsButton"):
            show_credit_details = False
            values.pop("HideCreditDetailsButton", None)
            continue

        go_forward = not values.get("BackButton", False)

        values.pop("BackButton", None)
        values.pop("NextButton", None)

        dialog_values[step] = values

        step = step + 1 if go_forward else step - 1

        if step &lt; 0 or step &gt;= len(pages):
            play_dialog = False


def _get_intersecting_setting(start_date, end_date, settingchange_list):
    """
    Checks, if we have intersecting settings (completely inside periode).
    """

    ocl_query = "self-&gt;select(entryDate&gt;={0})-&gt;select(endDate.notNull and (endDate&lt;{1}))" \
        "-&gt;orderby(endDate)-&gt;last" \
        .format(vtcapp.ocldate(start_date), vtcapp.ocldate(end_date))

    return settingchange_list.evalocl(ocl_query)


def get_ratio(total, average):
    """
    Calculates how often `average` fits into `total`.
    Returns 0 if `average` is None or 0.

    This is used especially to calculate the number of days
    for given minutes and working time per day.
    """

    if not average:
        return 0

    return total / float(average)


def get_working_time_information(setting):
    """
    Returns:
    - the maximum working time per day
    - the calculated number of working days

    A full working day is defined as a day with the maximum working time.
    Days with fewer hours are counted proportionally.
    """

    working_times = [
        float(working_time)
        for working_time in setting.evalocl("stdhoursPerWeekday-&gt;stringtolist")
        if working_time not in ("", "0", None)
    ]

    if not working_times:
        return 0, 0

    maximum_working_time = max(working_times)

    calculated_working_days = sum(
        working_time / maximum_working_time
        for working_time in working_times
    )

    return maximum_working_time, calculated_working_days


def get_working_time_per_day(setting):
    """
    Returns the maximum working time per day for the current setting.
    """

    maximum_working_time, _calculated_working_days = (
        get_working_time_information(setting)
    )

    return maximum_working_time


class BaseAbsenceRenderer:
    """
    Most values are shown readonly.
    """

    is_readonly = True
    value_type = 'float'

    def _has_values(self, rowobj):
        """
        Returns True if the row has a valid type and a non-null user of type User.
        """

        # We need a user, a date and a carryover
        if self.evalocl("(type &lt;&gt; 1) or user.isnull", rowobj):
            return False

        # User cannot be a user group
        if not self.evalocl("user.oclistypeof(User)", rowobj):
            return False

        return True


class AbsenceInDaysRenderer(BaseAbsenceRenderer):
    """
    Displays the absence in days on absence entries.
    """

    def get_value(self, rowobj, expression, subscriber):
        """
        Calculates and returns the absence duration in days for the given period.
        """

        # Ensure the necessary values are set
        if not self._has_values(rowobj):
            return

        # Store variables
        user = self.evalocl("user", rowobj)
        start_date = self.evalocl("entryDate", rowobj)
        end_date = self.evalocl("if endDate.notnull then endDate else entryDate endif", rowobj)

        # Subscribe to all members
        ocl_absences_subscriber = "self-&gt;collect(typelink.art.asstring+"\
            "minutesabsent.asstring+entryDate.asstring+endDate.asstring" \
            "+user.asstring+active.asstring)"
        self.evalocl(ocl_absences_subscriber, rowobj)

        # Loop over every day to count the days
        day = start_date
        number_of_vacation_days = 0

        while day &lt;= end_date:
            minutes_taken = get_vacation_taken(user, day, day)
            setting = get_target_time_setting(user, day)
            working_time_per_day = get_working_time_per_day(setting)
            if minutes_taken and working_time_per_day:
                number_of_vacation_days += get_ratio(minutes_taken, working_time_per_day)
            day = vtcapp.incday(day, 1)

        return number_of_vacation_days


class VacationBalanceInDaysRenderer(BaseAbsenceRenderer):
    """
    Displays the vacation balance in days on absence entries.
    """

    def get_value(self, rowobj, expression, subscriber):
        """
        Calculates and returns the vacation balance in days for the relevant date.
        """

        # Ensure the necessary values are set
        if not self._has_values(rowobj):
            return

        # Store variables
        user = self.evalocl("user", rowobj)
        day = self.evalocl("if endDate.notnull then endDate else entryDate endif", rowobj)
        
        setting = get_target_time_setting(user, day)
        working_time_per_day = get_working_time_per_day(setting)
        
        # Subscribe to all absences
        ocl_absences_subscriber = "absences-&gt;reject(entryDate&gt;{})-&gt;collect(typelink.art.asstring+"\
            "minutesabsent.asstring+entryDate.asstring+endDate.asstring" \
            "+user.asstring+active.asstring)"
        self.evalocl(ocl_absences_subscriber.format(vtcapp.ocldate(day)), user)

        return get_ratio(get_vacation_balance(user, day), working_time_per_day)


class VacationBalanceDetailsRenderer(BaseAbsenceRenderer):
    """
    Displays a button to open the vacation balance detail dialog on absence entries.
    """

    display_type = "button"

    def get_buttoniconid(self, rowobj, expression, subscriber):
        """
        Returns the button icon identifier if the row has valid values, otherwise no button.
        """

        # Ensure the necessary values are set
        if not self._has_values(rowobj):
            return NO_BUTTON

        return BUTTON_INFO

    def button_clicked(self, rowobj, expression):
        """
        Opens the vacation balance detail dialog for the given row.
        """
        
        # Ensure the necessary values are set
        if not self._has_values(rowobj):
            return
        user = self.evalocl("user", rowobj)
        show_vacation_balance(user, rowobj)
        return
        

class VacationSettingsInDaysRenderer(BaseAbsenceRenderer):
    """
    Allows the user to set and view the vacation carryover in days on the settings.
    """

    def _has_values(self, rowobj, ocl_prefix=""):
        """
        Returns True if the row has a non-null entry date 
        and user not of type user group, considering an optional OCL prefix.
        """
        
        # We need a user, a date and a carryover
        if self.evalocl(ocl_prefix + "entryDate.isnull or user.isnull", rowobj):
            return False

        # User cannot be a user group
        if not self.evalocl("user.oclistypeof(User)", rowobj):
            return False

        return True

    def get_value(self, rowobj, expression, subscriber):
        """
        Calculates and returns the vacation carryover in days based 
        on working time if all required values are present.
        """

        # Ensure the necessary values are set
        if not self._has_values(rowobj, ocl_prefix="vacationCarryover.isnull or "):
            return

        # Store variables
        day = self.evalocl("entryDate", rowobj)
        vacation_carryforward = self.evalocl("vacationCarryover", rowobj)
        user = self.evalocl("user", rowobj)

        # Calc and return values
        setting = get_target_time_setting(user, day)
        working_time = get_working_time_per_day(setting)

        return get_ratio(vacation_carryforward, working_time)

    def set_value(self, rowobj, expression, value):
        """
        Sets the vacation carryover in minutes based on 
        the given value and working time if inputs are valid.
        """
        
        # Ensure the necessary values are set
        if not self._has_values(rowobj):
            return

        if value is None:
            rowobj.vacationCarryover = None
            return

        # Store variables
        day = self.evalocl("entryDate", rowobj)
        user = self.evalocl("user", rowobj)

        # Calc and return values
        setting = get_target_time_setting(user, day)
        working_time = get_working_time_per_day(setting)

        # We do not accept non-integer values
        try:
            vacation_minutes = int(value) * int(working_time)
        except Exception:
            return

        # set_value
        rowobj.vacationCarryover = vacation_minutes

    def get_is_readonly(self, rowobj, expression, subscriber):
        """
        Returns True if the field should be read-only because the user is a user group.
        """

        return self.evalocl("not user.oclistypeof(User)", rowobj)
    
    
def get_vacation_credit(user, start_date, end_date):
    """
    Returns the vacation entitlement during a period.
    """

    return _base_get_generic_function(user, "getVacationCredit", start_date, end_date)


def get_employment_level(user, day):
    """
    Returns the employment level of the user on a certain day.
    """

    return _base_get_generic_function(user, "getEmploymentLevel", day)


def get_vacation_taken(user, start_date, end_date=None):
    """
    Returns the vacation taken in a period.
    """

    end_date = end_date or start_date
    return _base_get_generic_function(user, "getVacationTaken", start_date, end_date)


def get_vacation_balance_start(user, day):
    """
    Returns the vacation balance at the start of a period.
    This means, the vacation balance plus vacation taken on the first day
    """
    
    return get_vacation_balance(user, day) + get_vacation_taken(user, day, day)
    

def get_vacation_balance(user, day):
    """
    Returns the vacation balance on the day given.
    """

    return _base_get_generic_function(user, "getVacationBalance", day)


def get_vacation_carryforwardbalance(user, day):
    """
    Returns the vacation carryover on the given day.
    """

    return _base_get_generic_function(user, "getvacationCarryover", day)


def get_vacation_end(user, day):
    """
    Returns the next end of a vacation period.
    """
    
    return _base_get_generic_function(user, "getvacationEnd", day)
    
    
def get_target_time_setting(user, day, settingchange_list=None):
    """
    Returns the last relevant setting before the given date and user.
    """

    if not settingchange_list:
        ocl_query = "self-&gt;union(groups).orderedSettings"
        baseobject = user
    else:
        ocl_query = "self"
        baseobject = settingchange_list

    ocl_query += "-&gt;select(weeklyStdhours.notNull)-&gt;select(entryDate&lt;={0})-&gt;select(endDate.isNull or (endDate&gt;={0}))" \
        "-&gt;orderby(entryDate)-&gt;last".format(vtcapp.ocldate(day))

    return baseobject.evalocl(ocl_query)


def _base_get_generic_function(user, oclfunction, start_date, end_date=None):
    """
    Middleware to call generic ocl functions with one or two dates.
    """

    if not user or not start_date or not oclfunction:
        return None

    if end_date is not None:
        ocl_query = "self-&gt;{}({}, {})".format(oclfunction,
                                              vtcapp.ocldate(start_date),
                                              vtcapp.ocldate(end_date))
    else:
        ocl_query = "self-&gt;{}({})".format(oclfunction,
                                          vtcapp.ocldate(start_date))

    return user.evalocl(ocl_query)
    
    
def get_relevant_period(user, day):
    """
    Depending on the day and the users entry and exit dates, 
    we return the interval with the corresponding vacation period.
    """

    # Do not show, if day is before entry date.
    if user.joiningDate &gt; day:
        return None, None

    # If day is after the exit date, update date to exit date.
    if user.exitDate and user.exitDate &lt;= day:
        day = user.exitDate

    # Default is vacation period, normally the full year    
    end_date = get_vacation_end(user, day)
    # As end_date gives us the next end of a vacation period after the date given
    # we can calculate the start date 
    start_date = vtcapp.incday(vtcapp.incyear(end_date, -1), 1)
    # Make sure whole period is in the intervall between entry and exit
    if start_date.date() &lt; user.joiningDate.date():
        start_date = user.joiningDate

    if user.exitDate and user.exitDate.date() &lt;= end_date.date():
        end_date = user.exitDate

    
    return start_date, end_date
    </member>
      <member name="eventType">None</member>
      <member name="objectScript">false</member>
      <member name="extendedRights">false</member>
      <member name="selectedObjectsScript">false</member>
      <member name="platform">Python3</member>
    </object>
    <object class="Script" alias="Script_Showvacationbalance6">
      <member name="entryID">ScriptShowVacationBalance</member>
      <member name="designation">Show vacation balance</member>
      <member name="classes">User</member>
      <member name="containerScript">false</member>
      <member name="condExpression">self-&gt;size=1</member>
      <member name="scriptText"># coding: windows-1252
#
# Designation: Show vacation balance
# Show in Vertec menus: Y
# Classes: User
# Display condition: self-&gt;size=1
# Event script for:
# Event classes:
# Event fields:
# Extended rights:
  
# Shows a dialog with vacation balance in days
# to be called on a user
  
# 2026-05-12 UHE: Initial version

import module_vacation_balance_in_days
module_vacation_balance_in_days.show_vacation_balance(argobject)
</member>
      <member name="eventType">None</member>
      <member name="objectScript">false</member>
      <member name="extendedRights">false</member>
      <member name="selectedObjectsScript">true</member>
      <member name="platform">Python3</member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Absences(days)12">
      <member name="entryID">TranslationAbsencesDays</member>
      <member name="text">
        <mltext language="DE">Abwesenheiten (Tage)</mltext>
        <mltext language="EN">Absences (days)</mltext>
        <mltext language="FR">Absences (jours)</mltext>
        <mltext language="IT">Assenze (giorni)</mltext>
        <mltext language="DD">Abwesenheiten (Tage)</mltext>
        <mltext language="NV">Absences (days)</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Openingbalance6">
      <member name="entryID">TranslationOpeningBalance</member>
      <member name="text">
        <mltext language="DE">Saldo zum Start</mltext>
        <mltext language="EN">Opening balance</mltext>
        <mltext language="FR">Solde d’ouverture</mltext>
        <mltext language="IT">Saldo di apertura</mltext>
        <mltext language="DD">Saldo zum Start</mltext>
        <mltext language="NV">Opening balance</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:({}perdayon{}days)16">
      <member name="entryID">TranslationPerDayOnDaysWithBrackets</member>
      <member name="text">
        <mltext language="DE">({} pro Tag an {} Tagen)</mltext>
        <mltext language="EN">({} per day on {} days)</mltext>
        <mltext language="FR">({} par jour pendant {} jours)</mltext>
        <mltext language="IT">({} al giorno su {} giorni)</mltext>
        <mltext language="DD">({} pro Tag an {} Tagen)</mltext>
        <mltext language="NV">({} per day on {} days)</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:({}perday)10">
      <member name="entryID">TranslationPerDayWithBrackets</member>
      <member name="text">
        <mltext language="DE">({} pro Tag)</mltext>
        <mltext language="EN">({} per day)</mltext>
        <mltext language="FR">({} par jour)</mltext>
        <mltext language="IT">({} al giorno)</mltext>
        <mltext language="DD">({} pro Tag)</mltext>
        <mltext language="NV">({} per day)</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Prerequisites(days)13">
      <member name="entryID">TranslationPrerequisitesDays</member>
      <member name="text">
        <mltext language="DE">Vorgaben (Tage)</mltext>
        <mltext language="EN">Settings (days)</mltext>
        <mltext language="FR">Données obligatoires (jours)</mltext>
        <mltext language="IT">Dati requisiti (giorni)</mltext>
        <mltext language="DD">Vorgaben (Tage)</mltext>
        <mltext language="NV">Prerequisites (days)</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Remainingbalance9">
      <member name="entryID">TranslationRemainingBalance</member>
      <member name="text">
        <mltext language="DE">Verbleibender Saldo</mltext>
        <mltext language="EN">Remaining balance</mltext>
        <mltext language="FR">Solde restant</mltext>
        <mltext language="IT">Saldo residuo</mltext>
        <mltext language="DD">Verbleibender Saldo</mltext>
        <mltext language="NV">Remaining balance</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Showvacationbalance7">
      <member name="entryID">TranslationShowVacationBalance</member>
      <member name="text">
        <mltext language="DE">Feriensaldo anzeigen</mltext>
        <mltext language="EN">Show vacation balance</mltext>
        <mltext language="FR">Afficher le solde des vacances</mltext>
        <mltext language="IT">Mostra il saldo delle vacanze</mltext>
        <mltext language="DD">Urlaubssaldo anzeigen</mltext>
        <mltext language="NV">Show vacation balance</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Thevacationbalanceiscalculatedprorataonthebasisofcalendardaysperperiodandmultipliedbytheannualentitlement.Thenumberofhoursofvacationdaysdependsontheemploymentlevel.5">
      <member name="entryID">TranslationTheVacationBalanceIsCalculatedProRataOnTheBasisOfCalendarDaysPerPeriodAndMultipliedByTheAnnualEntitlementTheNumberOfHoursOfVacationDaysDependsOnTheEmploymentLevel</member>
      <member name="text">
        <mltext language="DE">Der Ferienanspruch wird pro Periode anteilig nach Kalendertagen berechnet und mit dem Jahresanspruch multipliziert. Die Stundenanzahl der Ferientage richtet sich nach dem Beschäftigungsgrad.</mltext>
        <mltext language="EN">The vacation credit is calculated pro rata on the basis of calendar days per period and multiplied by the annual entitlement. The number of hours of vacation days depends on the employment level.</mltext>
        <mltext language="FR">Le crédit de vacances est calculé au prorata des jours civils par période et multiplié par le droit annuel. Le nombre d’heures de vacances dépend du Taux d’emploi.</mltext>
        <mltext language="IT">L’avere per ferie sarà calcolato per periodo in proporzione ai giorni di calendario e moltiplicato per il diritto annuo. Il numero di ore di ferie dipende dal Tasso di occupazione.</mltext>
        <mltext language="DD">Der Urlaubsanspruch wird pro Periode anteilig nach Kalendertagen berechnet und mit dem Jahresanspruch multipliziert. Die Stundenanzahl der Urlaubstage richtet sich nach dem Beschäftigungsgrad.</mltext>
        <mltext language="NV">The vacation credit is calculated pro rata on the basis of calendar days per period and multiplied by the annual entitlement. The number of hours of vacation days depends on the employment level.</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Vacationbfw.indays11">
      <member name="entryID">TranslationVacationBfwInDays</member>
      <member name="text">
        <mltext language="DE">Ferienvortrag in Tagen</mltext>
        <mltext language="EN">Vacation bfw. in days</mltext>
        <mltext language="FR">Rep. vacances en jours</mltext>
        <mltext language="IT">Riporto vacanze in giorni</mltext>
        <mltext language="DD">Urlaubsvortrag in Tagen</mltext>
        <mltext language="NV">Vacation bfw. in days</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Vacationcredit{}-{}14">
      <member name="entryID">TranslationVacationCreditFromToPeriod</member>
      <member name="text">
        <mltext language="DE">Ferienanspruch {} – {}</mltext>
        <mltext language="EN">Vacation credit {} – {}</mltext>
        <mltext language="FR">Crédit vacances {} – {}</mltext>
        <mltext language="IT">Credito vacanza {} – {}</mltext>
        <mltext language="DD">Urlaubsanspruch {} – {}</mltext>
        <mltext language="NV">Vacation credit {} - {}</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Vacationcreditinperiod15">
      <member name="entryID">TranslationVacationCreditInPeriod</member>
      <member name="text">
        <mltext language="DE">Ferienanspruch in Periode</mltext>
        <mltext language="EN">Vacation credit in period</mltext>
        <mltext language="FR">Crédit vacances en période</mltext>
        <mltext language="IT">Credito per ferie in periodo</mltext>
        <mltext language="DD">Urlaubsanspruch in Periode</mltext>
        <mltext language="NV">Vacation credit in period</mltext>
      </member>
    </object>
    <object class="TranslationItem" alias="TranslationItem_Übersetzung:Vacationdays8">
      <member name="entryID">TranslationVacationDays</member>
      <member name="text">
        <mltext language="DE">Ferientage</mltext>
        <mltext language="EN">Vacation days</mltext>
        <mltext language="FR">Jours de congé</mltext>
        <mltext language="IT">Giorni di vacanza</mltext>
        <mltext language="DD">Urlaubstage</mltext>
        <mltext language="NV">Vacation days</mltext>
      </member>
    </object>
    <object class="WrapperLinkType" alias="WrapperLinkType_Bearbeiter-Abwesenheiten11">
      <member name="entryID">LnkUserAbsencesWithDays</member>
      <member name="active">true</member>
      <member name="allowLinks">true</member>
      <member name="roleA">
        <object class="WrapperLinkRole" alias="WrapperLinkRole0">
          <member name="alwaysShowContainer">true</member>
          <member name="isSingle">false</member>
          <member name="orderIdx">200</member>
          <member name="canCreateTarget">true</member>
          <member name="deleteTarget">false</member>
          <member name="linkmember">abwesenheiten</member>
          <member name="targetClassName">Absence</member>
          <member name="persistentIcon">62</member>
          <member name="containerClassName">AbwesenheitenContainer</member>
          <member name="designation">Absences (days)</member>
          <member name="showContainer">true</member>
          <member name="isExpandable">true</member>
          <member name="openInNavigationView">CurrentView</member>
          <member name="linktypeA">
            <reference-object alias="WrapperLinkType_Bearbeiter-Abwesenheiten11" />
          </member>
          <member name="gridDefs">
            <object class="GridDef" alias="GridDef0">
              <member name="rowHeight">17</member>
              <member name="ghostrow">true</member>
              <member name="editing">true</member>
              <member name="gridcols">
                <object class="GridColDef" alias="GridColDef0">
                  <member name="alignment">0</member>
                  <member name="orderIdx">0</member>
                  <member name="controlName">cmbdatepicker</member>
                  <member name="totals">false</member>
                  <member name="expression">entryDate</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">1</member>
                  <member name="title">
                    <mltext language="NV">Date</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">75</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef0" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef1">
                  <member name="alignment">0</member>
                  <member name="orderIdx">1</member>
                  <member name="controlName">cmbdatepicker</member>
                  <member name="totals">false</member>
                  <member name="expression">enddate</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">To date</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">75</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef0" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef2">
                  <member name="alignment">0</member>
                  <member name="orderIdx">2</member>
                  <member name="controlName">cmbAbwesenheitsTyp</member>
                  <member name="totals">false</member>
                  <member name="expression">typelink</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Type</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">80</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef0" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef3">
                  <member name="alignment">0</member>
                  <member name="orderIdx">3</member>
                  <member name="totals">false</member>
                  <member name="expression">description</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Description</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">118</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef0" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef4">
                  <member name="alignment">1</member>
                  <member name="orderIdx">4</member>
                  <member name="rendererName">dbmTIM.MinuteRenderer</member>
                  <member name="totals">false</member>
                  <member name="expression">minutesabsent</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Hours</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">50</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef0" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef5">
                  <member name="alignment">1</member>
                  <member name="orderIdx">5</member>
                  <member name="rendererName">module_vacation_balance_in_days.AbsenceInDaysRenderer</member>
                  <member name="totals">false</member>
                  <member name="expression">''</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Vacation days</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">80</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef0" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef6">
                  <member name="alignment">1</member>
                  <member name="orderIdx">6</member>
                  <member name="rendererName">module_vacation_balance_in_days.VacationBalanceInDaysRenderer</member>
                  <member name="totals">false</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Remaining balance</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">100</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef0" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef7">
                  <member name="alignment">2</member>
                  <member name="orderIdx">8</member>
                  <member name="rendererName">module_vacation_balance_in_days.VacationBalanceDetailsRenderer</member>
                  <member name="totals">false</member>
                  <member name="expression">''</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="tabstop">true</member>
                  <member name="width">22</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef0" />
                  </member>
                </object>
              </member>
              <member name="linkrole">
                <reference-object alias="WrapperLinkRole0" />
              </member>
            </object>
          </member>
          <member name="expression">abwesenheiten</member>
          <member name="defaultFiltered">false</member>
        </object>
      </member>
      <member name="roleB">
        <object class="WrapperLinkRole" alias="WrapperLinkRole1">
          <member name="alwaysShowContainer">false</member>
          <member name="isSingle">false</member>
          <member name="orderIdx">0</member>
          <member name="canCreateTarget">false</member>
          <member name="deleteTarget">false</member>
          <member name="targetClassName">User</member>
          <member name="persistentIcon">-1</member>
          <member name="containerClassName">WrapperLinkContainer</member>
          <member name="designation">Processor</member>
          <member name="showContainer">false</member>
          <member name="isExpandable">true</member>
          <member name="openInNavigationView">CurrentView</member>
          <member name="linktypeB">
            <reference-object alias="WrapperLinkType_Bearbeiter-Abwesenheiten11" />
          </member>
          <member name="expression">user</member>
          <member name="defaultFiltered">false</member>
        </object>
      </member>
    </object>
    <object class="WrapperLinkType" alias="WrapperLinkType_Bearbeiter-Vorgaben11">
      <member name="entryID">LnkUserPrerequisitesDays</member>
      <member name="active">true</member>
      <member name="allowLinks">false</member>
      <member name="roleA">
        <object class="WrapperLinkRole" alias="WrapperLinkRole2">
          <member name="alwaysShowContainer">false</member>
          <member name="isSingle">false</member>
          <member name="orderIdx">770</member>
          <member name="canCreateTarget">true</member>
          <member name="deleteTarget">false</member>
          <member name="linkmember">settings</member>
          <member name="targetClassName">UserSetting</member>
          <member name="persistentIcon">100</member>
          <member name="containerClassName">WrapperLinkContainer</member>
          <member name="designation">Prerequisites (days)</member>
          <member name="showContainer">true</member>
          <member name="isExpandable">true</member>
          <member name="openInNavigationView">CurrentView</member>
          <member name="linktypeA">
            <reference-object alias="WrapperLinkType_Bearbeiter-Vorgaben11" />
          </member>
          <member name="gridDefs">
            <object class="GridDef" alias="GridDef1">
              <member name="rowHeight">17</member>
              <member name="ghostrow">true</member>
              <member name="editing">true</member>
              <member name="gridcols">
                <object class="GridColDef" alias="GridColDef8">
                  <member name="alignment">0</member>
                  <member name="orderIdx">0</member>
                  <member name="rendererName">rndUserSettingDate</member>
                  <member name="totals">false</member>
                  <member name="expression">entryDate</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Date</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">70</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef9">
                  <member name="alignment">1</member>
                  <member name="orderIdx">10</member>
                  <member name="rendererName">rndUserSettingMinute</member>
                  <member name="totals">false</member>
                  <member name="expression">stdhours</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Std hours</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">80</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef10">
                  <member name="alignment">1</member>
                  <member name="orderIdx">11</member>
                  <member name="rendererName">rndUserSettingMinute</member>
                  <member name="totals">false</member>
                  <member name="expression">vacationAbsolute</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Vacation abs.</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">80</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef11">
                  <member name="alignment">0</member>
                  <member name="orderIdx">12</member>
                  <member name="rendererName">rndUserSettingBoolean</member>
                  <member name="totals">false</member>
                  <member name="expression">ismonth</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Monthly</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">60</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef12">
                  <member name="alignment">0</member>
                  <member name="orderIdx">13</member>
                  <member name="rendererName">rndUserSettingDate</member>
                  <member name="totals">false</member>
                  <member name="expression">endDate</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">To date</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">70</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef13">
                  <member name="alignment">0</member>
                  <member name="orderIdx">14</member>
                  <member name="color">clgreen</member>
                  <member name="totals">false</member>
                  <member name="expression">''</member>
                  <member name="readOnly">true</member>
                  <member name="sortOrder">0</member>
                  <member name="tabstop">false</member>
                  <member name="width">3</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef14">
                  <member name="alignment">1</member>
                  <member name="orderIdx">15</member>
                  <member name="rendererName">rndUserSettingCurrency</member>
                  <member name="totals">false</member>
                  <member name="expression">overheads</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">General costs</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">80</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef15">
                  <member name="alignment">1</member>
                  <member name="orderIdx">16</member>
                  <member name="rendererName">rndUserSettingCurrency</member>
                  <member name="totals">false</member>
                  <member name="expression">salary</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Salary</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">80</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef16">
                  <member name="alignment">0</member>
                  <member name="orderIdx">17</member>
                  <member name="rendererName">rndUserSettingRemarks</member>
                  <member name="totals">false</member>
                  <member name="expression">remarks</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Remark</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">200</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef17">
                  <member name="alignment">1</member>
                  <member name="orderIdx">3</member>
                  <member name="controlName">edtWeekTime</member>
                  <member name="rendererName">rndUserSettingMinute</member>
                  <member name="totals">false</member>
                  <member name="expression">weeklyStdhours</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Weekly std. hours</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">90</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef18">
                  <member name="alignment">1</member>
                  <member name="orderIdx">4</member>
                  <member name="rendererName">rndUserSettingMinute</member>
                  <member name="totals">false</member>
                  <member name="expression">vacation</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Vacation 100%</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">80</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef19">
                  <member name="alignment">0</member>
                  <member name="orderIdx">5</member>
                  <member name="color">clgreen</member>
                  <member name="totals">false</member>
                  <member name="expression">''</member>
                  <member name="readOnly">true</member>
                  <member name="sortOrder">0</member>
                  <member name="tabstop">false</member>
                  <member name="width">3</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef20">
                  <member name="alignment">1</member>
                  <member name="orderIdx">6</member>
                  <member name="rendererName">rndUserSettingMinute</member>
                  <member name="totals">false</member>
                  <member name="expression">overtimeCarryover</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Overtime bfw.</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">80</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef21">
                  <member name="alignment">1</member>
                  <member name="orderIdx">7</member>
                  <member name="rendererName">rndUserSettingMinute</member>
                  <member name="totals">false</member>
                  <member name="expression">vacationCarryover</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Vacation bfw.</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">80</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef22">
                  <member name="alignment">1</member>
                  <member name="orderIdx">8</member>
                  <member name="color">if user.oclIsTypeOf(User) then 'cltransparent' else 'clgray' endif</member>
                  <member name="rendererName">module_vacation_balance_in_days.VacationSettingsInDaysRenderer</member>
                  <member name="totals">false</member>
                  <member name="readOnly">false</member>
                  <member name="sortOrder">0</member>
                  <member name="title">
                    <mltext language="NV">Vacation bfw. in days</mltext>
                  </member>
                  <member name="tabstop">true</member>
                  <member name="width">115</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
                <object class="GridColDef" alias="GridColDef23">
                  <member name="alignment">0</member>
                  <member name="orderIdx">9</member>
                  <member name="color">clgreen</member>
                  <member name="totals">false</member>
                  <member name="expression">''</member>
                  <member name="readOnly">true</member>
                  <member name="sortOrder">0</member>
                  <member name="tabstop">false</member>
                  <member name="width">3</member>
                  <member name="isFixed">false</member>
                  <member name="isDynamic">false</member>
                  <member name="enableSpellCheck">false</member>
                  <member name="griddef">
                    <reference-object alias="GridDef1" />
                  </member>
                </object>
              </member>
              <member name="linkrole">
                <reference-object alias="WrapperLinkRole2" />
              </member>
            </object>
          </member>
          <member name="expression">orderedSettings</member>
          <member name="defaultFiltered">false</member>
        </object>
      </member>
      <member name="roleB">
        <object class="WrapperLinkRole" alias="WrapperLinkRole3">
          <member name="alwaysShowContainer">false</member>
          <member name="isSingle">false</member>
          <member name="orderIdx">0</member>
          <member name="canCreateTarget">false</member>
          <member name="deleteTarget">false</member>
          <member name="targetClassName">User</member>
          <member name="persistentIcon">-1</member>
          <member name="containerClassName">WrapperLinkContainer</member>
          <member name="designation">Processor</member>
          <member name="showContainer">false</member>
          <member name="isExpandable">true</member>
          <member name="openInNavigationView">CurrentView</member>
          <member name="linktypeB">
            <reference-object alias="WrapperLinkType_Bearbeiter-Vorgaben11" />
          </member>
          <member name="expression">user</member>
          <member name="defaultFiltered">false</member>
        </object>
      </member>
    </object>
  </objects>
  <settings />
</configset>