def flexi_remain (db, user, date_in_year, ctype) : y = common.start_of_year (date_in_year) eoy = common.end_of_year (y) fa = flexi_alliquot (db, user, date_in_year, ctype) acpt = db.leave_status.lookup ('accepted') cnrq = db.leave_status.lookup ('cancel requested') if not fa : return 0 sd = 0 dyn = user_dynamic.get_user_dynamic (db, user, y) if not dyn : dyn = user_dynamic.first_user_dynamic (db, user, y) while dyn : # Check the case that we found a dyn user record far in the past if dyn.valid_to and dyn.valid_to < y : dyn = user_dynamic.next_user_dynamic (db, dyn) continue if dyn.contract_type == ctype : b = dyn.valid_from if b < y : b = y if b > eoy : break e = dyn.valid_to if e > eoy or not e : e = eoy else : e -= common.day ct = dyn.contract_type if dyn.all_in : sd += flexitime_submission_days (db, user, ct, b, e, acpt, cnrq) dyn = user_dynamic.next_user_dynamic (db, dyn) return fa - sd
def vac_next_user_dynamic (db, dyn, ctype = -1) : if ctype == -1 : ctype = dyn.contract_type dyn = user_dynamic.next_user_dynamic (db, dyn) while ( dyn and ( dyn.contract_type != ctype or not dyn.vacation_month or not dyn.vacation_day ) ) : dyn = user_dynamic.next_user_dynamic (db, dyn) return dyn
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)
from roundup.password import Password, encodePassword dir = os.getcwd () sys.path.insert (0, os.path.join (dir, 'lib')) from user_dynamic import first_user_dynamic, next_user_dynamic, compute_balance from common import start_of_period, end_of_period, day tracker = instance.open (dir) db = tracker.open ('admin') ymd = '%Y-%m-%d' for u in db.user.getnodeids () : lop = None dyn = first_user_dynamic (db, u) while dyn : lop = dyn.overtime_period dt = dyn.valid_to dyn = next_user_dynamic (db, dyn) if not dt and dyn : dt = dyn.valid_from if lop and dyn and dyn.overtime_period is None : otp = db.overtime_period.getnode (lop) eop = end_of_period (dt - day, otp) sop = start_of_period (dt - day, otp) is_eop = eop == dt - day un = db.user.get (dyn.user, 'username') d1 = dyn.valid_from.pretty ('%Y-%m-%d') d2 = '' if dyn.valid_to : d2 = dyn.valid_to.pretty ('%Y-%m-%d') b1, bla = compute_balance (db, u, sop - day, sharp_end = True) b2, bla = compute_balance (db, u, dt - day, sharp_end = True) if not is_eop :
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"))
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)) if not vc: db.vacation_correction.create \ (absolute = True, date = s2014, user = user, days = exact) db.commit()
def find_existing_leave(db, cl, nodeid, new_values): """ Search for existing leave requests when start/end date changes. Reject if valid records are found. """ stati = ('open', 'submitted', 'accepted', 'cancel requested') stati = list(db.leave_status.lookup(x) for x in stati) if 'valid_to' not in new_values and 'valid_from' not in new_values: return dyn = cl.getnode(nodeid) if 'valid_to' in new_values: # check if another dynamic user record exists valid_to = new_values['valid_to'] if valid_to is not None: next = user_dynamic.next_user_dynamic(db, dyn) if not next or next.valid_from > valid_to: leaves = db.leave_submission.filter \ ( None , dict ( user = dyn.user , last_day = common.pretty_range (valid_to) , status = stati ) ) if next: new_leaves = [] for id in leaves: leave = db.leave_submission.getnode(id) if valid_to <= leave.first_day < next.valid_from: new_leaves.append(id) continue if valid_to <= leave.last_day < next.valid_from: new_leaves.append(id) continue if (leave.first_day < valid_to and leave.last_day >= next.valid_from): new_leaves.append(id) continue leaves = new_leaves if leaves: raise Reject \ (_ ("There are open leave requests at or after %s" % valid_to.pretty (common.ymd) ) ) if 'valid_from' in new_values: # check if another dynamic user record exists valid_from = new_values['valid_from'] prev = user_dynamic.prev_user_dynamic(db, dyn) if not prev or prev.valid_to < valid_from: leaves = db.leave_submission.filter \ ( None , dict ( user = dyn.user , first_day = common.pretty_range (None, valid_from - common.day) , status = stati ) ) if prev: new_leaves = [] for id in leaves: leave = db.leave_submission.getnode(id) if prev.valid_to <= leave.first_day < valid_from: new_leaves.append(id) continue if prev.valid_to <= leave.last_day < valid_from: new_leaves.append(id) continue if (leave.first_day < prev.valid_to and leave.last_day >= valid_from): new_leaves.append(id) continue leaves = new_leaves if leaves: raise Reject \ (_ ("There are open leave requests before %s" % valid_from.pretty (common.ymd) ) )
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