def invalidate_tr_duration (db, uid, v_frm, v_to) : """ Invalidate all cached tr_duration_ok values in all daily records in the given range for the given uid. We modify v_frm and/or v_to if these use required_overtime in their overtime_period: In that case we need to invalidate the whole month. We also invalidate computations on v_to (which is too far) but these get recomputed (we're in a non-frozen range anyway). Make sure the tr_duration_ok is *really* set even if our cached value is None. """ otp = required_overtime (db, uid, v_frm) if otp : start = common.start_of_period (v_frm, otp) if freeze.frozen (db, uid, start) : frz = freeze.find_next_dr_freeze (db, uid, start) start = frz.date assert (start <= v_frm) v_frm = start if v_to is None : pdate = v_frm.pretty (ymd) + ';' else : otp = required_overtime (db, uid, v_to) if otp : v_to = common.end_of_period (v_to, otp) pdate = ';'.join ((v_frm.pretty (ymd), v_to.pretty (ymd))) for dr in db.daily_record.filter (None, dict (date = pdate, user = uid)) : db.daily_record.set (dr, tr_duration_ok = 0) db.daily_record.set (dr, tr_duration_ok = None)
def required_overtime_in_period (db, user, date, period) : """ Loop over a whole period and compute the required overtime in that period and the number of workdays in that period. Return a tuple of overtime, workdays. """ assert period.required_overtime sop = common.start_of_period (date, period) eop = common.end_of_period (date, period) key = (user, str (sop)) if not getattr (db, 'cache_required_overtime_in_period', None) : def cache_required_overtime_in_period_clear (db) : db.cache_required_overtime_in_period = {} db.registerClearCacheCallback \ (cache_required_overtime_in_period_clear, db) db.cache_required_overtime_in_period = {} if key in db.cache_required_overtime_in_period : return db.cache_required_overtime_in_period [key] wd = 0.0 spp = 0.0 date = sop #print >> sys.stderr, sop, eop while date <= eop : dyn = get_user_dynamic (db, user, date) is_wd = is_work_day (dyn, date) if not dyn : wday = gmtime (date.timestamp ())[6] is_wd = wday < 5 wd += 1.0 * is_wd if dyn and period.id == dyn.overtime_period : otd = req_overtime_quotient (db, dyn, user, date) if dyn.supp_per_period : spp += dyn.supp_per_period * otd date += day # otdsum (the sum of all overtime day ratios, sum of otd above) # spp / otdsum is dyn.supp_per_period if supp_per_period doesn't change # otherwise we get a weighted average # then we have to multiply the weighted average with the quotient of # overtime_days and workdays (otd / wd), this yields # (otdsum / wd) * (spp / otdsum) # which can be reduced to (spp / wd) # that's why we don't compute otdsum. if not spp : # probably wd is also 0 here, avoid division by zero r = db.cache_required_overtime_in_period [key] = (0.0, wd) else : r = db.cache_required_overtime_in_period [key] = (spp / wd, wd) return r
def overtime_period (db, user, start, end, period) : """ Return triple start, end, period for the given date which represents a contiguous range of the same overtime_period in this users dynamic user data """ periods = [] sop = common.start_of_period (start, period) eop = common.end_of_period (end, period) otp = overtime_periods (db, user, sop, eop) for s, e, p in otp : #print period.id, p.id, start, s, e, end if ( p.id == period.id and start >= s and start < e or end > s and end <= e ) : periods.append ((s, e, p)) #print user, sop, eop, otp # this fails if somebody has the idea of switching twice within a week or so assert (len (periods) <= 1) if not periods : return None return periods [0]
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 : print 'user_dynamic%-4s: %11s %s-%10s %s-%s %6.2f' % \ ( dyn.id , un , d1 , d2 , sop.pretty (ymd)
def __init__ \ ( self , db , user , start , end , period , start_balance , overtime_corrections = {} ) : use_additional = not period.weekly overtime = 0.0 overtadd = 0.0 required = 0.0 worked = 0.0 over_per = 0.0 over_per_2 = 0.0 days = 0.0 self.achieved_supp = 0.0 self.overtime_balance = 0.0 self.start_balance = start_balance self.period = period date = common.start_of_period (start, self.period) eop = common.end_of_period (end, self.period) try : s, e, p = overtime_period (db, user, start, end, period) except TypeError : s, e, p = eop + day, eop + day, self.period assert (p.id == self.period.id) opp = 0 while date <= eop : days += 1.0 d = date date += day if d < s or d > e : continue dur = durations (db, user, d) if ( dur.dyn and period.required_overtime and dur.dyn.overtime_period == period.id ) : if opp == 0 : opp = dur.req_overtime_pp elif opp != dur.req_overtime_pp : #print >> sys.stderr, "Oops:", opp, dur.req_overtime_pp opp = None over_per += \ ( period.months and dur.dyn and dur.dyn.overtime_period == self.period.id and dur.supp_per_period ) or 0 assert (days) if period.required_overtime : self.overtime_per_period = opp else : self.overtime_per_period = over_per / days date = start while date <= end : dur = durations (db, user, date) work = dur.tr_duration req = dur.day_work_hours over = dur.supp_weekly_hours do_over = use_work_hours (db, dur.dyn, period) oc = overtime_corrections.get (date.pretty (ymd), []) for o in oc : self.overtime_balance += o.value or 0 if use_additional : over = dur.additional_hours if period.required_overtime : over = req + dur.required_overtime overtime += over * do_over if period.months and date <= end : overtadd += dur.additional_hours * do_over required += req * do_over worked += work * do_over eow = common.week_from_date (date) [1] if ( date == eow and period.months and (period.weekly or period.required_overtime) ) : if period.required_overtime : self.overtime_balance += worked - overtime else : if worked > overtadd : self.achieved_supp += min (worked, overtime) - overtadd if worked > overtime : self.overtime_balance += worked - overtime elif worked < required : self.overtime_balance += worked - required overtadd = overtime = worked = required = 0.0 self._consolidate () # increment at end (!) date += day if not period.weekly and not period.required_overtime : overtime += self.overtime_per_period if worked > overtadd and period.months : if period.weekly : self.achieved_supp += min (worked, overtime) - overtadd elif not period.required_overtime : self.achieved_supp += worked - overtadd if worked > overtime : self.overtime_balance += worked - overtime elif period.required_overtime : if worked < overtime : self.overtime_balance += worked - overtime else : if worked < required : self.overtime_balance += worked - required self._consolidate () self.achieved_supp = min (self.achieved_supp, self.overtime_per_period)