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)
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()
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)]
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]
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
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)
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
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 ()
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)) )
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
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') )
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 ()
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
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'))
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
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
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
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
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
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
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)
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]
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
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'))
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)
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'):