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
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))
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