Пример #1
0
def get_vacation_correction (db, user, ctype = -1, date = None) :
    """ Get latest absolute vacation_correction.
        Special handling of ctype: None means ctype 'None' while -1
        means "don't care, search for *any* ctype". Note that roundups
        interface for searching specifies -1 when searching for an
        empty link....
    """
    if date is None :
        date = Date ('.')
    dt = ";%s" % date.pretty (common.ymd)
    d = dict \
        ( user          = user
        , absolute      = True
        , date          = dt
        )
    # If no ctype given, try to get dyn. user record on date and use
    # ctype from there. If not found we simply search for the latest
    # vacation correction before date.
    if ctype == -1 :
        dyn = user_dynamic.get_user_dynamic (db, user, date)
        if dyn :
            ctype = dyn.contract_type
    if ctype != -1 :
        d ['contract_type'] = ctype
        if ctype is None :
            d ['contract_type'] = '-1' # roundup: -1 means search empty
    vcs = db.vacation_correction.filter (None, d, sort = [('-', 'date')])
    if not vcs :
        return
    for id in vcs :
        vc = db.vacation_correction.getnode (id)
        if ctype == -1 or vc.contract_type == ctype :
            return vc
Пример #2
0
def get_user_dynamic (db, user, date) :
    """ Get a user_dynamic record by user and date.
        Return None if no record could be found.
    """
    user = str  (user)
    date = Date (date)
    # last_dynamic: Simple one-element cache as attribute of db
    try :
        if  (   db.last_dynamic
            and db.last_dynamic.user == user
            and db.last_dynamic.valid_from < date
            and (  not db.last_dynamic.valid_to
                or db.last_dynamic.valid_to > date
                )
            ) :
            return db.last_dynamic
    except AttributeError :
        db.last_dynamic = None
        def last_dynamic_clear (db) :
            db.last_dynamic = None
        db.registerClearCacheCallback (last_dynamic_clear, db)
    ids = db.user_dynamic.filter \
        ( None, dict (user = user, valid_from = date.pretty (';%Y-%m-%d'))
        , group = ('-', 'valid_from')
        )
    if ids :
        db.last_dynamic = db.user_dynamic.getnode (ids [0])
        if not db.last_dynamic.valid_to or db.last_dynamic.valid_to > date :
            return db.last_dynamic
    return None
def check_start_end_duration \
    (date, start, end, duration, new_values, dist = 0) :
    """
        either duration or both start/end must be set but not both
        of duration/end
        set duration from start/end if duration empty
        set end from start/duration if end empty
        Note: We are using naive times (with timezone 0) here, this
        means we can safely use date.pretty for converting back to
        string.
    """
    dstart = dend = None
    if dist :
        check_duration (dist)
    if start and ":" not in start :
        start = start + ":00"
    if end   and ":" not in end :
        end   = end   + ":00"
    if 'end' in new_values :
        if not start :
            attr = _ ('start')
            raise Reject, _ (''"%(attr)s must be specified") % locals ()
        if 'duration' in new_values :
            raise Reject, _ (''"Either specify duration or start/end")
        dstart, dend, sp, ep, dur = check_timestamps (start, end, date)
        duration                = dur
        new_values ['duration'] = duration
        new_values ['start']    = sp
        new_values ['end']      = ep
    else :
        check_duration (duration, 24)
        if 'duration' in new_values :
            new_values ['duration'] = duration
        if start :
            if 'start' in new_values or 'duration' in new_values :
                minutes = duration * 60
                hours   = int (duration % 60)
                minutes = minutes - hours * 60
                ds      = Date (start, offset = 0)
                t = (ds + Interval ('%d:%d' % (hours, minutes))).pretty \
                    (hour_format)
                if duration > 0 and t == '00:00' :
                    t = '24:00'
                dstart, dend, sp, ep, dur = check_timestamps (start, t, date)
                assert dur == duration
                new_values ['start'] = sp
                new_values ['end']   = ep
    if dist and dist < duration :
        duration -= dist
        if start :
            hours   = int (dist)
            minutes = (dist - hours) * 60
            if not dstart :
                dstart = Date (start, offset = 0)
            dstart = dstart + Interval ('%d:%d' % (hours, minutes))
            new_values ['start'] = dstart.pretty (hour_format)
        new_values ['duration']  = duration
    return dstart, dend
def check_timestamps (start, end, date) :
    t = end
    if end == '24:00' :
        t = '00:00'
    dstart = Date (start, offset = 0)
    dend   = Date (t,     offset = 0)
    dstart.year  = dend.year  = date.year
    dstart.month = dend.month = date.month
    dstart.day   = dend.day   = date.day
    if end == '24:00' :
        dend += common.day
        dend.hours = dend.seconds = dend.minutes = 0
    if dstart > dend :
        raise Reject, _ ("start and end must be on same day and start <= end.")
    if dstart.timestamp () % 900 or dend.timestamp () % 900 :
        raise Reject, _ ("Times must be given in quarters of an hour")
    dur = (dend - dstart).as_seconds () / 3600.
    ep  = dend.pretty (hour_format)
    if end == '24:00' :
        assert ep == '00:00'
        ep = end
    return dstart, dend, dstart.pretty (hour_format), ep, dur
Пример #5
0
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
Пример #6
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
Пример #7
0
def correct_midnight_date_string (db) :
    """returns GMT's "today.midnight" in localtime format.
    suitable for passing in to forms that need this date.
    """
    d   = Date ('00:00', -db._db.getUserTimezone ())
    return d.pretty ('%Y-%m-%d.%H:%M:%S')
Пример #8
0
def consolidated_vacation \
    (db, user, ctype = -1, date = None, vc = None, to_eoy = True) :
    """ Compute remaining vacation on the given date
    """
    if date is None :
        date = Date ('.')
    if ctype == -1 :
        ctype = _get_ctype (db, user, date)
    if ctype == -1 :
        return
    vc  = vc or get_vacation_correction (db, user, ctype, date)
    if not vc :
        return None
    ed  = next_yearly_vacation_date (db, user, ctype, date)
    if not to_eoy :
        ed = min (ed, date + common.day)
    d   = vc.date
    dyn = vac_get_user_dynamic (db, user, ctype, d)
    while dyn and dyn.valid_to and dyn.valid_to <= d :
        dyn = vac_next_user_dynamic (db, dyn)
    if dyn is None :
        return None
    vac = float (vc.days)
    msg = "vac_aliq None for user_dynamic%s" % dyn.id
    assert dyn.vac_aliq, msg
    va = db.vac_aliq.getnode (dyn.vac_aliq)
    assert va.name in ('Daily', 'Monthly')
    # Need to skip first period without a dyn user record
    # sd is the current start date for german aliquotation
    # We subtract 1 day to easily compare the day of the ending-date
    # with the day of the start date
    sd = d
    # This is used for corrections if the start day lies beyond 28 -- in
    # that case there are months that simply don't have that date. So we
    # must correct for this in months with less days.
    sd_day = 0
    if dyn.valid_from > d :
        sd = d = dyn.valid_from
    while dyn and d < ed :
        if dyn.valid_from > d :
            # We want to check if the days that are lost here whenever a
            # jump in dyn user records occurs are OK for monthly aliqotation
            sd = d = dyn.valid_from
            continue
        assert not dyn.valid_to or dyn.valid_to > d
        eoy = Date ('%s-12-31' % d.year)
        msg = "vacation_yearly None for user_dynamic%s" % dyn.id
        assert dyn.vacation_yearly is not None, msg
        msg = ( "vac_aliq changes w/o absolute vac_corr for user_dynamic%s"
              % dyn.id
              )
        assert dyn.vac_aliq == va.id, msg
        if dyn.valid_to and dyn.valid_to <= ed and dyn.valid_to < eoy :
            if va.name == 'Daily' :
                yd = float (common.ydays (dyn.valid_to))
                vac += interval_days \
                    (dyn.valid_to - d) * dyn.vacation_yearly / yd
            else :
                md  = month_diff (sd, dyn.valid_to)
                dy  = sd_day or sd.day
                if dyn.valid_to.day < dy :
                    md -= 1
                    # Example: sd = 2018-04-03 valid_to = 2018-06-01
                    # Need to set sd=2018-05-03, i.e. the next start
                    # day before valid_to
                    # Even more complex is the case where e.g.
                    # sd = 2018-03-31 valid_to = 2018-05-01
                    # We set sd=2018-04-30 and sd_day=31
                    # Get last day of last month
                    lm = dyn.valid_to - Interval ('%sd' % dyn.valid_to.day)
                    em = common.end_of_month (lm)
                    if dy > em.day :
                        sd_day = sd.day
                        sd = em
                    else :
                        sd = Date (lm.pretty ("%%Y-%%m-%s" % sd.day))
                        sd_day = 0
                else :
                    sd = Date (dyn.valid_to.pretty ("%%Y-%%m-%s" % sd.day))
                    sd_day = 0
                d = dyn.valid_to
                vac += dyn.vacation_yearly * md / 12.0
            dyn = vac_next_user_dynamic (db, dyn)
        elif eoy < ed :
            if va.name == 'Daily' :
                yd = float (common.ydays (eoy))
                iv = eoy + common.day - d
                vac += interval_days (iv) * dyn.vacation_yearly / yd
            else :
                md  = month_diff (sd, eoy)
                dy  = sd_day or sd.day
                assert eoy.day >= dy
                if dy == 1 :
                    md += 1
                    sd = eoy + common.day
                else :
                    sd = Date (eoy.pretty ("%%Y-%%m-%s" % sd.day))
                sd_day = 0
                vac += dyn.vacation_yearly * md / 12.0
            d  = eoy + common.day
            if dyn.valid_to == d :
                dyn = vac_next_user_dynamic (db, dyn)
        else :
            if va.name == 'Daily' :
                yd = float (common.ydays (ed - common.day))
                vac += interval_days (ed - d) * dyn.vacation_yearly / yd
            else :
                md = month_diff (sd, ed)
                dy  = sd_day or sd.day
                if ed.day < dy :
                    md -= 1
                sd = ed
                vac += dyn.vacation_yearly * md / 12.0
            d = ed
    return vac
Пример #9
0
class Report (object) :

    def __init__ \
        (self, db, date
        , send_mail = False
        , users     = []
        , mailall   = []
        , do_nosy   = False
        ) :
        self.db     = db
        self.date   = Date (date)
        self.output = self.print_results
        if send_mail :
            self.output = self.mail_results
        self.users = list (users)
        if self.users :
            self.users.extend (mailall)
        self.now = Date ('.')
        stati = dict ((db.status.get (i, 'name'), i)
                      for i in db.status.getnodeids ())
        for n in 'deferred', 'done-cbb', 'resolved' :
            del stati [n]
        self.user_reports = {}
        issues = db.issue.filter \
            ( None
            , dict 
                ( status = stati.values ()
                , deadline = ';%s' % self.date.pretty ('%Y-%m-%d.%H:%M:%S')
                )
            , sort = [('+', 'deadline'), ('+', 'priority')]
            )
        for i in issues :
            n = db.issue.getnode (i)

            if n.assignedto :
                u = self.add_user (db.user.get (n.assignedto, 'address'))
                if u :
                    u.report_lines.append (ReportLine (n))
            if do_nosy :
                for uid in n.nosy :
                    if uid != n.assignedto :
                        u = self.add_user (db.user.get (uid, 'address'))
                        if u :
                            u.nosy_lines.append (ReportLine (n))
            for m in mailall :
                u = self.add_user (m)
                if u :
                    u.all_lines.append (ReportLine (n))

    def add_user (self, email) :
        if self.users and email not in self.users :
            return None
        if email not in self.user_reports :
            self.user_reports [email] = UserReport (self, email)
        return self.user_reports [email]

    def print_results (self) :
        for email, report in self.user_reports.iteritems () :
            print "%s:\n" % email, str (report)

    def mail_results (self) :
        smtp = SMTP (self.db.config.MAIL_HOST)
        header = \
            [ "Subject: Summary of pending bug reports until %s"
            % self.date.pretty ('%Y-%m-%d')
            ]
        for report in self.user_reports.values () :
            try :
                report.mail (smtp, header)
            except SMTPRecipientsRefused, cause :
                print >> sys.stderr, cause
Пример #10
0
def month_name(date):
    d = Date(date)
    return d.pretty('%B')
Пример #11
0
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
Пример #12
0
def new_time_record (db, cl, nodeid, new_values) :
    """ auditor on time_record
    """
    uid    = db.getuid ()
    travel = False
    common.require_attributes (_, cl, nodeid, new_values, 'daily_record')
    common.reject_attributes  (_, new_values, 'dist', 'tr_duration')
    check_generated (new_values)
    dr       = db.daily_record.getnode (new_values ['daily_record'])
    uname    = db.user.get (dr.user, 'username')
    if dr.status != db.daily_record_status.lookup ('open') and uid != '1' :
        raise Reject, _ ('Editing of time records only for status "open"')
    if frozen (db, dr.user, dr.date) :
        date = dr.date
        raise Reject, _ ("Frozen: %(uname)s, %(date)s") % locals ()
    start    = new_values.get ('start',    None)
    end      = new_values.get ('end',      None)
    duration = new_values.get ('duration', None)
    wpid     = new_values.get ('wp')
    ttby     = db.user.get (dr.user, 'timetracking_by')
    if  (   uid != dr.user
        and uid != ttby
        and not common.user_has_role (db, uid, 'controlling', 'admin')
        and not leave_wp (db, dr, wpid, start, end, duration)
        and not vacation_wp (db, wpid)
        ) :
        raise Reject, _ \
            ( ("Only %(uname)s, Timetracking by, and Controlling "
               "may create time records"
              )
            % locals ()
            )
    dynamic  = user_dynamic.get_user_dynamic (db, dr.user, dr.date)
    date     = dr.date.pretty (common.ymd)
    if not dynamic :
        if uid != '1' :
            raise Reject, _ \
                ("No dynamic user data for %(uname)s, %(date)s") % locals ()
    else :
        if not dynamic.booking_allowed and uid != '1' :
            raise Reject, _ \
                ("Booking not allowed for %(uname)s, %(date)s") % locals ()
        if not (dr.weekend_allowed or dynamic.weekend_allowed) and uid != '1' :
            wday = gmtime (dr.date.timestamp ())[6]
            if wday in (5, 6) :
                raise Reject, _ ('No weekend booking allowed')
    dstart, dend = check_start_end_duration \
        (dr.date, start, end, duration, new_values)
    # set default work location for new time record by work location ID
    # for ID reference values check file initial_data.py or use endpoint
    # /work_location in the web interface
    if 'work_location' not in new_values :
        new_values ['work_location'] = '2'
    # set default values according to selected work package
    if 'wp' in new_values and new_values ['wp'] :
        wp = new_values ['wp']
        # overwrite work location default if default specified in time category
        correct_work_location (db, wp, new_values)
        travel = travel or db.time_wp.get (wp, 'travel')
    if 'time_activity' in new_values and new_values ['time_activity'] :
        act    = new_values ['time_activity']
        travel = travel or db.time_activity.get (act, 'travel')
    duration = new_values.get ('duration', None)
    ls       = Date (db.user.get (dr.user, 'lunch_start') or '12:00')
    ls.year  = dr.date.year
    ls.month = dr.date.month
    ls.day   = dr.date.day
    ld       = db.user.get (dr.user, 'lunch_duration') or 1
    hours    = int (ld)
    minutes  = (ld - hours) * 60
    le       = ls + Interval ('%d:%d' % (hours, minutes))
    if not travel and duration > 6 and start and dstart < ls and dend > ls :
        newrec  = { 'daily_record' : new_values ['daily_record']
                  , 'start'        : le.pretty (hour_format)
                  }

        dur1    = (ls - dstart).as_seconds () / 3600.
        dur2    = duration - dur1
        if end :
            dur2 -= ld
        newrec ['duration']     = dur2
        for attr in 'wp', 'time_activity', 'work_location' :
            if attr in new_values and new_values [attr] :
                newrec [attr] = new_values [attr]
        new_values ['end']      = ls.pretty (hour_format)
        new_values ['duration'] = dur1
        if dur2 > 0 :
            db.time_record.create (** newrec)
Пример #13
0
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
# Cron-job for updating adr-type for valid or closed abos.
# Should be run in the early morning of every 1st of a month.

import sys
from roundup.date      import Date
from roundup           import instance
tracker = instance.open (sys.argv [1])
db      = tracker.open  ('admin')

now           = Date ('.')
type_cat      = db.adr_type_cat.lookup ('ABO')
abo_adr_types = db.adr_type.find (typecat = type_cat)
adr           = db.address.filter (None, {'adr_type' : abo_adr_types})
adr           = dict ([(k, 1) for k in adr])

valid_abos    = db.abo.filter (None, {'end' : '-1'})
storno_abos   = db.abo.filter (None, {'end' : now.pretty (';%Y-%m-%d')})

for abo in valid_abos + storno_abos :
    adr [db.abo.get (abo, 'subscriber')] = 1

for a in adr.iterkeys () :
    # May seem like a noop -- leave the correct updating to the auditor.
    db.address.set (a, adr_type = db.address.get (a, 'adr_type'))
db.commit ()
Пример #14
0
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