Exemple #1
0
def find_time_records(db, cl, nodeid, new_values):
    """ Search for existing time records when start/end date changes.
        Reject if valid records other than public holidays are found.
    """
    if 'valid_to' not in new_values and 'valid_from' not in new_values:
        return
    dyn = cl.getnode(nodeid)
    valid_to = new_values.get('valid_to')
    valid_from = new_values.get('valid_from')
    next = user_dynamic.next_user_dynamic(db, dyn)
    prev = user_dynamic.prev_user_dynamic(db, dyn)
    to = next and (next.valid_from - common.day)
    frm = prev and (prev.valid_to)
    ranges = dict \
        ( valid_to   = common.pretty_range (valid_to, to)
        , valid_from = common.pretty_range (frm, valid_from - common.day)
        )
    gaps = dict \
        ( valid_to   = not next or next.valid_from > valid_to
        , valid_from = not prev or prev.valid_to   < valid_from
        )
    msgs = dict \
        ( valid_to   = _
            ( "There are (non public holiday) "
              "time records at or after %s"
            )
        , valid_from = _
            ( "There are (non public holiday) "
              "time records before %s"
            )
        )
    for k in ('valid_to', 'valid_from'):
        value = new_values.get(k)
        if value is not None and gaps[k]:
            trs = db.time_record.filter \
                ( None
                , { 'daily_record.user': dyn.user
                  , 'daily_record.date': ranges [k]
                  }
                )
            # loop for checking if time recs are public holiday
            for id in trs:
                tr = db.time_record.getnode(id)
                # case where no wp was entered yet
                if tr.wp is None:
                    raise Reject(msgs[k] % value.pretty(common.ymd))
                # case where wp was set
                wp = db.time_wp.getnode(tr.wp)
                tc = db.time_project.getnode(wp.project)
                if not tc.is_public_holiday:
                    raise Reject(msgs[k] % value.pretty(common.ymd))
            # loop entirely for retiring public holidys
            for id in trs:
                tr = db.time_record.getnode(id)
                assert tr.wp
                wp = db.time_wp.getnode(tr.wp)
                tc = db.time_project.getnode(wp.project)
                assert tc.is_public_holiday
                db.time_record.retire(id)
Exemple #2
0
def main():
    cmd = ArgumentParser()
    cmd.add_argument \
        ( 'leave_submission'
        , help = 'Leave submission number'
        )
    cmd.add_argument \
        ( '-d', '--directory'
        , help    = 'Tracker directory'
        , default = os.getcwd ()
        )
    cmd.add_argument \
        ( '-u', '--user'
        , help    = 'User to open DB as'
        , default = 'admin'
        )
    args = cmd.parse_args()
    sys.path.insert(1, os.path.join(args.directory, 'lib'))
    import common
    import vacation
    tracker = instance.open(args.directory)
    db = tracker.open(args.user)
    ls = db.leave_submission.getnode(args.leave_submission)
    leave = db.daily_record_status.lookup('leave')
    d = dict()
    d['daily_record.user'] = ls.user
    d['daily_record.date'] = common.pretty_range(ls.first_day, ls.last_day)
    d['daily_record.status'] = leave
    trs = db.time_record.filter(None, d)
    if trs:
        print("Found time records, exiting")
        return
    dy = ls.first_day
    off = db.work_location.lookup('off')
    while dy <= ls.last_day:
        du = mindu = vacation.leave_duration(db, ls.user, dy)
        dt = common.pretty_range(dy, dy)
        dr = db.daily_record.filter(None, dict(user=ls.user, date=dt))
        wp = db.time_wp.getnode(ls.time_wp)
        tp = db.time_project.getnode(wp.project)
        if tp.max_hours is not None:
            mindu = min(du, tp.max_hours)
        assert len(dr) == 1
        if mindu:
            db.time_record.create \
                ( daily_record  = dr [0]
                , duration      = mindu
                , work_location = off
                , wp            = ls.time_wp
                )
        db.daily_record.set(dr[0], status=leave)
        dy += common.day
    db.commit()
Exemple #3
0
def get_daily_record (db, user, date) :
    """ Use caching: prefetch all records from given date until now and
        store them. Use a per-user cache of the earliest dr found.
    """
    pdate = date.pretty (ymd)
    date  = Date (pdate)
    now   = Date (Date ('.').pretty (ymd))
    if not getattr (db, 'daily_record_cache', None) :
        db.daily_record_cache = {}
        def daily_record_cache_clear (db) :
            db.daily_record_cache = {}
        db.registerClearCacheCallback (daily_record_cache_clear, db)
    if (user, pdate) not in db.daily_record_cache :
        if date < now :
            start = date
            end   = now
        else :
            start = now
            end   = date
        range = common.pretty_range (start, end)
        drs = db.daily_record.filter \
            (None, dict (user = user, date = range), sort = ('+', 'date'))
        next = start
        for drid in drs :
            dr = db.daily_record.getnode (drid)
            _update_empty_dr (db, user, dr.date, next)
            db.daily_record_cache [(user, dr.date.pretty (ymd))] = dr
            next = dr.date + day
        _update_empty_dr (db, user, end, next)
    return db.daily_record_cache [(user, pdate)]
Exemple #4
0
def check_range(db, nodeid, uid, first_day, last_day):
    """ Check length of range and if there are any records in the given
        time-range for this user. This means either the first day of an
        existing record is inside the new range or the last day is
        inside the new range or the first day is lower than the first
        day *and* the last day is larger (new interval is contained in
        existing interval).
    """
    if first_day > last_day:
        raise Reject(_("First day may not be after last day"))
    if (last_day - first_day) > Interval('30d'):
        raise Reject(_("Max. 30 days for single leave submission"))
    range = common.pretty_range(first_day, last_day)
    both = (first_day.pretty(';%Y-%m-%d'), last_day.pretty('%Y-%m-%d;'))
    stati = [
        x for x in db.leave_status.getnodeids(retired=False)
        if db.leave_status.get(x, 'name') not in ('declined', 'cancelled')
    ]
    for f, l in ((range, None), (None, range), both):
        d = dict(user=uid, status=stati)
        if f:
            d['first_day'] = f
        if l:
            d['last_day'] = l
        r = [x for x in db.leave_submission.filter(None, d) if x != nodeid]
        if r:
            raise Reject \
                (_ ("You already have vacation requests in this time range"))
def department_users(db, department_id):
    """ Find all valid dynamic user records with a given department
        and return all user ids (not the dynamic user but only the uid)
    """
    now = Date('.')
    dt = common.pretty_range(None, now)
    try:
        if department_id in db.dep_uid_cache:
            return db.dep_uid_cache[department_id]
    except AttributeError:

        def dep_uid_cache_clear(db):
            db.dep_uid_cache = {}

        db.registerClearCacheCallback(dep_uid_cache_clear, db)
        db.dep_uid_cache = {}
    users = {}
    d = dict(department=department_id, valid_from=dt)
    for did in db.user_dynamic.filter(None, d):
        dyn = db.user_dynamic.getnode(did)
        if dyn.valid_to and dyn.valid_to < now:
            continue
        users[dyn.user] = 1
    db.dep_uid_cache[department_id] = users
    return db.dep_uid_cache[department_id]
Exemple #6
0
def avg_hours_per_week_this_year (db, user, date_in_year) :
    """ Loop over all dyn records in this year and use only those with
        all-in set. For those we count the hours and compute the
        average over all all-in days.
    """
    y     = common.start_of_year (date_in_year)
    eoy   = common.end_of_year   (y)
    now   = Date ('.')
    if eoy > now :
        eoy = now
    hours = 0.0
    dsecs = 0.0
    ds    = 24 * 60 * 60
    for dyn in user_dynamic.user_dynamic_year_iter (db, user, y) :
        if not dyn.all_in :
            continue
        vf = dyn.valid_from
        if vf < y :
            vf = y
        vt = dyn.valid_to
        if not vt or vt > eoy + common.day :
            vt = eoy + common.day
        dsecs += (vt - vf).as_seconds ()
        drs = db.daily_record.filter \
            (None, dict (date = common.pretty_range (vf, vt), user = user))
        for drid in drs :
            dr  = db.daily_record.getnode (drid)
            dur = user_dynamic.update_tr_duration (db, dr)
            hours += dur
    days = dsecs / ds
    assert days <= 366
    if not days :
        return 0
    avgday = hours / float (days)
    return avgday * 7
Exemple #7
0
def fix_vacation (db, uid, date_from = None, date_to = None) :
    """ Fix vacation for a user where the dyn. user record has been
        changed *after* the user already booked vacation.
        We search for all time-records with a daily-record in state
        'leave' since the last frozen time or date_from if given.
    """
    #print ("fix_vacation: %s %s %s" % (uid, date_from, date_to))
    if date_from is None :
        date_from = Date ('2000-01-01')
        frozen = db.daily_record_freeze.filter \
            (None, dict (user = uid, frozen = True), sort = ('-', 'date'))
        if frozen :
            frozen = db.daily_record_freeze.getnode (frozen [0])
            date_from = frozen.date + common.day
    leave = db.daily_record_status.lookup ('leave')
    d = dict ()
    d ['daily_record.user']   = uid
    d ['daily_record.date']   = common.pretty_range (date_from, date_to)
    d ['daily_record.status'] = leave
    trs = db.time_record.filter (None, d)
    for trid in trs :
	tr = db.time_record.getnode  (trid)
	dr = db.daily_record.getnode (tr.daily_record)
	wp = db.time_wp.getnode      (tr.wp)
	tp = db.time_project.getnode (wp.project)
	if not tp.is_vacation and not tp.is_public_holiday :
	    continue
	du = leave_duration (db, uid, dr.date, tp.is_public_holiday)
	if tr.duration != du :
	    #print "Wrong: time_record%s: %s->%s" % (trid, tr.duration, du)
	    db.time_record.set (trid, duration = du)
Exemple #8
0
    def handle (self) :
        uid = self.db.user.lookup (self.user)
        if not self.db.user.get (uid, 'supervisor') :
            f_supervisor = self._ ('supervisor')
            user      = self.user
            msg       = self._ ("No %(f_supervisor)s for %(user)s") % locals ()
            url       = 'index?:error_message=' + msg 
            raise Redirect, url

        self.create_daily_records ()
        self.request.filterspec = \
            { 'date' : common.pretty_range (self.start, self.end)
            , 'user' : [self.user]
            }
        url = self.request.indexargs_url \
            ( ''
            , { ':action'    : 'search'
              , ':template'  : 'edit'
              , ':sort'      : 'date'
              , ':group'     : 'user'
              , ':startwith' : '0'
              , ':filter'    : ','.join (self.request.filterspec.keys ())
              }
            )
        raise Redirect, url
Exemple #9
0
 def create_daily_records (self) :
     self.set_request ()
     request         = self.request
     filterspec      = request.filterspec
     columns         = request.columns
     assert (request.classname == 'daily_record')
     start, end      = common.date_range (self.db, filterspec)
     self.start      = start
     self.end        = end
     max             = start + Interval ('31d')
     if end > max :
         msg = \
             ( "Error: Interval may not exceed one month: %s"
             % ' to '.join ([i.pretty (common.ymd) for i in (start, end)])
             )
         end = max
         request.filterspec ['date'] = common.pretty_range (start, end)
         url = request.indexargs_url \
             ( ''
             , { ':action'        : 'search'
               , ':template'      : 'edit'
               , ':sort'          : 'date'
               , ':group'         : 'user'
               , ':filter'        : ','.join (request.filterspec.keys ())
               , ':startwith'     : '0'
               , ':error_message' : msg
               }
             )
         raise Redirect, url
     if 'user' in filterspec :
         self.user = filterspec ['user'][0]
     else :
         self.user = self.db.getuid ()
     vacation.create_daily_recs (self.db, self.user, start, end)
     self.db.commit ()
Exemple #10
0
def no_overlap (db, cl, nodeid, new_values) :
    ymd = common.ymd
    if 'first_day' in new_values or 'last_day' in new_values :
        fd = new_values.get ('first_day')
        ld = new_values.get ('last_day')
        u  = new_values.get ('user')
        assert fd and ld and u or nodeid
        if not fd :
            fd = cl.get (nodeid, 'first_day')
        if not ld :
            ld = cl.get (nodeid, 'last_day')
        if not u :
            u = cl.get (nodeid, 'user')
        dt = common.pretty_range (fd, ld)
        d  = dict (user = u)
        fl = dict \
            ( first_day = fd.pretty (';%Y-%m-%d')
            , last_day = ld.pretty ('%Y-%m-%d;')
            )
        for d in (dict (first_day = dt), dict (last_day = dt), fl) :
            results = cl.filter (None, dict (d, user = u))
            for r in results :
                if r == nodeid :
                    continue
                item = cl.getnode (r)
                raise Reject \
                    (_ ("Overlap with existing absence: %s-%s")
                    % (item.first_day.pretty (ymd), item.last_day.pretty (ymd))
                    )
Exemple #11
0
def vacation_time_sum (db, user, ctype, start, end) :
    dt  = common.pretty_range (start, end)
    dr  = db.daily_record.filter (None, dict (user = user, date = dt))
    dtt = [('+', 'daily_record.date')]
    vwp = vacation_wps (db)
    trs = db.time_record.filter \
        (None, dict (daily_record = dr, wp = vwp), sort = dtt)
    vac = 0.0
    if ctype == -1 :
        ctype = _get_ctype (db, user, Date ('.'))
    by_dr = {}
    for tid in trs :
        tr  = db.time_record.getnode  (tid)
        dr  = db.daily_record.getnode (tr.daily_record)
        dyn = user_dynamic.get_user_dynamic (db, user, dr.date)
        # dyn is None if time_records booked but dyn record revoked for this period:
        if not dyn or dyn.contract_type != ctype :
            continue
        wh  = user_dynamic.day_work_hours (dyn, dr.date)
        assert wh
        if dr.id not in by_dr :
            by_dr [dr.id] = (wh, [])
        assert by_dr [dr.id][0] == wh
        by_dr [dr.id][1].append (tr.duration)
    for wh, durs in by_dr.itervalues () :
        vac += ceil (sum (durs) / wh * 2) / 2.
    return vac
Exemple #12
0
def try_create_public_holiday (db, daily_record, date, user) :
    st_open = db.daily_record_status.lookup ('open')
    wp      = public_holiday_wp (db, user, date)
    # Don't change anything if status not open
    if db.daily_record.get (daily_record, 'status') != st_open :
        return
    # Only perform public holiday processing if user has a public
    # holiday wp to book on.
    if not wp :
        return
    dyn = user_dynamic.get_user_dynamic (db, user, date)
    wh  = user_dynamic.day_work_hours   (dyn, date)
    if wh :
        loc = db.org_location.get (dyn.org_location, 'location')
        hol = db.public_holiday.filter \
            ( None
            , { 'date'      : common.pretty_range (date, date)
              , 'locations' : loc
              }
            )
        if hol and wh :
            holiday = db.public_holiday.getnode (hol [0])
            if holiday.is_half :
                wh = wh / 2.
            wh = user_dynamic.round_daily_work_hours (wh)
            # Check if there already is a public-holiday time_record
            # Update duration (and wp) if wrong
            trs = db.time_record.filter \
                (None, dict (daily_record = daily_record))
            for trid in trs :
                tr = db.time_record.getnode (trid)
                if tr.wp is None :
                    continue
                tp = db.time_project.getnode \
                    (db.time_wp.get (tr.wp, 'project'))
                if tp.is_public_holiday :
                    d = {}
                    if tr.duration != wh :
                        d ['duration'] = wh
                    if tr.wp != wp :
                        d ['wp'] = wp
                    if d :
                        db.time_record.set (trid, ** d)
                    return
            comment = holiday.name
            if holiday.description :
                comment = '\n'.join ((holiday.name, holiday.description))
            db.time_record.create \
                ( daily_record  = daily_record
                , duration      = wh
                , wp            = wp
                , comment       = comment
                , work_location = db.work_location.lookup ('off')
                )
Exemple #13
0
 def handle (self) :
     self.create_daily_records ()
     self.request.filterspec = \
         { 'date' : common.pretty_range (self.start, self.end)
         , 'user' : [self.user]
         }
     # insert into form for new request objects
     self.request.form ['date'].value = self.request.filterspec ['date']
     self.request.form ['user'].value = self.request.filterspec ['user'][0]
     # returns only in error case
     return self.__super.handle ()
Exemple #14
0
def overtime_corr (db, user, start, end) :
    """ Return overtime corrections in given time range. """
    cids = db.overtime_correction.filter \
        (None, dict (user = user, date = common.pretty_range (start, end)))
    corr = {}
    for c in cids :
        oc  = db.overtime_correction.getnode (c)
        dyn = get_user_dynamic (db, user, oc.date)
        if dyn :
            d = oc.date.pretty (ymd)
            if d not in corr :
                corr [d] = []
            corr [d].append (oc)
    return corr
Exemple #15
0
def check_dr_status(db, user, first_day, last_day, st_name):
    # All daily records must be in state status
    dt = common.pretty_range(first_day, last_day)
    dr = db.daily_record.filter(None, dict(user=user, date=dt))
    st = db.daily_record_status.lookup(st_name)
    for drid in dr:
        if st != db.daily_record.get(drid, 'status'):
            raise Reject \
                (_ ('Daily record not in status "%(st_name)s"') % locals ())
    # If not open, *all* daily records must exist
    # Maybe this should be an assertion...
    if st_name != 'open':
        iv = last_day + common.day - first_day
        if len(dr) != vacation.interval_days(iv):
            raise Reject(_('Daily records must exist'))
Exemple #16
0
def remaining_vacation \
    (db, user, ctype = -1, date = None, cons = None, to_eoy = True) :
    """ Compute remaining vacation on the given date
    """
    if date is None :
        date = Date ('.')
    pdate = date.pretty (common.ymd)
    if ctype == -1 :
        ctype = _get_ctype (db, user, date)
    if ctype == -1 :
        return
    vac   = None
    try :
        vac = db.rem_vac_cache.get ((user, ctype, pdate, to_eoy))
    except AttributeError :
        def vac_clear_cache (db) :
            db.rem_vac_cache = {}
        db.registerClearCacheCallback (vac_clear_cache, db)
        db.rem_vac_cache = {}
    if vac is not None :
        return vac
    vc = get_vacation_correction (db, user, ctype, date)
    if not vc :
        return
    ed  = next_yearly_vacation_date (db, user, ctype, date)
    if not to_eoy :
        ed = min (ed, date)
    if cons is None :
        cons = consolidated_vacation (db, user, ctype, date, vc, to_eoy)
    vac = cons
    vac -= vacation_time_sum (db, user, ctype, vc.date, ed)
    # All vacation_correction records up to date but starting with one
    # day later (otherwise we'll find the absolute correction)
    # Also one day *earlier* than ed for the same reason.
    dt  = common.pretty_range (vc.date + common.day, ed - common.day)
    d   = dict (user = user, date = dt)
    if ctype is not None :
        d ['contract_type'] = ctype
    ds  = [('+', 'date')]
    vcs = db.vacation_correction.filter (None, d, sort = ds)
    for vcid in vcs :
        vc = db.vacation_correction.getnode (vcid)
        if vc.contract_type != ctype :
            continue
        assert not vc.absolute
        vac += vc.days
    db.rem_vac_cache [(user, ctype, pdate, to_eoy)] = vac
    return vac
Exemple #17
0
def next_week (db, request) :
    try :
        db  = db._db
    except AttributeError :
        pass
    start, end = common.date_range (db, request.filterspec)
    n_start = end     + Interval ('1d')
    n_end   = n_start + Interval ('6d')
    date    = common.pretty_range (n_start, n_end)
    return \
        '''javascript:
            if(submit_once()) {
              document.forms.edit_daily_record ['date'].value = '%s';
              document.edit_daily_record.submit ();
            }
        ''' % date
Exemple #18
0
def weeksum (db, drid, format = None) :
    start, end = common.week_from_date (db.daily_record.get (drid, 'date'))
    user       = db.daily_record.get (drid, 'user')
    d   = start
    sum = 0.
    while d <= end :
        dr   = db.daily_record.filter \
            (None, dict (date = common.pretty_range (d, d), user = user))
        if len (dr) == 0 :
            d = d + Interval ('1d')
            continue
        assert (len (dr) == 1)
        dr   = dr [0]
        sum += daysum (db, dr)
        d    = d + Interval ('1d')
    if format :
        return format % sum
    return sum
Exemple #19
0
def leave_submission_days (db, user, ctype, start, end, type, * stati) :
    """ Sum leave submissions of the given type
        with the given status in the given time range for the given user
        and ctype (contract_type).
    """
    assert start <= end
    dt   = common.pretty_range (start, end)
    dts  = ';%s' % start.pretty (common.ymd)
    dte  = '%s;' % end.pretty   (common.ymd)
    if type == 'vacation' :
        lwp  = vacation_wps (db)
    elif type == 'flexi' :
        lwp  = flexi_wps (db)
    else :
        lwp  = special_wps (db)
    d    = dict (user = user, status = list (stati), time_wp = lwp)
    d1   = dict (d, first_day = dt)
    vs1  = db.leave_submission.filter (None, d1)
    d2   = dict (d, last_day = dt)
    vs2  = db.leave_submission.filter (None, d2)
    d3   = dict (d, first_day = dts, last_day = dte)
    vs3  = db.leave_submission.filter (None, d3)
    vss  = dict.fromkeys (vs1 + vs2 + vs3).keys ()
    vss  = [db.leave_submission.getnode (i) for i in vss]
    days = 0.0
    for vs in vss :
        first_day = vs.first_day
        last_day  = vs.last_day
        dyn = user_dynamic.get_user_dynamic (db, user, first_day)
        if not dyn :
            continue
        if dyn.contract_type != ctype :
            continue
        if first_day < start :
            assert vs.last_day >= start
            first_day = start
        if last_day > end :
            assert vs.first_day <= end
            last_day  = end
        days += leave_days (db, user, first_day, last_day)
    return days
Exemple #20
0
def create_daily_recs (db, user, first_day, last_day) :
    d = first_day
    while d <= last_day :
        pr = common.pretty_range (d, d)
        x = db.daily_record.filter (None, dict (user = user, date = pr))
        if x :
            assert len (x) == 1
            x = x [0]
        else :
            dyn = user_dynamic.get_user_dynamic (db, user, d)
            if not dyn :
                d += common.day
                continue
            x = db.daily_record.create \
                ( user              = user
                , date              = d
                , weekend_allowed   = False
                , required_overtime = False
                )
        try_create_public_holiday (db, x, d, user)
        d += common.day
Exemple #21
0
def leave_duration (db, user, date) :
    """ Duration of leave on a single day to be booked. """
    dyn = user_dynamic.get_user_dynamic (db, user, date)
    wh  = user_dynamic.day_work_hours (dyn, date)
    if not wh :
        return 0.0
    dt  = common.pretty_range (date, date)
    dr  = db.daily_record.filter (None, dict (user = user, date = dt))
    assert len (dr) == 1
    try_create_public_holiday (db, dr [0], date, user)
    trs = db.time_record.filter (None, dict (daily_record = dr [0]))
    bk  = 0.0
    for trid in trs :
        tr = db.time_record.getnode (trid)
        if not tr.wp :
            continue
        wp = db.time_wp.getnode (tr.wp)
        tp = db.time_project.getnode (wp.project)
        if tp.is_public_holiday :
            bk += tr.duration
    assert bk <= wh
    return wh - bk
Exemple #22
0
def state_change_reactor(db, cl, nodeid, old_values):
    vs = cl.getnode(nodeid)
    old_status = old_values.get('status')
    new_status = vs.status
    accepted = db.leave_status.lookup('accepted')
    declined = db.leave_status.lookup('declined')
    submitted = db.leave_status.lookup('submitted')
    cancelled = db.leave_status.lookup('cancelled')
    crq = db.leave_status.lookup('cancel requested')
    if old_status == new_status:
        return
    dt = common.pretty_range(vs.first_day, vs.last_day)
    drs = db.daily_record.filter(None, dict(user=vs.user, date=dt))
    trs = db.time_record.filter(None, dict(daily_record=drs))
    if new_status == accepted:
        handle_accept(db, vs, trs, old_status)
    elif new_status == declined:
        handle_decline(db, vs)
    elif new_status == submitted:
        handle_submit(db, vs)
    elif new_status == cancelled:
        handle_cancel(db, vs, drs, trs, old_status == crq)
    elif new_status == crq:
        handle_cancel_rq(db, vs)
Exemple #23
0
            d['contract_type'] = ct
        db.auto_wp.create(**d)
        print("Created: %s" % str(d))
        sys.stdout.flush()
# Commit after creating Auto-WPs
db.commit()

# Now loop over all olo/ct -> tc combinations
for olo, ct in tc_dict:
    tcs = tc_dict[(olo, ct)]
    # Find all dynamic user records for this combination of olo and ct
    # Which are valid on due_date
    # Note that the contract type is '-1' for 'normal' users, so the
    # query will find dynamic user records with empty contract type for
    # those.
    e = common.pretty_range(date.Date(due_date) + common.day) + ',-'
    d  = dict \
        ( org_location  = olo
        , contract_type = ct
        , valid_from    = ';' + due_date
        , valid_to      = e
        )
    du = db.user_dynamic.filter(None, d)
    for did in du:
        dyn = db.user_dynamic.getnode(did)
        # Nothing to do if booking not allowed
        if not dyn.booking_allowed:
            continue
        user = db.user.getnode(dyn.user)
        snam = user.username.split('@', 1)[0]
Exemple #24
0
def approvals_pending (db, request, userlist) :
    try :
        db  = db._db
    except AttributeError :
        pass
    pending   = {}
    submitted = db.daily_record_status.lookup ('submitted')
    spec      = copy (request.filterspec)
    filter    = request.filterspec
    editdict  = {':template' : 'edit', ':filter' : 'user,date'}
    now       = Date ('.')
    for u in userlist :
        find_user   = dict (user = u, status = submitted)
        fdate       = None
        last_frozen = db.daily_record_freeze.filter \
            ( None
            , dict (user = u, date = now.pretty (';%Y-%m-%d'), frozen = True)
            , group = [('-', 'date')]
            )
        if last_frozen :
            fdate = db.daily_record_freeze.get (last_frozen [0], 'date') \
                  + common.day
            find_user ['date'] = fdate.pretty ('%Y-%m-%d;')
        dr_per_user = db.daily_record.filter (None, find_user)
        pending [u] = {}
        if dr_per_user :
            earliest = latest = None
            for p in dr_per_user :
                date = db.daily_record.get (p, 'date')
                week, year = common.weekno_year_from_day (date)
                if not earliest or date < earliest :
                    earliest = date
                if not latest   or date > latest :
                    latest   = date
                start, end = common.week_from_date (date)
                if fdate and start < fdate :
                    start = fdate
                filter ['date'] = common.pretty_range (start, end)
                filter ['user'] = u
                pending [u][(year, week)] = \
                    [ None
                    , request.indexargs_url ('', editdict)
                    , 'todo'
                    ]
            interval = latest - earliest
            for k in pending [u].iterkeys () :
                if interval < Interval ('31d') :
                    filter ['date'] = common.pretty_range (earliest, latest)
                    pending [u][k][0] = request.indexargs_url ('', editdict)
                else :
                    pending [u][k][0] = pending [u][k][1]
        else :
            dyn = user_dynamic.last_user_dynamic (db, u)
            if dyn and (not dyn.valid_to or not fdate or dyn.valid_to > fdate) :
                date = now
                if dyn.valid_to and dyn.valid_to < date :
                    date = dyn.valid_to
                week, year = common.weekno_year_from_day (date)
                start, end = common.week_from_date (date)
                if fdate and start < fdate :
                    start = fdate
                if dyn.valid_to and dyn.valid_to < end :
                    end   = dyn.valid_to
                filter ['date'] = common.pretty_range (start, end)
                filter ['user'] = u
                url = request.indexargs_url ('', editdict)
                pending [u][(year, week)] = [url, url, 'done']
    request.filterspec = spec
    return pending
def wp_check_auto_wp (db, cl, nodeid, new_values) :
    """ Check that modifications to wp that has auto_wp set is ok
    """
    if not nodeid and 'auto_wp' not in new_values :
        return
    if nodeid :
        if not cl.get (nodeid, 'auto_wp') :
            if 'auto_wp' in new_values :
                raise Reject \
                    (_ ("Property %s may not change") % _ ('auto_wp'))
            return
    # These are not allowed to change
    props = \
        ( 'auto_wp'
        , 'bookers'
        , 'contract_type'
        , 'org_location'
        , 'project'
        , 'is_public'
        )
    if nodeid :
        for p in props :
            if p in new_values :
                raise Reject \
                    (_ ("Property %s may not change for auto wp") % _ (p))
        bookers = cl.get (nodeid, 'bookers')
        auto_wp = cl.get (nodeid, 'auto_wp')
    else :
        common.require_attributes \
            ( _, cl, nodeid, new_values
            , 'bookers'
            , 'auto_wp'
            , 'time_project'
            , 'durations_allowed'
            )
        bookers = new_values ['bookers']
        auto_wp = new_values ['auto_wp']
    auto_wp = db.auto_wp.getnode (auto_wp)
    if 'time_start' not in new_values and 'time_end' not in new_values :
        return
    start = new_values.get ('time_start')
    end   = new_values.get ('time_end')
    if not start :
        assert nodeid
        start = cl.get (nodeid, 'time_start')
    # Cannot check for empty end here, we could set the end to empty!
    if 'time_end' not in new_values and nodeid :
        end = cl.get (nodeid, 'time_end')
    assert len (bookers) == 1
    booker = bookers [0]
    freeze = freeze_date (db, booker)
    # Get dyn user for start
    dyn = user_dynamic.get_user_dynamic (db, booker, start)
    if not dyn and start != end :
        raise Reject (_ ("Invalid change of start/end: no dyn. user"))
    if not dyn :
        return
    if not lib_auto_wp.is_correct_dyn (dyn, auto_wp) :
        raise Reject \
            (_ ("Invalid change of start: Invalid dyn. user"))
    # loop backwards through dyns
    if 'time_start' in new_values :
        # Find the first dyn user which matches up with our start date
        prev = dyn
        while prev.valid_from > start :
            p = user_dynamic.prev_user_dynamic (db, prev)
            if  (  p.valid_to != prev.valid_from
                or not lib_auto_wp.is_correct_dyn (p, auto_wp)
                ) :
                raise Reject ("Invalid change of start: Invalid dyn. user")
            prev = p
        # We need to find previous wp if we don't start freezedate + day
        if prev.valid_from < start and start > freeze + common.day :
            d = dict \
                ( auto_wp  = auto_wp.id
                , time_end = common.pretty_range (None, start)
                )
            wps = db.time_wp.filter (None, d, sort = ('-', 'time_end'))
            if not wps :
                raise Reject (_ ("Invalid change of start: No prev. WP"))
            wp = db.time_wp.getnode (wps [0])
            if wp.time_end != start :
                raise Reject (_ ("Invalid change of start: Invalid prev. WP"))
    # loop forward through dyns
    if 'time_end' in new_values :
        next = dyn
        # Need to find next wp if dyn is valid longer than end and not
        # limited by a duration
        dur_end = lib_auto_wp.auto_wp_duration_end (db, auto_wp, booker)
        while next.valid_to and (not end or next.valid_to < end) :
            if dur_end and dur_end <= next.valid_to :
                break
            n  = user_dynamic.next_user_dynamic (db, next)
            if  (  n.valid_from != next.valid_to
                or not lib_auto_wp.is_correct_dyn (n, auto_wp)
                ) :
                raise Reject ("Invalid change of end: Invalid dyn. user")
            next = n
        if end and not dur_end and (not next.valid_to or end < next.valid_to) :
            d = dict \
                ( auto_wp    = auto_wp.id
                , time_start = common.pretty_range (end)
                )
            wps = db.time_wp.filter (None, d, sort = ('+', 'time_start'))
            if not wps :
                raise Reject (_ ("Invalid change of end: No next WP"))
            wp = db.time_wp.getnode (wps [0])
            if wp.time_start != end :
                raise Reject (_ ("Invalid change of end: Invalid next WP"))
def check_time_record (db, cl, nodeid, new_values) :
    for i in 'daily_record', :
        if i in new_values :
            raise Reject, _ ("%(attr)s may not be changed") % {'attr' : _ (i)}
    drec     = new_values.get ('daily_record', cl.get (nodeid, 'daily_record'))
    dr       = db.daily_record.getnode (drec)
    date     = dr.date
    user     = dr.user
    wpid     = new_values.get ('wp', cl.get (nodeid, 'wp'))
    wp       = None
    tp       = None
    is_ph    = False
    if (wpid) :
        wp = db.time_wp.getnode (wpid)
        tp = db.time_project.getnode (wp.project)
        is_ph = tp.is_public_holiday
    if  (   frozen (db, user, date)
        and new_values.keys () != ['tr_duration']
        ) :
        uname = db.user.get (user, 'username')
        raise Reject, _ ("Frozen: %(uname)s, %(date)s") % locals ()
    status   = db.daily_record.get (cl.get (nodeid, 'daily_record'), 'status')
    leave    = db.daily_record_status.lookup ('leave')
    allow    = False
    if dr.status == leave :
        du = vacation.leave_duration (db, user, date, is_ph)
        if  (   new_values.keys () == ['duration']
            and new_values ['duration'] == du
            and cl.get (nodeid, 'duration') != du
            ) :
            allow = True
    allow    = allow or db.getuid () == '1'
    if  (   status != db.daily_record_status.lookup ('open')
        and new_values.keys () != ['tr_duration']
        and not allow
        ) :
        raise Reject, _ ('Editing of time records only for status "open"')
    # allow empty duration to delete record
    if 'duration' in new_values and new_values ['duration'] is None :
        keys = dict.fromkeys (new_values.iterkeys ())
        del keys ['duration']
        if len (keys) > 0 :
            raise Reject, \
                ( _ ('%(date)s: No duration means "delete record" but '
                     'you entered new value for %(attr)s'
                    )
                % dict ( date = date.pretty (common.ymd)
                       , attr = ", ".join (['"' + _ (i) + '"' for i in keys])
                       )
                )
        return
    check_generated (new_values)
    start    = new_values.get ('start',        cl.get (nodeid, 'start'))
    end      = new_values.get ('end',          cl.get (nodeid, 'end'))
    duration = new_values.get ('duration',     cl.get (nodeid, 'duration'))
    dist     = new_values.get ('dist',         cl.get (nodeid, 'dist'))
    wp       = new_values.get ('wp',           cl.get (nodeid, 'wp'))
    wl       = 'work_location'
    ta       = 'time_activity'
    location = new_values.get (wl,             cl.get (nodeid, wl))
    activity = new_values.get (ta,             cl.get (nodeid, ta))
    comment  = new_values.get ('comment',      cl.get (nodeid, 'comment'))
    check_start_end_duration \
        (date, start, end, duration, new_values, dist = dist)
    if not location :
        new_values ['work_location'] = '1'
    if dist and not wp :
        raise Reject, _ ("Distribution: WP must be given")
    if dist :
        if dist < duration :
            newrec = dict \
                ( daily_record  = drec
                , duration      = dist
                , wp            = wp
                , time_activity = activity
                , work_location = location
                )
            if comment :
                newrec ['comment'] = comment
            start_generated = new_values.get \
                ('start_generated', cl.get (nodeid, 'start_generated'))
            if (start) :
                newrec     ['start']           = start
                newrec     ['end_generated']   = True
                newrec     ['start_generated'] = start_generated
                new_values ['start_generated'] = True
            cl.create (** newrec)
            for attr in 'wp', 'time_activity', 'work_location', 'comment' :
                if attr in new_values :
                    del new_values [attr]
            wp = cl.get (nodeid, 'wp')
        elif dist == duration :
            # Nothing to do -- just set new wp
            pass
        else :
            dist -= duration
            wstart, wend = common.week_from_date (date)
            dsearch = common.pretty_range (date, wend)
            drs = db.daily_record.filter \
                (None, dict (user = user, date = dsearch))
            trs = db.time_record.filter  \
                (None, {'daily_record' : drs})
            trs = [db.time_record.getnode (t) for t in trs]
            trs = [t for t in trs
                   if  (   not t.wp
                       and t.id != nodeid
                       and (   t.daily_record != drec
                           or (  start and t.start > start
                              or not start
                              )
                           )
                       )
                  ]
            trs = [(db.daily_record.get (tr.daily_record, 'date'), tr.start, tr)
                   for tr in trs
                  ]
            trs.sort ()
            trs = [tr [2] for tr in trs]
            sum = reduce (add, [t.duration for t in trs], 0)
            if sum < dist :
                raise Reject, _ \
                    ("dist must not exceed sum of unassigned times in week")
            for tr in trs :
                if tr.duration <= dist :
                    dist -= tr.duration
                    db.time_record.set \
                        ( tr.id
                        , wp            = wp
                        , time_activity = activity
                        , work_location = location
                        )
                else :
                    param = dict (duration = tr.duration - dist)
                    newrec = dict \
                        ( daily_record  = tr.daily_record
                        , duration      = dist
                        , wp            = wp
                        , time_activity = activity
                        , work_location = location
                        )
                    if tr.start :
                        param ['start_generated'] = True
                        dstart  = Date (tr.start)
                        hours   = int (dist)
                        minutes = (dist - hours) * 60
                        dstart += Interval ('%d:%d' % (hours, minutes))
                        param ['start'] = dstart.pretty (hour_format)
                        newrec ['start']           = tr.start
                        newrec ['end_generated']   = True
                    cl.create          (** newrec)
                    # warning side-effect, calling set will change
                    # values in current tr!
                    db.time_record.set (tr.id, **param)
                    dist = 0
                if not dist :
                    break
            assert (dist == 0)
        del new_values ['dist']
    if wp :
        correct_work_location (db, wp, new_values)
    if 'tr_duration' not in new_values :
        new_values ['tr_duration'] = None
Exemple #27
0
    def __init__(self, db, request):
        self.db = db = db._db
        now = Date('.')
        user = None
        dt = None
        department = None
        supervisor = None
        self.filterspec = request.filterspec
        self.request = request
        if request.filterspec:
            if 'first_day' in request.filterspec:
                dt = request.filterspec['first_day']
            if 'user' in request.filterspec:
                user = request.filterspec['user']
            if 'supervisor' in request.filterspec:
                supervisor = request.filterspec['supervisor']
            if 'department' in request.filterspec:
                department = request.filterspec['department']
        if not dt:
            som = common.start_of_month(now)
            eom = common.end_of_month(now)
            dt = common.pretty_range(som, eom)
        try:
            fd, ld = dt.split(';')
        except ValueError:
            fd = ld = dt
        self.fdd = fdd = Date(fd)
        self.ldd = ldd = Date(ld)
        self.month = month_name(self.fdd)
        if common.start_of_month(fdd) != common.start_of_month(ldd):
            self.ldd = ldd = common.end_of_month(fdd)
            dt = common.pretty_range(fdd, ldd)
        srt = [('+', a) for a in ('lastname', 'firstname')]
        self.users = users = []
        if user:
            self.users = users = db.user.filter(user, {}, sort=srt)
        if supervisor:
            u = db.user.filter \
                (None, dict (supervisor = supervisor), sort = srt)
            users.extend(u)
        if department:
            u = db.user.filter \
                (None, dict (department = department), sort = srt)
            users.extend(u)
        valid = db.user_status.lookup('valid')
        if not self.users:
            users = db.user.filter(None, dict(status=valid), sort=srt)
            self.users = users
        else:
            users = db.user.filter(users, dict(status=valid), sort=srt)
            self.users = users
        acc = db.leave_status.lookup('accepted')
        flt = dict \
            ( first_day = ';%s' % fd
            , last_day  = '%s;' % ld
            , user      = users
            , status    = acc
            )
        sp = ('user.lastname', 'user.firstname', 'first_day')
        srt = [('+', a) for a in sp]
        lvfirst  = db.leave_submission.filter \
            (None, dict (first_day = dt, user = users, status = acc))
        lvlast   = db.leave_submission.filter \
            (None, dict (last_day = dt, user = users, status = acc))
        lvperiod = db.leave_submission.filter(None, flt)
        lvs = dict.fromkeys(lvfirst + lvlast + lvperiod).keys()
        # Put them in a dict by user-id
        self.lvdict = {}
        for id in lvs:
            lv = db.leave_submission.getnode(id)
            if lv.user not in self.lvdict:
                self.lvdict[lv.user] = []
            self.lvdict[lv.user].append(lv)

        # Get all absence records in the given time range, same algo as for
        if 'status' in flt:
            del flt['status']
        abfirst  = db.absence.filter \
            (None, dict (first_day = dt, user = users))
        ablast   = db.absence.filter \
            (None, dict (last_day  = dt, user = users))
        abperiod = db.absence.filter(None, flt)
        abs = dict.fromkeys(abfirst + ablast + abperiod).keys()
        # Put them in a dict by user-id
        self.abdict = {}
        for id in abs:
            ab = db.absence.getnode(id)
            if ab.user not in self.abdict:
                self.abdict[ab.user] = []
            self.abdict[ab.user].append(ab)

        # Get public holidays
        srt = [('+', 'date')]
        ph = db.public_holiday.filter(None, dict(date=dt), sort=srt)
        # Index by location and sort by date
        self.by_location = {}
        for id in ph:
            holiday = db.public_holiday.getnode(id)
            for loc in holiday.locations:
                if loc not in self.by_location:
                    self.by_location[loc] = []
                self.by_location[loc].append(holiday)

        self.abs_v = db.absence_type.getnode(db.absence_type.lookup('V'))
        self.abs_a = db.absence_type.getnode(db.absence_type.lookup('A'))
Exemple #28
0
 def month_link(self, s, e, symbol):
     if 'first_day' not in self.request.filter:
         self.request.filter.append('first_day')
     url = self.request.indexargs_url \
         ('timesheet', dict (first_day = common.pretty_range (s, e)))
     return '<a href="%s">%s</a>' % (url, symbol)
Exemple #29
0
def handle_accept(db, vs, trs, old_status):
    cancr = db.leave_status.lookup('cancel requested')
    warn = []
    if old_status != cancr:
        for trid in trs:
            tr = db.time_record.getnode(trid)
            wp = tp = None
            if tr.wp is not None:
                wp = db.time_wp.getnode(tr.wp)
                tp = db.time_project.getnode(wp.project)
            trd = db.daily_record.get(tr.daily_record, 'date')
            if tp is None or not tp.is_public_holiday:
                if wp is None or wp.id != vs.time_wp:
                    dt = trd.pretty(common.ymd)
                    st = tr.start or ''
                    en = tr.end or ''
                    wn = (wp and wp.name) or ''
                    tn = (tp and tp.name) or ''
                    warn.append((dt, tn, wn, st, en, tr.duration))
                db.time_record.retire(trid)
        d = vs.first_day
        off = db.work_location.lookup('off')
        while (d <= vs.last_day):
            ld = du = vacation.leave_duration(db, vs.user, d)
            dt = common.pretty_range(d, d)
            dr = db.daily_record.filter(None, dict(user=vs.user, date=dt))
            wp = db.time_wp.getnode(vs.time_wp)
            tp = db.time_project.getnode(wp.project)
            if tp.max_hours is not None:
                du = min(ld, tp.max_hours)
            assert len(dr) == 1
            if ld:
                db.time_record.create \
                    ( daily_record  = dr [0]
                    , duration      = du
                    , work_location = off
                    , wp            = vs.time_wp
                    )
            leave = db.daily_record_status.lookup('leave')
            db.daily_record.set(dr[0], status=leave)
            d += common.day
    deleted_records = ''
    if warn:
        d = []
        try:
            d = [db.config.ext.MAIL_LEAVE_USER_ACCEPT_RECS_TEXT]
        except KeyError:
            pass
        tdl = wdl = 0
        for w in warn:
            tdl = max(tdl, len(w[1]))
            wdl = max(wdl, len(w[2]))
        fmt = "%%s: %%%ds / %%%ds %%5s-%%5s duration: %%s" % (tdl, wdl)
        for w in warn:
            d.append(fmt % w)
        deleted_records = '\n'.join(d) + '\n'

    now = Date('.')
    if old_status == cancr:
        try_send_mail \
            ( db, vs, now
            , 'MAIL_LEAVE_USER_NOT_CANCELLED_TEXT'
            , 'MAIL_LEAVE_USER_NOT_CANCELLED_SUBJECT'
            )
    else:
        try_send_mail \
            ( db, vs, now
            , 'MAIL_LEAVE_USER_ACCEPT_TEXT'
            , 'MAIL_LEAVE_USER_ACCEPT_SUBJECT'
            , deleted_records = deleted_records
            )
    if old_status != cancr:
        try_send_mail \
            ( db, vs, now
            , 'MAIL_LEAVE_NOTIFY_TEXT'
            , 'MAIL_LEAVE_NOTIFY_SUBJECT'
            , 'MAIL_LEAVE_NOTIFY_EMAIL'
            )
        if tp.is_special_leave:
            try_send_mail \
                ( db, vs, now
                , 'MAIL_SPECIAL_LEAVE_NOTIFY_TEXT'
                , 'MAIL_SPECIAL_LEAVE_NOTIFY_SUBJECT'
                , 'MAIL_SPECIAL_LEAVE_NOTIFY_EMAIL'
                )
assert len(pwp) == 1
db.query.set \
    ( pwp [0]
    , url = ':columns=name,wp_no,responsible,project,time_start,'
            'time_end,cost_center&:sort=name&:filter=is_public&'
            ':pagesize=20&:startwith=0&is_public=yes'
    )

broken_int = dict.fromkeys \
    (( '2.56'
    ,  ''
    ))
if len(sys.argv) == 2:
    fd = open(sys.argv[1], 'r')
    cr = csv.reader(fd, delimiter=';')
    dt = common.pretty_range(s2014, s2014)
    for line in cr:
        if line[0] == 'Username':
            if line[5] == 'Stand 31.12.2013':
                idx_e = 9
                idx_r = 5
            elif line[2] == 'STAND 31.12.13':
                idx_e = 3
                idx_r = 2
            continue
        username = line[0].strip().lower()
        try:
            user = db.user.lookup(username)
        except KeyError:
            # Special hacks for my data, without disclosing usernames
            if username.startswith('l'):