Exemple #1
0
def daily_record_viewable(db, userid, itemid):
    """User may view a daily_record (and time_records that are attached
       to that daily_record) if the user owns the daily_record or has
       role 'HR' or 'Controlling', or the user is supervisor or
       substitute supervisor of the owner of the daily record (the
       supervisor relationship is transitive) or the user is the
       department manager of the owner of the daily record.
       If user has role HR-Org-Location and is in the same Org-Location
       as the record, it may also be seen.
    """
    if common.user_has_role(db, userid, 'HR', 'Controlling'):
        return True
    dr = db.daily_record.getnode(itemid)
    if userid == dr.user:
        return True
    if user_dynamic.hr_olo_role_for_this_user(db, userid, dr.user, dr.date):
        return True
    # find departments managed by userid
    deps = db.department.filter(None, dict(manager=userid))
    # find all users which are member of above departments:
    depusers = {}
    if deps:
        depusers = dict.fromkeys \
            (db.user.filter (None, dict (department = deps)))
    return \
        (  dr.user in depusers
        or dr.user in supervised_users (db, userid)
        )
 def approver_non_finance (db, userid, itemid) :
     """ Approvers are allowed if not finance and PR not yet approved
         by finance.
     """
     if not itemid or itemid < 1 :
         return False
     # User may not change
     if own_pr (db, userid, itemid) :
         return False
     # Finance may not change
     if common.user_has_role (db, userid, 'finance') :
         return False
     pr = db.purchase_request.getnode (itemid)
     st_approving = db.pr_status.lookup ('approving')
     if pr.status != st_approving :
         return False
     un = db.pr_approval_status.lookup ('undecided')
     ap = db.pr_approval.filter (None, dict (purchase_request = itemid))
     for id in ap :
         a = db.pr_approval.getnode (id)
         # already signed by finance?
         finance = db.pr_approval_order.lookup ('finance')
         if a.status != un and a.role_id == finance :
             return False
     return linked_pr (db, userid, itemid)
Exemple #3
0
 def handle (self) :
     self.request = templating.HTMLRequest (self.client)
     assert \
         (   self.request.classname
         and self.request.classname == 'user_dynamic'
         and self.client.nodeid
         )
     id       = self.client.nodeid
     dyn      = self.db.user_dynamic.getnode (id)
     perm     = self.db.security.hasPermission
     if not common.user_has_role (self.db, self.db.getuid (), 'HR') :
         raise Reject, "Not allowed"
     fields   = user_dynamic.dynuser_copyfields + ['valid_to']
     param    = dict ((i, dyn [i]) for i in fields)
     if dyn.valid_to :
         date = common.pretty_range \
             (dyn.valid_from, dyn.valid_to - common.day)
     else :
         date = dyn.valid_from.pretty (common.ymd) + ';'
     
     frozen   = self.db.daily_record_freeze.filter \
         ( None
         , dict (user = dyn.user, date = date, frozen = True)
         , group = [('-', 'date')]
         )
     assert (frozen)
     frozen               = self.db.daily_record_freeze.getnode (frozen [0])
     splitdate            = frozen.date + common.day
     self.db.user_dynamic.set (id, valid_to = splitdate)
     param ['valid_from'] = splitdate
     newid                = self.db.user_dynamic.create (** param)
     self.db.commit ()
     raise Redirect, 'user_dynamic%s' % newid
def new_time_wp(db, cl, nodeid, new_values):
    if 'is_public' not in new_values:
        new_values['is_public'] = False
    common.require_attributes \
        ( _
        , cl
        , nodeid
        , new_values
        , 'name'
        , 'responsible'
        , 'project'
        , 'is_public'
        )
    prid = new_values['project']
    uid = db.getuid()
    prj = db.time_project.getnode(prid)
    if (uid != prj.responsible and uid != prj.deputy
            and not common.user_has_role(db, uid, 'Project') and uid != '1'):
        raise Reject, ("You may only create WPs for your own projects")
    act = db.time_project_status.get(prj.status, 'active')
    if not act and uid != '1':
        raise Reject, ("You may only create WPs for active projects")
    if 'durations_allowed' not in new_values:
        new_values['durations_allowed'] = False
    common.check_prop_len(_, new_values['name'])
    project = new_values['project']
    if 'wp_no' in new_values and not new_values['wp_no']:
        del new_values['wp_no']
    for i in 'name', 'wp_no':
        if i in new_values:
            check_duplicate_field_value(cl, project, i, new_values[i])
    status = db.time_project.get(project, 'status')
    new_values['cost_center'] = prj.cost_center
Exemple #5
0
def check_unlinking(db, cl, nodeid, new_values):
    """ Don't allow unlinking of properties """
    for prop in classprops[cl.classname]:
        if prop not in new_values:
            continue
        # allow admin
        if db.getuid() == '1':
            continue
        ids = dict.fromkeys(new_props(cl, prop, new_values))
        for id in old_props(cl, prop, nodeid):
            if id not in ids:
                name = _(cl.classname)
                kls = cl.properties[prop]
                klass = db.getclass(kls.classname)
                cls = _(kls.classname)
                # Check Link property exceptions:
                if cl.classname in l_exceptions:
                    if l_exceptions[cl.classname] == kls.classname:
                        continue
                # Allow Link properties if old linked prop is owned by user
                if (isinstance(kls, Link)
                        and klass.get(id, 'creator') == db.getuid()):
                    continue
                # Allow Multilink properties in exceptions if linked
                # prop is owned by user
                if (prop in exceptions.get(cl.classname, [])
                        and klass.get(id, 'creator') == db.getuid()):
                    continue
                # Allow IT and admin roles
                if common.user_has_role(db, db.getuid(), 'it', 'admin'):
                    continue
                raise Reject, \
                    _ ("You may not unlink %(cls)s from %(name)s") % locals ()
def new_it(db, cl, nodeid, new_values):
    if 'messages' not in new_values:
        raise Reject, _("New %s requires a message") % _(cl.classname)
    if ('status' not in new_values
            or not common.user_has_role(db, db.getuid(), 'IT')):
        new_values['status'] = '1'
    if 'category' not in new_values:
        new_values['category'] = '1'
    cat = db.it_category.getnode(new_values['category'])
    nosy = dict.fromkeys(new_values.get("nosy", []))
    if cat.responsible:
        nosy[cat.responsible] = 1
        if 'responsible' not in new_values:
            new_values['responsible'] = cat.responsible
    if cat.nosy:
        nosy.update(dict.fromkeys(cat.nosy))
    new_values['nosy'] = nosy.keys()
    if 'responsible' not in new_values:
        new_values['responsible'] = db.user.lookup('helpdesk')
    if 'stakeholder' not in new_values:
        new_values['stakeholder'] = db.getuid()
    if 'it_prio' not in new_values:
        new_values['it_prio'] = common.get_default_it_prio(db)
    if 'confidential' not in new_values:
        new_values['confidential'] = 1
    # Always make new issues confidential.
    new_values['confidential'] = True
    # Defaults for some attributes only in it_issue
    # We chose the item with lowest order as default
    for attr in 'int_prio', 'it_request_type':
        if attr in cl.properties and attr not in new_values:
            cls = db.getclass(cl.properties[attr].classname)
            values = cls.filter(None, {}, [('+', 'order')])
            if values:
                new_values[attr] = values[0]
def _domain_user_role_check(db):
    # Get role of user and check permission
    uid = db.getuid()
    # Allow admin user
    if uid == '1':
        return False
    roles = ("Dom-User-Edit-GTT", "Dom-User-Edit-HR", "Dom-User-Edit-Office")
    if not common.user_has_role(db, uid, *roles):
        return False
    return True
def pr_filter_status_transitions(db, context):
    try:
        db = db._db
    except AttributeError:
        pass
    uid = db.getuid()
    stati = ['approving', 'approved']
    if not common.user_has_role(db, uid, *prlib.reject_roles):
        stati.append('rejected')
    # This filters the stati *out* of the list.
    return common.filter_status_transitions(context, *stati)
Exemple #9
0
def time_wp_viewable(db, userid, itemid):
    """User may view work package if responsible for it, if user is
       owner or deputy of time category or on nosy list of time category
       or if user is department manager of time category
    """
    wp = db.time_wp.getnode(itemid)
    return \
        (  userid == wp.responsible
        or time_project_viewable (db, userid, wp.project)
        or common.user_has_role (db, userid, 'Summary_View')
        )
Exemple #10
0
 def project_or_wp_name_visible (db, userid, itemid) :
     """User is allowed to view work package and time category names
        if he/she is department manager or supervisor or has role HR
        or HR-Org-Location.
     """
     if common.user_has_role (db, userid, 'HR', 'HR-Org-Location') :
         return True
     if db.department.filter (None, dict (manager = userid)) :
         return True
     if db.user.filter (None, dict (supervisor = userid)) :
         return True
Exemple #11
0
def pr_filter_status_transitions(db, context):
    try:
        db = db._db
    except AttributeError:
        pass
    uid = db.getuid()
    stati = ['approving', 'approved']
    allowed_from = ('approving', 'approved', 'ordered')
    if not common.user_has_role(db, uid, 'Procurement-Admin'):
        stati.append('rejected')
    return common.filter_status_transitions(context, *stati)
Exemple #12
0
def hr_olo_role_for_this_user_dyn (db, dbuid, userdyn) :
    """ Given db uid has role HR-Org-Location and the given dynamic user
        is in the same Org-Location as the uid.
    """
    if not common.user_has_role (db, dbuid, 'HR-Org-Location') :
        return False
    dyn = get_user_dynamic (db, dbuid, Date ('.'))
    if not dyn :
        return False
    if userdyn.org_location == dyn.org_location :
        return True
    return False
def new_daily_record (db, cl, nodeid, new_values) :
    """
        Only create a daily_record if a user_dynamic record exists for
        the user.
        If a new daily_record is created, we check the date provided:
        If hours, minutes, seconds are all zero we think the time was
        entered in UTC and do no conversion. If one is non-zero, we get
        the timezone from the user information and re-encode the date as
        UTC -- this effectively makes the date a 'naive' date. Then we
        nullify hour, minute, second of the date.
        After that, we check that there is no duplicate daily_record
        with the same date for this user.
    """
    uid = db.getuid ()
    common.require_attributes (_, cl, nodeid, new_values, 'user', 'date')
    user  = new_values ['user']
    ttby  = db.user.get (user, 'timetracking_by')
    uname = db.user.get (user, 'username')
    if  (   uid != user
        and uid != ttby
        and not common.user_has_role (db, uid, 'controlling', 'admin')
        ) :
        raise Reject, _ \
            ("Only user, Timetracking by user, "
             "and Controlling may create daily records"
            )
    common.reject_attributes (_, new_values, 'time_record')
    # the following is allowed for the admin (import!)
    if uid != '1' :
        common.reject_attributes (_, new_values, 'status')
    date = new_values ['date']
    date.hour = date.minute = date.second = 0
    new_values ['date'] = date
    dyn  = user_dynamic.get_user_dynamic (db, user, date)
    if not dyn and uid != '1' :
        raise Reject, \
            _ ("No dynamic user data for %(uname)s, %(date)s") % locals ()
    if uid != '1' and not dyn.booking_allowed :
        raise Reject, _ \
            ("Booking not allowed for %(uname)s, %(date)s") % locals ()
    if frozen (db, user, date) :
        raise Reject, _ ("Frozen: %(uname)s, %(date)s") % locals ()
    if db.daily_record.filter \
        (None, {'date' : date.pretty ('%Y-%m-%d'), 'user' : user}) :
        raise Reject, _ ("Duplicate record: date = %(date)s, user = %(user)s") \
            % new_values
    new_values ['time_record'] = []
    if 'status' not in new_values :
        new_values ['status']  = db.daily_record_status.lookup ('open')
    new_values ['tr_duration_ok'] = None
Exemple #14
0
 def handle(self):
     red = None
     try:
         r = self.__super.handle()
     except Redirect as red:
         pass
     assert self.classname == 'purchase_request'
     cl = self.db.getclass(self.classname)
     pr = cl.getnode(self.nodeid)
     uid = self.db.getuid()
     st_ud = self.db.pr_approval_status.lookup('undecided')
     st_ap = self.db.pr_approval_status.lookup('approved')
     d = dict(purchase_request=pr.id, status=st_ud, user=uid)
     ap = dict.fromkeys(self.db.pr_approval.filter(None, d))
     del d['user']
     d['deputy'] = uid
     ap.update(dict.fromkeys(self.db.pr_approval.filter(None, d)))
     assert len(ap) <= 1
     if len(ap) < 1:
         assert common.user_has_role(self.db, uid, 'Procurement-Admin')
         del d['deputy']
         d['user'] = pr.requester
         ap = dict.fromkeys(self.db.pr_approval.filter(None, d))
     assert len(ap) == 1
     for a in ap:
         pr_ap = self.db.pr_approval.getnode(a)
         assert \
             (  pr_ap.user == uid
             or pr_ap.creator == uid
             or common.user_has_role (self.db, uid, 'Procurement-Admin')
             )
         assert pr_ap.status == st_ud
         self.db.pr_approval.set(a, status=st_ap)
         self.db.commit()
         break
     if red is not None:
         raise red
Exemple #15
0
def stay_closed(db, cl, nodeid, new_values):
    if 'status' in new_values:
        nst = new_values['status']
        ost = cl.get(nodeid, 'status')
        if nst != ost and ost == db.it_issue_status.lookup('closed'):
            if not common.user_has_role(db, db.getuid(), 'IT'):
                raise Reject \
                    ( _ ("This %(it_issue)s is already closed.\n"
                         "Please create a new issue or have it "
                         "reopened via a phone call to IT.\n"
                         "This can also mean you have chosen a subject "
                         "line of your email that matches an old closed "
                         "issue. Please chose another subject in that case."
                        ) % dict (it_issue = _ ('it_issue'))
                    )
Exemple #16
0
def check_ext_user_container(db, cl, nodeid, new_values):
    """ Check that new responsible of a container isn't an external
        user (role External).
    """
    co = new_values.get('composed_of', cl.get(nodeid, 'composed_of'))
    u = new_values.get('responsible', cl.get(nodeid, 'responsible'))
    if co and common.user_has_role(db, u, 'External'):
        if 'responsible' in new_values:
            raise Reject, _ \
                ("External user may not be responsible for container")
        else:
            raise Reject, _ \
                ("Operation would create a container "
                 "with an external user as owner"
                )
Exemple #17
0
def new_submission(db, cl, nodeid, new_values):
    """ Check that new leave submission is allowed and has sensible
        parameters
    """
    common.reject_attributes(_, new_values, 'approval_hr', 'comment_cancel')
    uid = db.getuid()
    st_subm = db.leave_status.lookup('submitted')
    if 'user' not in new_values:
        user = new_values['user'] = uid
    else:
        user = new_values['user']
    common.require_attributes \
        (_, cl, nodeid, new_values, 'first_day', 'last_day', 'user')
    first_day = new_values['first_day']
    last_day = new_values['last_day']
    fix_dates(new_values)
    if 'time_wp' not in new_values:
        wps = vacation.valid_leave_wps \
            ( db
            , user
            , last_day
            , [('-', 'project.is_vacation'), ('-', 'project.approval_hr')]
            )
        if wps:
            new_values['time_wp'] = wps[0]

    common.require_attributes(_, cl, nodeid, new_values, 'time_wp')
    if freeze.frozen(db, user, first_day):
        raise Reject(_("Frozen"))
    comment = new_values.get('comment')
    check_range(db, None, user, first_day, last_day)
    check_wp(db, new_values['time_wp'], user, first_day, last_day, comment)
    is_admin = (uid == '1')
    if 'status' in new_values and new_values[
            'status'] != st_subm and not is_admin:
        raise Reject(_('Initial status must be "submitted"'))
    if 'status' not in new_values:
        new_values['status'] = st_subm
    if (user != uid and
            not (is_admin or common.user_has_role(db, uid, 'HR-vacation'))):
        raise Reject \
            (_ ("Only special role may create submission for other user"))
    vacation.create_daily_recs(db, user, first_day, last_day)
    if vacation.leave_days(db, user, first_day, last_day) == 0:
        raise Reject(_("Vacation request for 0 days"))
    check_dr_status(db, user, first_day, last_day, 'open')
    check_dyn_user_params(db, user, first_day, last_day)
def new_time_wp (db, cl, nodeid, new_values) :
    if 'is_public' not in new_values :
        new_values ['is_public'] = False
    common.require_attributes \
        ( _
        , cl
        , nodeid
        , new_values
        , 'name'
        , 'responsible'
        , 'project'
        , 'is_public'
        )
    if 'is_extern' in cl.properties and 'is_extern' not in new_values :
        new_values ['is_extern'] = False
    prid = new_values ['project']
    uid  = db.getuid ()
    prj  = db.time_project.getnode (prid)
    is_auto_wp = False
    if 'auto_wp' in new_values :
        ap = db.auto_wp.getnode (new_values ['auto_wp'])
        if ap.time_project != new_values ['project'] :
            raise Reject (_ ("Auto-WP %s doesn't match") % _ ('time_project'))
        # If user may edit dyn. user we allow auto creation of wp
        if db.security.hasPermission ('Edit', db.getuid (), 'user_dynamic') :
            is_auto_wp = True
    if  (  uid != prj.responsible
        and uid != prj.deputy
        and not common.user_has_role (db, uid, 'Project')
        and uid != '1'
        and not is_auto_wp
        ) :
        raise Reject, ("You may only create WPs for your own projects")
    act  = db.time_project_status.get (prj.status, 'active')
    if not act and uid != '1' :
        raise Reject, ("You may only create WPs for active projects")
    if 'durations_allowed' not in new_values :
        new_values ['durations_allowed'] = False
    common.check_prop_len (_, new_values ['name'])
    project = new_values  ['project']
    if 'wp_no' in new_values and not new_values ['wp_no'] :
        del new_values ['wp_no']
    for i in 'name', 'wp_no' :
        if i in new_values :
            check_duplicate_field_value (cl, project, i, new_values [i])
    status = db.time_project.get (project, 'status')
    new_values ['cost_center'] = prj.cost_center
def check_retire (db, cl, nodeid, new_values) :
    assert not new_values
    uid      = db.getuid ()
    st_open  = db.daily_record_status.lookup ('open')
    st_leave = db.daily_record_status.lookup ('leave')
    tr = cl.getnode (nodeid)
    dr = db.daily_record.getnode (tr.daily_record)
    if frozen (db, dr.user, dr.date) :
        raise Reject (_ ("Can't retire frozen time record"))
    tt_by = db.user.get (dr.user, 'timetracking_by')
    allowed = True
    if dr.status == st_open :
        if  (   uid != dr.user
            and uid != tt_by
            and not common.user_has_role (db, uid, 'controlling', 'admin')
            ) :
            # Must have a leave submission in status accepted, then we
            # can retire existing records
            ac = db.leave_status.lookup ('accepted')
            vs = vacation.leave_submissions_on_date \
                (db, dr.user, dr.date, filter = dict (status = ac))
            if not vs :
                allowed = False
    else :
        if dr.status == st_leave :
            # All leave submissions must be in state cancelled or declined
            # At least one must be cancelled
            cn = db.leave_status.lookup ('cancelled')
            dc = db.leave_status.lookup ('declined')
            vs = vacation.leave_submissions_on_date (db, dr.user, dr.date)
            if not vs :
                allowed = False
            if allowed :
                allowed = False
                for v in vs :
                    if v.status == dc :
                        continue
                    if v.status == cn :
                        allowed = True
                    if v.status != cn :
                        allowed = False
                        break
        else :
            allowed = False
    # Allow admin to retire any time_record (!)
    if not allowed and db.getuid () != '1' :
        raise Reject (_ ("Permission denied"))
Exemple #20
0
def check_document_frozen (db, cl, nodeid, newvalues) :
    if common.user_has_role (db, db.getuid (), 'Doc_Admin') :
        return

    if nodeid :
        attr_lst = ('product_type', 'reference', 'artefact', 'department')
        action   = _ ('modify')
    else :
        attr_lst = ('document_nr',)
        action   = _ ('specify')

    attrs = ", ".join (_ (a) for a in attr_lst if a in newvalues)
    if attrs :
        raise Reject, _ \
            ('You are not allowed to %(action)s: %(attrs)s'
            % locals ()
            )
def leave_wp (db, dr, wp, start, end, duration) :
    if not wp :
        return False
    if start is not None or end is not None or duration is None :
        return False
    tp = db.time_project.getnode (db.time_wp.get (wp, 'project'))
    if not tp.approval_required :
        return False
    # Only search for non-cancelled non-retired non-declined
    st  = []
    unwanted = ('cancelled', 'retired', 'declined')
    for stid in db.leave_status.getnodeids (retired = False) :
        if db.leave_status.get (stid, 'name') in unwanted :
            continue
        st.append (stid)
    vs = vacation.leave_submissions_on_date \
        (db, dr.user, dr.date, filter = dict (status = st))
    if not vs :
        return False
    assert len (vs) == 1
    vs = vs [0]
    if vs.status != db.leave_status.lookup ('accepted') :
        return False
    clearer = common.tt_clearance_by (db, dr.user)
    uid     = db.getuid ()
    ld      = vacation.leave_duration (db, dr.user, dr.date)
    if tp.max_hours is not None :
        ld = min (ld, tp.max_hours)
    if not ld and tp.max_hours != 0 :
        return False
    if ld != duration :
        return False
    if  (  (uid in clearer and not tp.approval_hr)
        or (   common.user_has_role (db, uid, 'HR-leave-approval')
           and tp.approval_hr
           )
        ) :
        return True
    return False
Exemple #22
0
def check_statechange (db, cl, nodeid, newvalues) :
    """ Things to do for a state change:
        Add doc admins to nosy for certain state changes
    """
    if 'status' not in newvalues :
        return
    oldstate = cl.get (nodeid, 'status')
    newstate = newvalues ['status']
    wip = get_wip (db)
    if newstate != oldstate and oldstate != wip :
        nosy = newvalues.get ('nosy', cl.get (nodeid, 'nosy'))
        if not nosy :
            nosy = [db.getuid ()]
        nosy = dict.fromkeys (nosy)
        for u in db.user.getnodeids () :
            if common.user_has_role (db, u, 'Doc_Admin') :
                nosy [u] = True
        newvalues ['nosy'] = nosy.keys ()
    if newstate != oldstate :
        newvalues ['state_changed_by'] = db.getuid ()
        st = db.doc_status.getnode (newstate)
        if st.rq_link :
            common.require_attributes (_, cl, nodeid, newvalues, 'link')
Exemple #23
0
 def generate(self, ep_status):
     """ Buttons in leave submission forms (edit or approval)
     """
     ret = []
     self.ep_status = ep_status
     self.user = ep_status.item.user.id
     self.sunick = str(ep_status.item.user.supervisor.nickname).upper()
     stname = str(ep_status.prop.name)
     db = ep_status.item._db
     if (self.uid == self.user and stname in self.user_buttons):
         for b in self.user_buttons[stname]:
             ret.append(self.button(*b))
     elif stname in self.approve_buttons and self.uid != self.user:
         if common.user_has_role(self.db, self.uid, 'HR-leave-approval'):
             for b in self.approve_buttons[stname]:
                 ret.append(self.button(*b))
         else:
             tp = ep_status.item.time_wp.project.id
             tp = db.time_project.getnode(tp)
             first_day = ep_status.item.first_day._value
             last_day = ep_status.item.last_day._value
             dyn       = user_dynamic.get_user_dynamic \
                 (db, self.user, last_day)
             ctype = dyn.contract_type
             need_hr   = vacation.need_hr_approval \
                 (db, tp, self.user, ctype, first_day, last_day, stname)
             if (self.uid in common.tt_clearance_by(self.db, self.user)
                     and not need_hr):
                 for b in self.approve_buttons[stname]:
                     ret.append(self.button(*b))
     if ret:
         ret.append \
             ( '<input type="hidden" name="%s@status" value="%s">'
             % (ep_status.item.designator (), stname)
             )
     return ''.join(ret)
Exemple #24
0
def change_pr (db, cl, nodeid, new_values) :
    oitems    = new_values.get ('offer_items', cl.get (nodeid, 'offer_items'))
    approvals = db.pr_approval.filter (None, dict (purchase_request = nodeid))
    approvals = [db.pr_approval.getnode (a) for a in approvals]
    ap_appr   = db.pr_approval_status.lookup ('approved')
    ap_rej    = db.pr_approval_status.lookup ('rejected')
    requester = new_values.get ('requester', cl.get (nodeid, 'requester'))
    creator   = cl.get (nodeid, 'creator')
    ost       = cl.get (nodeid, 'status')
    now       = Date ('.')
    if 'status' in new_values and new_values ['status'] != ost :
        if new_values ['status'] == db.pr_status.lookup ('approving') :
            # check that pr_justification is given
            prjust (db, cl, nodeid, new_values)
            io = new_values.get \
                ('internal_order', cl.get (nodeid, 'internal_order'))
            tc = new_values.get \
                ('time_project', cl.get (nodeid, 'time_project'))
            cc = new_values.get \
                ('sap_cc',  cl.get (nodeid, 'sap_cc'))
            if not tc and not cc :
                raise Reject \
                    (_ ("Need to specify %(tp)s or %(cc)s")
                    % dict (tp = _ ('time_project'), cc = _ ('sap_cc'))
                    )
            if tc and cc :
                raise Reject \
                    (_ ("Either specify %(tp)s or %(cc)s, not both")
                    % dict (tp = _ ('time_project'), cc = _ ('sap_cc'))
                    )
            if tc and io :
                raise Reject \
                    (_ ("Specify %(cc)s not %(tp)s with %(io)s")
                    % dict
                        ( tp = _ ('time_project')
                        , cc = _ ('sap_cc')
                        , io = _ ('internal_order')
                        )
                    )
            common.require_attributes \
                ( _, cl, nodeid, new_values
                , 'department',  'organisation'
                , 'offer_items', 'delivery_deadline', 'purchase_type'
                , 'part_of_budget', 'terms_conditions', 'frame_purchase'
                , 'pr_currency', 'purchasing_agents', 'pr_ext_resource'
                )
            org = new_values.get \
                ('organisation', cl.get (nodeid, 'organisation'))
            dep = new_values.get ('department', cl.get (nodeid, 'department'))
            if dep and db.department.is_retired (dep) :
                raise Reject (_ ("Department no longer valid"))
            sapcc = new_values.get ('sap_cc', cl.get (nodeid, 'sap_cc'))
            if sapcc and db.sap_cc.is_retired (sapcc) :
                raise Reject (_ ("SAP Cost-Center no longer valid"))
            fp = new_values.get \
                ( 'frame_purchase'
                , cl.get (nodeid, 'frame_purchase')
                )
            if fp :
                common.require_attributes \
                    (_, cl, nodeid, new_values, 'frame_purchase_end')
            co = new_values.get \
                ( 'continuous_obligation'
                , cl.get (nodeid, 'continuous_obligation')
                )
            if co :
                common.require_attributes \
                    ( _, cl, nodeid, new_values
                    , 'contract_term', 'intended_duration'
                    )
            if not oitems :
                raise Reject (_ ("Need at least one offer item"))
            for oi in oitems :
                fix_pr_offer_item (db, db.pr_offer_item, oi, {})
            for oi in oitems :
                common.require_attributes \
                    (_, db.pr_offer_item, oi, {}
                    , 'index'
                    , 'price_per_unit'
                    , 'units'
                    , 'description'
                    , 'vat'
                    )
                oitem = db.pr_offer_item.getnode (oi)
                if oitem.sap_cc and oitem.time_project :
                    raise Reject \
                        (_ ("Either specify %(tp)s or %(cc)s, not both")
                        % dict (tp = _ ('time_project'), cc = _ ('sap_cc'))
                        )
                if oitem.time_project and io :
                    raise Reject \
                        (_ ("Specify %(cc)s not %(tp)s with %(io)s")
                        % dict
                            ( tp = _ ('time_project')
                            , cc = _ ('sap_cc')
                            , io = _ ('internal_order')
                            )
                        )
                nv = dict \
                    (time_project = oitem.time_project, sap_cc = oitem.sap_cc)
                check_tp_cc_consistency (db, db.pr_offer_item, oi, nv, org)
            # Check that approval of requester exists
            for ap in approvals :
                if  (   ap.status == ap_appr
                    and (ap.by == requester or ap.by == creator)
                    ) :
                    break
                # Allow approval by procurement-admin instead of
                # requester/creator
                if  (   ap.status == ap_appr
                    and common.user_has_role (db, ap.by, 'Procurement-Admin')
                    ) :
                    break
            else :
                raise Reject ( _ ("No approval by requester found"))
            new_values ['total_cost']  = prlib.pr_offer_item_sum (db, nodeid)
            check_tp_cc_consistency (db, cl, nodeid, new_values)
            update_nosy (db, cl, nodeid, new_values)

        elif new_values ['status'] == db.pr_status.lookup ('approved') :
            for ap in approvals :
                if ap.status != ap_appr :
                    raise Reject (_ ("Not all approvals in status approved"))
            new_values ['date_approved'] = now
        elif new_values ['status'] == db.pr_status.lookup ('rejected') :
            for ap in approvals :
                if ap.status == ap_rej :
                    break
            else :
                uid = db.getuid ()
                if not common.user_has_role (db, uid, 'Procurement-Admin') :
                    raise Reject (_ ("No rejected approval-record found"))
            new_values ['date_approved'] = None
            new_values ['date_ordered']  = None
        elif new_values ['status'] == db.pr_status.lookup ('open') :
            # If setting status to open again we need to retire *all*
            # approvals and re-create the (pending) approval of the
            # requester
            if ost != db.pr_status.lookup ('rejected') :
                raise Reject ("Invalid status change to open")
            for ap in approvals :
                db.pr_approval.retire (ap.id)
            create_pr_approval (db, cl, nodeid, {})
            # We also need to set the total cost to None
            new_values ['total_cost']    = None
            new_values ['date_approved'] = None
            new_values ['date_ordered']  = None
        elif new_values ['status'] == db.pr_status.lookup ('ordered') :
            new_values ['date_ordered'] = now
        elif new_values ['status'] == db.pr_status.lookup ('cancelled') :
            new_values ['date_approved'] = None
            new_values ['date_ordered']  = None
Exemple #25
0
def add_ext_user(db, cl, nodeid, new_values):
    """ Add user to external_users of new issue if user has role
        External.
    """
    if common.user_has_role(db, db.getuid(), 'External'):
        new_values['external_users'] = [db.getuid()]
Exemple #26
0
def limit_new_entry(db, cl, nodeid, newvalues):
    """Limit creation of new issues, check on entered fields,
       and correctly complete missing fields.
    """
    title = newvalues.get("title")
    category = None
    catid = newvalues.get("category")
    if catid:
        category = db.category.getnode(catid)
    area = newvalues.get("area")
    kindid = newvalues.get("kind")
    responsible = newvalues.get("responsible")
    msg = newvalues.get("messages")
    severity = newvalues.get("severity")
    effort = newvalues.get("effort_hours")
    part_of = newvalues.get("part_of")
    try:
        bug = db.kind.lookup('Bug')
    except KeyError:
        bug = db.kind.lookup('Defect')
    bugname = db.kind.get(bug, 'name')
    analyzing = db.status.lookup("analyzing")

    if not kindid:
        kindid = newvalues['kind'] = bug
    kind = db.kind.getnode(kindid)
    if ("status" not in newvalues or not initial_status_ok(
            db, newvalues["status"], catid, kind.simple)):
        if kind.simple:
            newvalues["status"] = db.status.lookup('open')
        else:
            newvalues["status"] = analyzing
    status = newvalues["status"]

    # Default to no action for doc_issue_status for simple issues
    if kind.simple and 'doc_issue_status' not in newvalues:
        newvalues ['doc_issue_status'] = \
            db.doc_issue_status.lookup ('no documentation')

    if not category:
        catid = newvalues['category'] = db.category.lookup('pending')
        category = db.category.getnode(catid)
    if not area:
        try:
            area = newvalues['area'] = db.area.lookup('SW')
        except KeyError:
            pass
    if not severity:
        severity = newvalues['severity'] = db.severity.lookup('Minor')
    if not title:
        raise Reject, _('You must enter a "title".')
    if not msg:
        field = _('msg')
        raise Reject, _ ("A detailed description must be given in %(field)s") \
                        % locals ()
    if kindid == bug and 'release' not in newvalues:
        raise Reject, _ ("For a %(bugname)s you have to specify the release") \
                        % locals ()
    if not kind.simple and status != analyzing and not effort:
        raise Reject, \
            _ ("An effort estimation is required for issues to skip analyzing")

    # Set `part_of` to the category default if not given.
    if not part_of:
        newvalues["part_of"] = category.default_part_of

    # Set `responsible` to the category's responsible.
    if not responsible:
        responsible = category.responsible
        if not responsible:
            raise Reject, _('No responsible for category "%s"') % category.name
        newvalues["responsible"] = responsible

    if not user_has_role(db, responsible, 'Nosy'):
        raise Reject \
            ( _ ("'%s' is not a valid user (need 'Nosy' role)!")
            % (db.user.get (responsible, "username"))
            )

    # Set `nosy` to contain the creator, the responsible,
    # and the category's nosy list.
    nosy = newvalues.get("nosy", [])
    cat_nosy = category.nosy
    cat_resp = category.responsible
    creator = newvalues.get("creator")
    addnosy = [creator, responsible]
    if cat_resp:
        addnosy.append(cat_resp)
    nosy = union(nosy, cat_nosy, addnosy)
    newvalues["nosy"] = filter(None, nosy)

    # It is meaningless to create obsolete or mistaken issues.
    if kind.name in ["Mistaken", "Obsolete"]:
        raise Reject, ('[%s] It is stupid to create a new issue with a '
                       'kind of "Mistaken" or "Obsolete".' % nodeid)
Exemple #27
0
def limit_transitions(db, cl, nodeid, newvalues):
    """Enforce (i.e. limit) status transitions
    """
    cur_status = cl.get(nodeid, "status")
    cur_status_name = db.status.get(cur_status, "name")
    new_status = newvalues.get("status", cur_status)
    new_status_name = db.status.get(new_status, "name")

    may_not_vanish(db, cl, nodeid, newvalues, new_status_name)

    kindid = newvalues.get("kind", cl.get(nodeid, "kind"))
    kind = kind_name = None
    is_simple = False
    if kindid:
        kind = db.kind.getnode(kindid)
        kind_name = kind.name
        is_simple = kind.simple
    old_responsible = cl.get(nodeid, "responsible")
    new_responsible = newvalues.get("responsible", old_responsible)
    superseder = newvalues.get("superseder", cl.get(nodeid, "superseder"))
    is_container = db.issue.get(nodeid, "composed_of")
    fixed = newvalues.get("fixed_in", cl.get(nodeid, "fixed_in"))
    cat_id = newvalues.get("category", cl.get(nodeid, "category"))
    category = db.category.getnode(cat_id)
    affected        = newvalues.get \
        ("files_affected", cl.get (nodeid, "files_affected"))
    effort          = newvalues.get \
                      ("effort_hours", cl.get (nodeid, "effort_hours"))
    msg = newvalues.get("messages", None)
    severity = newvalues.get("severity", cl.get(nodeid, "severity"))
    dis_name = 'doc_issue_status'
    if dis_name in db.classes and dis_name in cl.properties:
        dis_id_old = cl.get(nodeid, dis_name)
        dis_id = newvalues.get(dis_name, dis_id_old)
        di_status = db.doc_issue_status.getnode(dis_id)

    ############ complete the form ############

    # If a Superseder is/has been set, automatically close this issue.
    # Also close if "Obsolete" or "Mistaken".
    if superseder or kind_name in ["Mistaken", "Obsolete"]:
        if is_container:
            if (newvalues.get('kind')
                    and kind_name in ["Mistaken", "Obsolete"]):
                raise Reject, \
                    "A container may not be set to Mistaken or Obsolete."
            if newvalues.get('superseder'):
                raise Reject, \
                    "A container may not be a duplicate of another issue."
        new_status_name = "closed"
        newvalues["status"] = new_status = db.status.lookup(new_status_name)

    # Set `closed_date` when a bug report is being closed
    # and reset it if re-opened
    if new_status_name == "closed" and cur_status_name != "closed":
        newvalues["closed"] = Date(".")
    if new_status_name != "closed" and cur_status_name == "closed":
        newvalues["closed"] = None

    # Automatically set status "feedback" to "open" when responsible changes.
    if  (old_responsible != new_responsible) \
    and (cur_status_name == "feedback") and (new_status_name == "feedback") :
        new_status_name = "open"
        newvalues["status"] = new_status = db.status.lookup(new_status_name)

    # Automatically clear the `fixed_in` field if testing failed.
    if cur_status_name == "testing" and new_status_name == "open":
        newvalues["fixed_in"] = fixed = ""  # active delete

    ############ prohibit invalid changes ############

    # Direct close only allowed if mistaken, obsolete or duplicate,
    # or if it is a container or simple kind.
    if (cur_status_name in ["open", "feedback", "suspended", "analyzing"]
            and new_status_name == "closed" and not is_simple):
        if not (kind_name in ["Mistaken", "Obsolete"] or superseder
                or is_container):
            raise Reject, ("[%s] To close this issue, kind must be set to "
                           "`Mistaken` or `Obsolete`, <br>or this issue must "
                           "be a duplicate of another issue or a container." %
                           nodeid)
        if (kind_name in ["Mistaken", "Obsolete"] or superseder) and not msg:
            raise Reject, ("[%s] A reason in `message` must be given here." %
                           nodeid)

    # Don't allow state change if doc_issue_status doesn't allow it
    if (dis_name in db.classes and dis_name in cl.properties
            and new_status_name != cur_status_name
            and not (kind_name in ["Mistaken", "Obsolete"] or superseder
                     or is_container)
            and new_status not in di_status.may_change_state_to):
        dis_name_l = _(dis_name)
        st = di_status.name
        raise Reject, \
            ( "[%(nodeid)s] %(dis_name_l)s = %(st)s "
              "doesn't allow state change to %(new_status_name)s"
            % locals ()
            )

    if (cur_status_name == "analyzing" and new_status_name != "analyzing"
            and not is_container and not is_simple):
        if new_status_name != 'closed':
            if not kind:
                raise Reject, "Kind must be filled in for status change"
            if effort is None:
                raise Reject, "Effort must be filled in for status change"
        if new_status_name == "open" and category.cert_sw:
            if kind_name == 'Change-Request':
                raise Reject, "No State-change to open for Change-Request"
            max_effort = int(getattr(db.config.ext, 'LIMIT_EFFORT', 8))
            if effort > max_effort:
                raise Reject \
                    (_ ("State-change to open only for "
                        "effort <= %(max_effort)s"
                       )
                    % locals ()
                    )

    # A `message` must be given whenever `responsible` changes.
    if old_responsible != new_responsible:
        if not msg:
            raise Reject, ("[%s] A reason in `message` must be given to "
                           "change the `responsible`." % nodeid)
        if not user_has_role(db, new_responsible, 'Nosy'):
            raise Reject \
                ( _ ("'%s' is not a valid user (need 'Nosy' role)!")
                % (db.user.get (new_responsible, "username"))
                )

    if (new_status_name != cur_status_name
            and kind_name not in ('Mistaken', 'Obsolete') and not superseder
            and not is_container and not kind.simple):
        # Check if the `fixed_in` field is filled in when moving to
        # `testing` or `closed`.
        if not fixed and new_status_name in ("testing", "closed"):
            raise Reject, ("[%s] The 'fixed_in' field must be set for "
                           "a transition to 'testing' or 'closed'." % nodeid)
        if not severity:
            raise Reject, ("[%s] The 'severity' field must be set for "
                           "this transition" % nodeid)
        if category.name == 'pending':
            raise Reject, ("[%s] No status change for category 'pending'" %
                           nodeid)

    # Check that fixed_in does not contain illegal pattern
    if 'fixed_in' in newvalues and fixed:
        for p in _fixed_in_patterns:
            if p.search(fixed):
                raise Reject, (
                    "[%s] %s is not allowed for the fixed_in field" %
                    (nodeid, fixed))

    # Ensure `files_affected` to be filled on certifyable products.
    if ((cur_status_name == "testing" and new_status_name == "closed"
         or cur_status_name == "open" and new_status_name == "testing")
            and category.cert_sw and not affected):
        raise Reject, ("[%s] The `files_affected` field must be (or remain) "
                       "set for certified software." % nodeid)

    if (not is_container and
        (new_status_name != cur_status_name or 'severity' in newvalues)):
        newvalues['maturity_index'] = None  # Force recomputation
Exemple #28
0
def check_submission(db, cl, nodeid, new_values):
    """ Check that changes to a leave submission are ok.
        We basically allow changes of first_day, last_day, and time_wp
        in status 'open'. The user must never change. The status
        transitions are bound to certain roles. Note that this auditor
        is called *after* it has been verified that a requested state
        change is at least possible (although we still have to check the
        role).
    """
    common.reject_attributes(_, new_values, 'user', 'approval_hr')
    old = cl.getnode(nodeid)
    uid = db.getuid()
    user = old.user
    old_status = db.leave_status.get(old.status, 'name')
    if old_status != 'accepted':
        common.reject_attributes(_, new_values, 'comment_cancel')
    new_status = db.leave_status.get \
        (new_values.get ('status', old.status), 'name')
    if old_status != 'open':
        common.reject_attributes \
            (_, new_values, 'first_day', 'last_day', 'time_wp', 'comment')
    fix_dates(new_values)
    first_day = new_values.get('first_day', cl.get(nodeid, 'first_day'))
    last_day = new_values.get('last_day', cl.get(nodeid, 'last_day'))
    if freeze.frozen(db, user, first_day):
        raise Reject(_("Frozen"))
    time_wp = new_values.get('time_wp', cl.get(nodeid, 'time_wp'))
    comment = new_values.get('comment', cl.get(nodeid, 'comment'))
    check_range(db, nodeid, user, first_day, last_day)
    check_wp(db, time_wp, user, first_day, last_day, comment)
    if old_status in ('open', 'submitted'):
        vacation.create_daily_recs(db, user, first_day, last_day)
    if 'first_day' in new_values or 'last_day' in new_values:
        if vacation.leave_days(db, user, first_day, last_day) == 0:
            raise Reject(_("Vacation request for 0 days"))
        check_dyn_user_params(db, user, first_day, last_day)
    if old_status in ('open', 'submitted'):
        check_dr_status(db, user, first_day, last_day, 'open')
    if old_status in ('accepted', 'cancel requested'):
        check_dr_status(db, user, first_day, last_day, 'leave')
    if old_status != new_status:
        if (old_status == 'accepted' and new_status == 'cancel requested'):
            common.require_attributes \
                (_, cl, nodeid, new_values, 'comment_cancel')
        # Allow special HR role to do any (possible) state changes
        # Except for approval of own records
        if (common.user_has_role(db, uid, 'HR-vacation') and
            (uid != user
             or new_status not in ('accepted', 'declined', 'cancelled'))):
            ok = True
        else:
            ok = False
            tp = db.time_project.getnode \
                (db.time_wp.get (old.time_wp, 'project'))
            if not ok and uid == user:
                if old_status == 'open' and new_status == 'submitted':
                    ok = True
                if (old_status == 'accepted'
                        and new_status == 'cancel requested'):
                    ok = True
                if old_status == 'submitted' and new_status == 'open':
                    ok = True
                if old_status == 'open' and new_status == 'cancelled':
                    ok = True
            elif not ok:
                clearer = common.tt_clearance_by(db, user)
                dyn = user_dynamic.get_user_dynamic(db, user, first_day)
                ctype = dyn.contract_type
                hr_only = vacation.need_hr_approval \
                    (db, tp, user, ctype, first_day, last_day, old_status)
                if (uid != user and
                    ((uid in clearer and not hr_only)
                     or common.user_has_role(db, uid, 'HR-leave-approval'))):
                    if (old_status == 'submitted'
                            and new_status in ('accepted', 'declined')):
                        ok = True
                    if (old_status == 'cancel requested'
                            and (new_status == 'cancelled'
                                 or new_status == 'accepted')):
                        ok = True
            if not ok:
                raise Reject(_("Permission denied"))
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)
def check_daily_record (db, cl, nodeid, new_values) :
    """ Check that status changes are OK. Allowed changes:

         - From open to submitted by user or by HR
           But only if no leave submission in state 'submitted',
           'approved', 'cancel requested' exists
         - From submitted to accepted by supervisor or by HR
           but don't allow accepting own records
         - From submitted to open     by supervisor or by HR or by user
         - From accepted  to open     by HR
         - From open to leave if an accepted leave_submission exists
         - From leave to open if leave_submissions exist which are *all*
           in state cancel
    """
    for i in 'user', 'date' :
        if i in new_values and db.getuid () != '1' :
            raise Reject, _ ("%(attr)s may not be changed") % {'attr' : _ (i)}
    if i in ('status',) :
        if i in new_values and not new_values [i] :
            raise Reject, _ ("%(attr)s must be set") % {'attr' : _ (i)}
    user       = cl.get (nodeid, 'user')
    date       = cl.get (nodeid, 'date')
    if frozen (db, user, date) and new_values.keys () != ['tr_duration_ok'] :
        uname = db.user.get (user, 'username')
        raise Reject, _ ("Frozen: %(uname)s, %(date)s") % locals ()
    uid        = db.getuid ()
    is_hr      = common.user_has_role (db, uid, 'hr')
    old_status = cl.get (nodeid, 'status')
    status     = new_values.get ('status', old_status)
    may_give_clearance = uid in common.tt_clearance_by (db, user)

    vs_exists = False
    st_accp = db.leave_status.lookup ('accepted')
    vs = vacation.leave_submissions_on_date (db, user, date)
    # All leave submissions in state cancelled (or declined)?
    # Check if at least one is cancelled
    cn = db.leave_status.lookup ('cancelled')
    dc = db.leave_status.lookup ('declined')
    op = db.leave_status.lookup ('open')
    vs_cancelled = True
    if not vs :
        vs_cancelled = False
    if vs_cancelled :
        for v in vs :
            if v.status == dc :
                continue
            if v.status == cn :
                vs_cancelled = True
            else :
                vs_cancelled = False
                break
    vs_has_valid  = False
    for v in vs :
        if v.status == op or v.status == cn or v.status == dc :
            continue
        vs_has_valid = True
        break
    vs = [v for v in vs if v.status == st_accp]
    if vs :
        assert len (vs) == 1
        vs_accepted = True

    old_status, status = \
        [db.daily_record_status.get (i, 'name') for i in [old_status, status]]
    ttby = db.user.get (user, 'timetracking_by')
    if status != old_status :
        if not (  (   status == 'submitted' and old_status == 'open'
                  and (is_hr or user == uid or ttby == uid)
                  and time_records_consistent (db, cl, nodeid)
                  and not vs_has_valid
                  )
               or (   status == 'accepted'  and old_status == 'submitted'
                  and (is_hr or may_give_clearance)
                  and user != uid
                  )
               or (   status == 'open'      and old_status == 'submitted'
                  and (is_hr or user == uid or may_give_clearance)
                  )
               or (   status == 'open'      and old_status == 'accepted'
                  and is_hr
                  )
               or (   status == 'leave'     and old_status == 'open'
                  and vs_accepted
                  )
               or (   status == 'open'      and old_status == 'leave'
                  and vs_cancelled
                  )
               ) :
            msg = "Invalid Transition"
            if old_status == 'open' :
                if 'status' == 'submitted' :
                    if not is_hr and user != uid :
                        msg = _ ("Permission denied")
                    elif not time_records_consistent (db, cl, nodeid) :
                        msg = _ ("Inkonsistent time records")
                    elif vs_has_valid :
                        msg = _ ("Leave submission exists")
                elif 'status' == 'leave' :
                    msg = _ ("No accepted leave submission")
            elif old_status == 'leave' :
                msg = _ ("Leave submission not cancelled")
            elif old_status == 'accepted' :
                msg = _ ("Re-open only by HR")
            elif old_status == 'submitted' :
                if status == 'accepted' :
                    if not is_hr and not may_give_clearance :
                        msg = _ ("Permission denied")
                    elif user == uid :
                        msg = _ ("May not self-approve")
                elif status == 'open' :
                    if not is_hr and user != uid and not may_give_clearance :
                        msg = _ ("Permission denied")
            raise Reject, \
                ( _ ("Denied state change: %(old_status)s->%(status)s: %(msg)s")
                % locals ()
                )