Exemple #1
0
def compute_running_balance \
    (db, user, start, date, period, sharp_end = False, start_balance = 0.0) :
    """ Compute the overtime balance at the given date.
        if not_after is True, we use the next end of period for
        searching for existing freeze records.

        If sharp_end is True, we compute the exact balance on that day,
        not on the end of previous period.

        If not_after is True, we use the next end of period for
        searching for existing freeze records. This is used for
        freezing: we don't want to find records at the freeze date.
    """
    c_end = end = common.freeze_date (date, period)
    if sharp_end :
        c_end  = date
    p_date     = start
    p_balance  = start_balance
    p_achieved = 0

    corr = overtime_corr (db, user, p_date, c_end)
    while p_date <= end :
        eop = common.end_of_period (p_date, period)
        #print >> sys.stderr, "OTB1: pd: %s d: %s e:%s eop:%s" \
        #    % ( p_date.pretty (ymd)
        #      , date.pretty   (ymd)
        #      , end.pretty    (ymd)
        #      , eop.pretty    (ymd)
        #      )
        pd  = Period_Data (db, user, p_date, eop, period, p_balance, corr)
        p_balance += pd.overtime_balance
        p_achieved = pd.achieved_supp
        #print >> sys.stderr, "OTB1: bal:%.2f as:%.2f" \
        #    % (pd.overtime_balance, pd.achieved_supp)
        p_date = eop + day
    #print "pdate: %(p_date)s, end: %(end)s, date: %(date)s" % locals ()
    #print "bal:", p_balance - start_balance
    assert (p_date <= date + day)
    eop = common.end_of_period (date, period)
    #print >> sys.stderr, "before 2nd call", sharp_end, date, eop, p_date, end
    if sharp_end and date != eop and p_date <= date :
        #print >> sys.stderr, "OTB2: pd: %s d: %s e:%s eop:%s" \
        #    % ( p_date.pretty (ymd)
        #      , date.pretty   (ymd)
        #      , end.pretty    (ymd)
        #      , eop.pretty    (ymd)
        #      )
        pd = Period_Data (db, user, p_date, date, period, p_balance, corr)
        p_balance += pd.overtime_balance
        p_achieved = pd.achieved_supp
        #print >> sys.stderr, "OTB2: bal:%.2f as:%.2f" \
        #    % (pd.overtime_balance, pd.achieved_supp)
        #print eop.pretty (ymd), "%.02f %.02f %.02f" \
        #    % (pd.overtime_balance, pd.achieved_supp, pd.overtime_per_period)
    #print "bal:", p_balance - start_balance
    return p_balance - start_balance, p_achieved
Exemple #2
0
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)
Exemple #3
0
def compute_saved_balance (db, user, start, date, not_after = False) :
    """ Compute the saved overtime balance before or at the given day
        and the date on which this balance is valid.
        
        This looks through saved values in freeze records and determines
        a matching freeze record and returns the value for the given
        weekly/monthly period.

        If not_after is True, we use the next end of period for
        searching for existing freeze records. This is used for
        freezing: we don't want to find records at the freeze date.

        Implementation note: we search for freeze records before the end
        of period one day *after* date. This is exactly the same end of
        period as for date in the usual case. In case date *is* the end
        of period, this will include freeze records with freeze_date ==
        date.
    """
    period = None
    dyn = get_user_dynamic (db, user, date)
    if dyn and dyn.overtime_period :
        period = db.overtime_period.getnode (dyn.overtime_period)
    eop = date
    if period and not not_after :
        eop = common.end_of_period (date + day, period)
    r = freeze.find_prev_dr_freeze (db, user, eop)
    if r :
        achieved = bool (date == r.validity_date) * r.achieved_hours
        return r.balance, r.validity_date, achieved
    return 0.0, None, 0.0
Exemple #4
0
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
Exemple #5
0
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]
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 :
                print 'user_dynamic%-4s: %11s %s-%10s %s-%s %6.2f' % \
                    ( dyn.id
                    , un
                    , d1
                    , d2
Exemple #7
0
    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)