Example #1
0
def vac_get_user_dynamic (db, user, ctype, date) :
    """ Get user_dynamic record for a vacation computation on the given
        date. Note that there are cases where no dyn user record exists
        exactly for the date but before -- or after. If the record
        starts a vacation period (e.g. an initial absolute vacation
        correction) there doesn't necessarily already exist a dynamic
        user record. On the other hand when computing the vacation at
        the end of a period no dyn user record may be available anymore
        (e.g., because the person has left).
    """
    dyn = user_dynamic.get_user_dynamic (db, user, date)
    if not dyn :
        dyn = user_dynamic.find_user_dynamic (db, user, date, '-')
    if  (   dyn
        and (  dyn.contract_type != ctype
            or not dyn.vacation_month
            or not dyn.vacation_day
            )
        ) :
        dyn = vac_prev_user_dynamic (db, dyn, ctype)
    if not dyn :
        dyn = user_dynamic.find_user_dynamic (db, user, date, '+')
    if  (   dyn
        and (  dyn.contract_type != ctype
            or not dyn.vacation_month
            or not dyn.vacation_day
            )
        ) :
        dyn = vac_next_user_dynamic (db, dyn, ctype)
    return dyn
Example #2
0
def new_user_dynamic(db, cl, nodeid, new_values):
    common.require_attributes \
        ( _, cl, nodeid, new_values
        , 'user'
        , 'valid_from'
        , 'org_location'
        , 'department'
        )
    user = new_values['user']
    valid_from = new_values['valid_from']
    valid_to = new_values.get('valid_to', None)
    olo = new_values['org_location']
    dept = new_values['department']
    if freeze.frozen(db, user, valid_from):
        raise Reject(_("Frozen: %(valid_from)s") % locals())
    last = user_dynamic.last_user_dynamic(db, user)
    if not valid_to or not last or last.valid_from < valid_from:
        update_user_olo_dept(db, user, olo, dept)
    if 'durations_allowed' not in new_values:
        new_values['durations_allowed'] = False
    new_values ['valid_from'], new_values ['valid_to'] = \
        check_ranges (cl, nodeid, user, valid_from, valid_to)
    check_overtime_parameters(db, cl, nodeid, new_values)
    user_dynamic.invalidate_tr_duration \
        (db, user, new_values ['valid_from'], new_values ['valid_to'])
    orgl = db.org_location.getnode(olo)
    prev_dyn = user_dynamic.find_user_dynamic(db, user, valid_from, '-')
    if 'vacation_month' not in new_values and 'vacation_day' not in new_values:
        if prev_dyn:
            new_values['vacation_month'] = prev_dyn.vacation_month
            new_values['vacation_day'] = prev_dyn.vacation_day
        elif orgl.vacation_legal_year:
            new_values['vacation_month'] = 1
            new_values['vacation_day'] = 1
        else:
            d = new_values['valid_from']
            month, mday = (int(x) for x in d.get_tuple()[1:3])
            if month == 2 and mday == 29:
                mday = 28
            new_values['vacation_month'] = month
            new_values['vacation_day'] = mday
    if 'vacation_yearly' not in new_values:
        if prev_dyn:
            new_values['vacation_yearly'] = prev_dyn.vacation_yearly
        elif orgl.vacation_yearly:
            new_values['vacation_yearly'] = orgl.vacation_yearly
    check_vacation(db, cl, nodeid, 'vacation_yearly', new_values)
    check_weekly_hours(db, cl, nodeid, new_values)
     # Special hacks for my data, without disclosing usernames
     if username.startswith('l'):
         username = '******' + username[1:]
     elif username.startswith('g'):
         username = username[:4] + 'r' + username[4:]
     else:
         username = '******' + username
     user = db.user.lookup(username)
 rounded = line[idx_r]
 exact = float(line[idx_e])
 if rounded not in broken_int:
     assert int(rounded) == float(rounded)
     assert ceil(exact) == int(rounded)
 dyn = user_dynamic.get_user_dynamic(db, user, s2014)
 if not dyn:
     dyn = user_dynamic.find_user_dynamic(db, user, s2014)
 while dyn:
     d = {}
     if dyn.vacation_yearly is None:
         d['vacation_yearly'] = 25.0
     if dyn.vacation_day is None or dyn.vacation_month is None:
         d['vacation_day'] = 1
         d['vacation_month'] = 1
     if d:
         assert 'vacation_yearly' in d
         print "WARN: dyn %s/%s had no vacation_yearly" \
             % (username, dyn.valid_from.pretty (common.ymd))
         db.user_dynamic.set(dyn.id, **d)
     dyn = user_dynamic.next_user_dynamic(db, dyn)
 vc = db.vacation_correction.filter \
     (None, dict (user = user, date = dt, absolute = True))
Example #4
0
def check_avc(db, cl, nodeid, new_values):
    """ Check that an absolute vacation correction exists at the date of
        the user_dynamic record if either the vac_aliq changed or the
        vac_aliq is monthly and the vacation changed (compared to the
        last user_dynamic record). We currently do not require a
        vacation correction if one already exists *and* there is a gap
        in user_dynamic record validity ranges.
    """
    # At least one of the following attributes must be in new_values
    # otherwise we don't have anything to check.
    attrs = ('vac_aliq', 'vacation_yearly', 'valid_from')
    for a in attrs:
        if a in new_values:
            break
    else:
        return
    va = new_values.get('vac_aliq')
    if not va and nodeid:
        va = cl.get(nodeid, 'vac_aliq')
    vy = new_values.get('vacation_yearly')
    if vy is None and nodeid:
        vy = cl.get(nodeid, 'vacation_yearly')
    # User must exist
    user = new_values.get('user')
    if not user:
        user = cl.get(nodeid, 'user')
    # valid_from must exist
    valid_from = new_values.get('valid_from')
    if not valid_from:
        valid_from = cl.get(nodeid, 'valid_from')
    prev_dyn = user_dynamic.find_user_dynamic(db, user, valid_from, '-')
    if not prev_dyn:
        return
    # Check if there is an absolute vacation correction for our
    # valid_from, everything ok if there is:
    dt = valid_from.pretty(common.ymd)
    vcs = db.vacation_correction.filter \
        (None, dict (user = user, date = dt, absolute = True))
    assert len(vcs) <= 1
    if not vcs:
        # Find the next vc backwards and check if it is at the end of
        # employment
        vcs = db.vacation_correction.filter \
            ( None
            , dict (user = user, date = ';%s' % dt, absolute = True)
            , sort = ('+', 'date')
            )
        if vcs:
            vc = db.vacation_correction.getnode(vcs[0])
            day = common.day
            # - day because we don't want to find the currently-change dyn
            vcdyn = user_dynamic.last_user_dynamic(db, user, valid_from - day)
            if not vcdyn.valid_to or vcdyn.valid_to > vc.date:
                vcs = []
    if vcs:
        assert db.vacation_correction.get(vcs[0], 'date') <= valid_from
        return
    if prev_dyn.vac_aliq != va:
        van = _('vac_aliq')
        raise Reject \
            ( _ ('Change of "%(van)s" without absolute vacation correction')
            % locals ()
            )
    vyo = prev_dyn.vacation_yearly
    if db.vac_aliq.get(va, 'name') == 'Monthly' and vy != vyo:
        vyn = _('vacation_yearly')
        raise Reject \
            ( _ ('Change of "%(vyn)s" without absolute vacation correction')
            % locals ()
            )
def check_auto_wp (db, auto_wp_id, userid) :
    """ Get latest non-frozen dynamic user record for this user and then
        look through auto WPs and check if they conform: There should be
        a WP for each contiguous time a dynamic user record with
        do_auto_wp set is available for this user.
    """
    user   = db.user.getnode (userid)
    snam   = str (user.username).split ('@') [0]
    desc   = "Automatic wp for user %s" % snam
    # Very early default should we not find any freeze records
    start  = Date ('1970-01-01')
    freeze = freeze_date (db, userid)
    auto_wp = db.auto_wp.getnode (auto_wp_id)
    duration_end = auto_wp_duration_end (db, auto_wp, userid)
    # Nothing todo if this ended all before freeze
    if duration_end and duration_end < start :
        return
    tp  = db.time_project.getnode (auto_wp.time_project)
    # Get dynamic user record on or after start
    dyn = find_user_dynamic (db, userid, start, ct = auto_wp.contract_type)
    # If auto_wp isn't valid, we set dyn to None this will invalidate
    # all WPs found:
    if not auto_wp.is_valid :
        dyn = None
    # Find first dyn with do_auto_wp and org_location properly set
    while dyn and not is_correct_dyn (dyn, auto_wp) :
        dyn = next_user_dynamic (db, dyn, use_ct = True)
    # Find all wps auto-created from this auto_wp after start.
    # Note that all auto wps have a start time. Only the very first wp
    # may start *before* start (if the time_end of the time_wp is either
    # empty or after start)
    d = dict \
        ( auto_wp    = auto_wp_id
        , time_start = pretty_range (start)
        , bookers    = userid
        )
    wps = db.time_wp.filter (None, d, sort = ('+', 'time_start'))
    wps = [db.time_wp.getnode (w) for w in wps]
    d ['time_start'] = pretty_range (None, start)
    wp1 = db.time_wp.filter (None, d, sort = ('-', 'time_start'), limit = 1)
    assert len (wp1) <= 1
    if wp1 :
        wp = db.time_wp.getnode (wp1 [0])
        is_same = wps and wps [0].id == wp.id
        if not is_same and not wp.time_end or wp.time_end > start :
            wps.insert (0, wp)
    # Now we have the first relevant dyn user record and a list of WPs
    # The list of WPs might be empty
    try :
        wp = wps.pop (0)
    except IndexError :
        wp = None

    while dyn :
        # Remember the start time of the first dyn
        dyn_start = dyn.valid_from
        # we compute the range of contiguous dyns with same do_auto_wp setting
        while dyn and dyn.valid_to :
            n = next_user_dynamic (db, dyn, use_ct = True)
            if  (   n
                and is_correct_dyn (n, auto_wp)
                and n.valid_from == dyn.valid_to
                ) :
                dyn = n
            else :
                break
        end_time = dyn.valid_to
        if duration_end and (not end_time or end_time > duration_end) :
            end_time = duration_end
        if end_time and end_time <= dyn_start :
            dyn = None
            break
        # Limit the first wp(s) if the start time of the dyn user record is
        # after the start time of the frozen range and the wp starts
        # before that.
        while wp and wp.time_start < start and dyn_start > start :
            if wp.time_end > start :
                n = '%s -%s' % (snam, start.pretty (ymd))
                d = dict (time_end = start, name = n)
                if wp.description != desc :
                    d ['description'] = desc
                db.time_wp.set (wp.id, **d)
            try :
                wp = wps.pop (0)
            except IndexError :
                wp = None
        assert not wp or wp.time_start > start

        # Check if there are wps that start before the validity of the
        # current dynamic user record
        while wp and wp.time_start < dyn_start :
            if wp.time_end and wp.time_end <= dyn_start :
                # Check that really nothing is booked on this WP
                d = dict (wp = wp.id)
                d ['daily_record.user'] = userid
                d ['daily_record.date'] = pretty_range \
                    (wp.time_start, wp.time_end)
                trs = db.time_record.filter (None, d)
                # If something is booked we set end = start
                # Otherwise we retire the wp
                if not trs :
                    db.time_wp.retire (wp.id)
                else :
                    n = '%s -%s' % (snam, wp.time_start.pretty (ymd))
                    d = dict (time_end = wp.time_start, name = n)
                    if wp.description != desc :
                        d ['description'] = desc
                    db.time_wp.set (wp.id, **d)
                try :
                    wp = wps.pop (0)
                except IndexError :
                    wp = None
            else :
                d = dict (time_start = dyn_start)
                if wp.description != desc :
                    d ['description'] = desc
                db.time_wp.set (wp.id, **d)
                break

        # We now either have a wp with correct start date or
        # with a start date > dyn.valid_to or none
        if wp and (not dyn.valid_to or wp.time_start < dyn.valid_to) :
            # We may have not a single wp but a set of 'fragments'
            # before the end-date of the dyn user record (or an open
            # end)
            while (   wps
                  and (  end_time and wp.time_end < end_time
                      or not end_time and wp.time_end
                      )
                  ) :
                if wps [0].time_start > end_time :
                    break
                assert not wps [0].time_end or wp.time_end <= wps [0].time_end
                if wp.time_end != wps [0].time_start :
                    n = '%s -%s' % (snam, wps [0].time_start.pretty (ymd))
                    d = dict (time_end = wps [0].time_start, name = n)
                    if wp.description != desc :
                        d ['description'] = desc
                    db.time_wp.set (wp.id, ** d)
                wp = wps.pop (0)
            if end_time :
                if wp.time_end != end_time :
                    n = '%s -%s' % (snam, end_time.pretty (ymd))
                    d = dict (time_end = end_time, name = n)
                    if wp.description != desc :
                        d ['description'] = desc
                    db.time_wp.set (wp.id, **d)
            else :
                if wp.time_end :
                    d = dict (time_end = None, name = snam)
                    if wp.description != desc :
                        d ['description'] = desc
                    db.time_wp.set (wp.id, **d)
            try :
                wp = wps.pop (0)
            except IndexError :
                wp = None
        else :
            # If we have no wp, create one
            d = dict \
                ( auto_wp           = auto_wp_id
                , project           = auto_wp.time_project
                , durations_allowed = auto_wp.durations_allowed
                , bookers           = [userid]
                , description       = desc
                , name              = snam
                , time_start        = max (start, dyn_start)
                , is_public         = False
                , planned_effort    = 0
                , responsible       = tp.responsible
                )
            # If the dyn is time-limited we have to name the wp
            # appropriately and limit the end-time of the wp.
            if end_time :
                d ['time_end'] = end_time
                d ['name']     = '%s -%s' % (snam, end_time.pretty (ymd))
            wpid = db.time_wp.create (**d)
        od  = dyn
        dyn = next_user_dynamic (db, dyn, use_ct = True)
        while dyn and not is_correct_dyn (dyn, auto_wp) :
            dyn = next_user_dynamic (db, dyn, use_ct = True)
        if not dyn :
            if not end_time :
                assert not wp and not wps
    if wp :
        wps.insert (0, wp)
        wp = None
        # Retire WPs that are left over but before that check that
        # nothing is booked.
        for wp in wps :
            d = dict (wp = wp.id)
            d ['daily_record.user'] = userid
            trs = db.time_record.filter (None, d)
            if not trs :
                db.time_wp.retire (wp.id)
            else :
                n = '%s -%s' % (snam, wp.time_start.pretty (ymd))
                d = dict (time_end = wp.time_start, name = n)
                if wp.description != desc :
                    d ['description'] = desc
                db.time_wp.set (wp.id, **d)
        wps = []
        wp  = None