6.7.0.1Timsession.allinstances->first.licproduct=2FolderUsersUsers5210falsetruetruefalsefalsetruefalseOrdnerAbwesenheitstypenAbsence types63135falsetrue17falsetrue00falsecodefalse0Codetrue84falsefalse01falsebeschriebfalse0Designationtrue200falsefalse010zusatzfeld('abs_ColourPlannedAbsence')false''false0true25falsefalse02falseartfalse0Kindtrue74falsefalse03falsepriorityfalse0Prioritytrue47falsefalse24rndBooleanfalseaktivfalse0Activetrue40falsefalse05falsezusatzfeld('abs_ColourWholeDayAbsence')false0Colour whole day absencetrue155falsefalse06zusatzfeld('abs_ColourWholeDayAbsence')false''false0true25falsefalse07falsezusatzfeld('abs_ColourAbsenceByHours')false0Colour absence by hourstrue155falsefalse08zusatzfeld('abs_ColourAbsenceByHours')false''false0true25falsefalse09falsezusatzfeld('abs_ColourPlannedAbsence')false0Colour planned absencetrue155falsefalseAbwesenheitsTypabwesenheitstypScript_moduleAbsencesmodule_absencesfalse# coding: windows-1252
#
#---Bezeichnung: module_absences
# Klassen:
# ObjectScript:
# ContainerScript:
# EventType:
#---Contains functions and renderers for the list overview absences
import locale
import vtcapp
from datetime import date
months = ('January', 'February', 'March', 'April', 'May_cs', 'June', 'July', 'August', 'September', 'October', 'November', 'December')
DATE_NOT_VALID = "-"
COLOUR_DATE_NOT_VALID = "cldarkgray"
WEEKEND = "Weekend / No std hours"
COLOUR_WEEKEND = "cllightgray"
NO_TEXT =""
NO_COLOUR = ""
NO_ICON = ""
ICON_OPEN = "open_in_new"
ICON_INFO = "info_outline"
DEFAULT_COLOUR = ('clMediumlightGreen', 'clMediumlightGreen', 'clyellow')
def month_diff(d1, d2):
diff = (12 * d1.year + d1.month) - (12 * d2.year + d2.month) + 1
return diff
def get_invalid_dates(self):
invalid_dates_list = []
for month_index in range(0, self.number_of_months):
for day_index in range(0,31):
day = day_index + 1
month_date = self.months[month_index].month_date
month = month_date.month
year = month_date.year
try:
date(int(year), int(month), int(day))
except:
# if there is no date for the given data (i.e. 30.02. or 31.04.) we return -
invalid_dates_list.append((month_index, day_index))
return invalid_dates_list
class MonthData:
def __init__(self, month_number, month_date):
self.month = month_number
self.month_date = month_date
class Controller():
def initialize(self, subscriber):
locale.getdefaultlocale()
self.months = {}
self.from_date = self.evalocl("varFromDate")
self.to_date = self.evalocl("varToDate")
if self.from_date > self.to_date:
self.to_date = self.from_date
self.start_month_date = vtcapp.firstdayofmonth(self.from_date)
self.end_month_date = vtcapp.lastdayofmonth(self.to_date)
self.number_of_months = month_diff(self.to_date, self.from_date)
for month_number in range(0, self.number_of_months):
month_date = vtcapp.incmonth(self.start_month_date, month_number)
data = MonthData(month_number, month_date)
self.months[month_number] = data
# list of users, is loaded here via self.evalocl to ensure subscriptions
self.users = self.evalocl("eintraege.oclastype(Projektbearbeiter)", self.context)
# if there are no users in the list (when the folder is opened first without clicking the search-button) we return here to load faster
if not self.users:
return
# there are columns that are no dates in the list, i.e. 30.2. or 31.4.
# these days should be displayed as DATE_NOT_VALID
# we create a list with these columns here to mark the items later
self.invalid_dates_list = get_invalid_dates(self)
# we need system context for sql request here
with vtcapp.SystemContext():
absence_types = vtcapp.getwithsql("Abwesenheitstyp", "", "objid")
# we store all absence types in a dictionary for performance reasons
obj_id_list = absence_types.evalocl("objid")
description_list = absence_types.evalocl("beschrieb")
colour_whole_day_absence_list = absence_types.evalocl("zusatzfeld('abs_ColourWholeDayAbsence')->collect(c| if c.asstring <> '' then c else '{}' endif)".format(DEFAULT_COLOUR[0]))
colour_hour_absence_list = absence_types.evalocl("zusatzfeld('abs_ColourAbsenceByHours')->collect(c| if c.asstring <> '' then c else '{}' endif)".format(DEFAULT_COLOUR[1]))
colour_planned_absence_list = absence_types.evalocl("zusatzfeld('abs_ColourPlannedAbsence')->collect(c| if c.asstring <> '' then c else '{}' endif)".format(DEFAULT_COLOUR[2]))
all_data_list = zip(description_list, colour_whole_day_absence_list, colour_hour_absence_list, colour_planned_absence_list)
absence_types_list = zip(obj_id_list, all_data_list)
absence_types_dict = dict(absence_types_list)
self.absence_types_dict = absence_types_dict
# check for users where currentlogin has no rights to read absences
self.denied_users = self.users.evalocl("self->reject(ismemberreadable('abwesenheiten'))")
# sql where clause for request to get absences of each user in given intervall
whereclause = vtcapp.sqlwherebetweendate('datum', self.start_month_date, self.end_month_date)
whereclause += " or "
whereclause += vtcapp.sqlwherebetweendate('bisdatum', self.start_month_date, self.end_month_date)
whereclause += " or (datum is not NULL and bisdatum is not NULL and datum <= {} and bisdatum > {})".format(vtcapp.sqldateliteral(self.start_month_date), vtcapp.sqldateliteral(self.end_month_date))
whereclause += " and typlink > 0"
self.user_absences_dict = {}
for user in self.users:
# for each user we store the entries for each column i a list and append this list to a dictionnary with key = user.objid
user_absence_list = []
for idx in range(0, self.number_of_months):
# we initialize the list with NO_TEXT
user_absence_list.append([(NO_TEXT, NO_COLOUR, NO_ICON)] * 31) # tuple (Text, Colour, Icon)
if user in self.denied_users:
self.user_absences_dict[user.objid] = user_absence_list
continue
# calculate user absences (including groups) and order descending by priority so the highest
# priority absence (lowest value) will be processed last and overwrites overlapping absences
userAndGroups = user.evalocl("self->union(gruppen)")
# we need system context for sql request here
with vtcapp.SystemContext():
user_absences = vtcapp.getwithsql("Abwesenheit", whereclause, "", "bearbeiter", userAndGroups)
# user absences are ordered in the following way:
# first by member active then by typlink.art (0=Free, 1=Holidays, 2=Compensation) and then py priority
# that ensures that we get first the active absences with the highest priority
# only the first absence in the list for one day is considered later
if len(user_absences) > 1:
user_absences = user_absences.evalocl("self->ordermulti('not(aktiv);typlink.art;typlink.priority')")
# We calculate target time settings for each user for every single day.
current_date = self.start_month_date
while current_date <= self.end_month_date:
month_index = month_diff(current_date, self.start_month_date)-1
day_index = current_date.day-1
standard_target_time = user.evalocl("self->getSollzeitVorgabe({0},{0})".format(vtcapp.ocldate(current_date)))
if standard_target_time == 0:
user_absence_list[month_index][day_index] = (WEEKEND, COLOUR_WEEKEND, NO_ICON)
current_date = vtcapp.incday(current_date, 1)
# now we check all absences of the user and save the absences for all days in a dictionnary
for absence in user_absences:
if not absence.datum:
continue
# we only make entries for dates starting from start_month_date
from_date = max(absence.datum, self.start_month_date.date())
if not absence.bisDatum:
to_date = from_date
else:
to_date = min(absence.bisDatum, self.end_month_date.date())
current_date = from_date
while current_date <= to_date:
# for each day with absence calculate the effective absence type and value and save it to the list
# we only take the first item for colour and icon, because the list is ordered by priority
# for the text we collect the types of every absence on that day
month_index = month_diff(current_date, self.start_month_date)-1
day_index = current_date.day-1
absence_type_id = self.evalocl("typlink.objid", absence)
minutes = self.evalocl("minutenabwesend", absence)
active = self.evalocl("aktiv", absence)
if user_absence_list[month_index][day_index] == (NO_TEXT, NO_COLOUR, NO_ICON):
user_absence_list[month_index][day_index] = (self.get_text(absence_type_id, minutes, absence.aktiv), self.get_colour(absence_type_id, minutes, active), self.get_icon(active))
else:
text = user_absence_list[month_index][day_index][0] + ", " + self.get_text(absence_type_id, minutes, active)
user_absence_list[month_index][day_index] = (text, user_absence_list[month_index][day_index][1], user_absence_list[month_index][day_index][2])
current_date = vtcapp.incday(current_date, 1)
self.user_absences_dict[user.objid] = user_absence_list
# mark invalid dates
for month_index, day_index in self.invalid_dates_list:
for user_id, value in self.user_absences_dict.items():
value[month_index][day_index] = (DATE_NOT_VALID, COLOUR_DATE_NOT_VALID, NO_ICON)
return
def get_dynamic_column_count(self, subscriber):
return self.number_of_months
def get_dynamic_column_title(self, column_index, subscriber):
month_date = self.months[column_index].month_date
month_name_index = month_date.month-1
return vtcapp.translate(months[month_name_index]) + ' ' + str(month_date.year)
def get_text(self, absence_type, minutes_absent, active):
text = self.absence_types_dict[absence_type][0]
if not active:
text = vtcapp.translate("Planned") + ": " + text
# if there is only an absence of some hours, we want to show the user the duration of the absence.
if minutes_absent > 0:
text = "{} {} ".format(text, vtcapp.formatminutes(minutes_absent)) + vtcapp.translate("Hours")
return text
def get_colour(self, absence_type, minutes_absent, active):
# there are colours specified for each absence type,
# depending on if there is a full absence or only some hours
# and another colour for inactive absences
if not active:
colour = self.absence_types_dict[absence_type][3]
else:
if minutes_absent:
colour = self.absence_types_dict[absence_type][2]
else:
colour = self.absence_types_dict[absence_type][1]
return colour
def get_icon(self, active):
icon = ICON_OPEN
if active:
icon = ICON_INFO
return icon
class AbsenceTypePerDay:
display_type = "button"
def button_clicked(self, rowobj, expression):
# when the button is clicked, we show the absence on that day if there is only one
# or a dialog with all absences, if there are more than one
day = self.get_date(expression)
self.show_absence_or_dialog_for_more_than_one_absence(rowobj, day)
def get_background_color(self, rowobj, expression, subscriber):
text, colour, icon_id = self.calc_absence_type(rowobj, expression, subscriber)
return colour
def get_buttoniconid(self, rowobj, expression, subscriber):
# if we do not set an icon, the button is not visible
# we don't want a button for normal working days, for invalid dates and for weekends/no target time
text, colour, icon_id = self.calc_absence_type(rowobj, expression, subscriber)
return icon_id
def get_buttontooltip(self, rowobj, expression, subscriber):
text, colour, icon_id = self.calc_absence_type(rowobj, expression, subscriber)
return text
def calc_absence_type(self, rowobj, expression, subscriber):
list = expression.split(".")
day = int(list[0])
day_index = day-1
column_index = int(list[1])
user_absences_list = self.controller.user_absences_dict[rowobj.objid]
return user_absences_list[column_index][day_index]
def show_absence_or_dialog_for_more_than_one_absence(self, user, day):
absences_list = user.evalocl("self->currentAbwesenheiten({})".format(vtcapp.ocldate(day)))
whereclause = "((bisdatum is NULL and {0} = datum) or ({0} >= datum and (not bisdatum is NULL) and {0} <= bisdatum))".format(vtcapp.sqldateliteral(day))
whereclause += " and (typlink > 0)"
userAndGroups = user.evalocl("self->union(gruppen)")
# we need system context for sql request here
with vtcapp.SystemContext():
absences_list = vtcapp.getwithsql("Abwesenheit", whereclause, "", "bearbeiter", userAndGroups)
if len(absences_list) == 1:
vtcapp.showdetailform(absences_list[0])
return
else:
dlgDefinition="""
{% if show_minutes %}
<Dialog Title="{Translate 'Absences'}" Width="480">
{% else %}
<Dialog Title="{Translate 'Absences'}" Width="440">
{% endif %}
<TextBlock Text="{{text}}" Appearance="Info" FitMode="Wrap"/>
<Spacer Height="10" />
{% for absence in absences_list %}
<Group>
<TextBlock Text="{{absence.evalocl("datum.asstring")}}" HorizontalAlignment="Left" Appearance="Info" FlexWidth="2"/>
<TextBlock Text="{{absence.evalocl("bisdatum.asstring")}}" HorizontalAlignment="Left" Appearance="Info" FlexWidth="2"/>
<TextBlock Text="{{absence.evalocl("typlink.asstring")}}" HorizontalAlignment="Left" Appearance="Info" FlexWidth="3"/>
{% if show_minutes %}
<TextBlock Text="{{absence.evalocl("minutenabwesend->collect(m|if m>0 then m.ashmstring + ' h' else '' endif)->first")}}" HorizontalAlignment="Right" Appearance="Info" FlexWidth="1"/>
{% endif %}
<Button Text="{Translate 'Open'}" Name="{{"Button{}".format(absence.objid)}}" FlexWidth="3" HorizontalAlignment="Right" Appearance="Input" Command="{Binding CloseCommand}"/>
</Group>
{% endfor %}
<Dialog.Buttons>
<Button Text="{Translate 'Close'}" IsCancel="True" Command="{Binding CancelCommand}" />
</Dialog.Buttons>
</Dialog>
"""
text = vtcapp.translate("There are several absences for {0} on {1}.").format(user, vtcapp.datetostr(day))
dlgDefinition = vtcapp.rendertemplate(dlgDefinition, text = text, absences_list = absences_list, show_minutes=absences_list.evalocl("minutenabwesend->sum>0"))
initValues = {}
ok, values = vtcapp.showcustomdialog(dlgDefinition, initValues)
if ok:
for absence in absences_list:
if values["Button" + str(absence.objid)]:
vtcapp.showdetailform(absence)
def get_date(self, expression):
expression_list = expression.split(".")
day = int(expression_list[0])
column_index = int(expression_list[1])
month_date = self.controller.months[column_index].month_date
act_date = date(month_date.year, month_date.month,day)
return act_date
NonefalsetruefalsePythonTranslation_ColourAbsenceByHoursFarbe stundenweise AbwesenheitColour absence by hoursCouleur absence de quelques heuresColore per assenza di poche oreFarbe stundenweise AbwesenheitColour absence by hoursTranslation_ColourPlannedAbsenceFarbe geplante AbwesenheitColour planned absenceCouleur absence planifiéeColore assenza programmataFarbe geplante AbwesenheitColour planned absenceTranslation_ColourWholeDayAbsenceFarbe ganztägige AbwesenheitColour whole day absenceCouleur absence de toute la journéeColore assenza per tutto il giornoFarbe ganztägige AbwesenheitColour whole day absenceTranslation_OnlyActiveUsersNur aktive MitarbeiterOnly active usersSeuls collaborateurs actifsSolo collaboratori attiviNur aktive MitarbeiterOnly active usersTranslation_OverviewAbsencesÜbersicht AbwesenheitenOverview absencesAperçu des absencesPanoramica assenzeÜbersicht AbwesenheitenOverview absencesTranslation_ThereAreSeveralAbsencesForUserOnDayAm {1} gibt es mehrere Abwesenheiten für {0}.There are several absences for {0} on {1}.Le {1}, il y a plusieurs absences pour {0}.Il giorno {1} ci sono diverse assenze per {0}.Am {1} gibt es mehrere Abwesenheiten für {0}.There are several absences for {0} on {1}.