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