def calculette_rh(db: SQLAlchemy, structure_repo: StructureRepository): demande = DemandeRH() json = request.json model = json["model"] form = json["form"] user = get_current_profile() model = cleanup_model(model, form) demande = DemandeRH(data=model) demande.form_state = form porteur_dto = model.get("porteur") if porteur_dto: porteur_id = porteur_dto["value"] demande.porteur = db.session.query(Profile).get(porteur_id) if user != demande.porteur: demande.gestionnaire = user structure_dto = model.get("laboratoire") if structure_dto: structure_id = structure_dto["value"] demande.structure = structure_repo.get_by_id(structure_id) blob = devis_rh(demande) response = make_response(blob) response.headers["content-type"] = "application/pdf" return response
def check_user_can_edit(): profile = get_current_profile() if profile.has_role(Role.ADMIN_CENTRAL) or profile.has_role( Role.FAQ_EDITOR): return raise Forbidden
def update_demande(id, model, form): user = get_current_profile() model = cleanup_model(model, form) demande = db.session.query(Demande).get(id) check_read_access(demande) check_write_access(demande) demande.form_state = form messages = [] if not demande.has_same_data(model): demande.update_data(model) porteur = get_porteur(model) if porteur != demande.porteur: demande.porteur = porteur demande.gestionnaire = user demande.structure = get_structure(model) db.session.commit() messages.append([ "Votre demande a été modifiée. La version précédente de la demande a été archivée.", "success", ]) else: messages.append( ["Vous n'avez pas effectué de modification.", "success"]) if not demande.is_valid(): messages.append( ["Attention, votre demande est encore incomplète.", "warning"]) whoosh.index_object(demande) return messages
def get_bi_context(): check_permission() user = get_current_profile() ctx = get_stats2(user) ctx["selectors"] = get_selectors() return ctx
def get_nb_demandes_a_valider(): user = get_current_profile() ctx = { "total_demandes": 0, "nb_pi_a_valider": 0, "nb_recrutements_a_valider": 0, "nb_conventions_a_valider": 0, "nb_autres_a_valider": 0, } if user.has_role(Role.RESPONSABLE, "*"): demandes = mes_taches(user) demandes = [d for d in demandes if d.wf_state == EN_VALIDATION.id] conventions = [ d for d in demandes if isinstance(d, (DemandeConvention, DemandeAvenantConvention)) ] recrutements = [d for d in demandes if isinstance(d, DemandeRH)] pi = [d for d in demandes if isinstance(d, DemandePiMixin)] autres = [d for d in demandes if isinstance(d, DemandeAutre)] ctx["total_demandes"] = len(demandes) ctx["nb_conventions_a_valider"] = len(conventions) ctx["nb_recrutements_a_valider"] = len(recrutements) ctx["nb_pi_a_valider"] = len(pi) ctx["nb_autres_a_valider"] = len(autres) return ctx
def get_demandes(scope="all", archives=False, tag="") -> JSONList: archives = bool(archives) profile = get_current_profile() view = get_table_view(scope, profile, archives) if not view: return [] demandes: list[Demande] = view.get_demandes_for(profile) def make_pred(tag): def pred(demande): if tag == "rh": return isinstance(demande, DemandeRH) elif tag == "conventions": return isinstance( demande, (DemandeConvention, DemandeAvenantConvention)) elif tag == "pi": return isinstance(demande, DemandePiMixin) elif tag == "autres": return isinstance(demande, DemandeAutre) else: raise RuntimeError() return pred if tag: demandes = r.filter(make_pred(tag), demandes) demandes.sort(key=lambda d: d.created_at, reverse=True) return demandes_to_json(demandes, profile)
def sg_get_possible_child_types(id: str) -> list[dict[str, str]]: user = get_current_profile() structure = structure_repo.get_by_id(id) if not structure: raise NotFound() if not has_permission(structure, "P3"): return [] result = [] for candidate_type in ALL_TYPES: if candidate_type.reel: continue if structure.type.can_have_child_of_type(candidate_type): if user.has_role(Role.ADMIN_CENTRAL): result.append({ "value": candidate_type.id, "text": candidate_type.name }) elif candidate_type in [DE, EQ]: result.append({ "value": candidate_type.id, "text": candidate_type.name }) return result
def update_global_roles(data: dict[str, JSON]): user = get_current_profile() if not user.has_role(Role.ADMIN_CENTRAL): raise Forbidden for role_name in data: role = getattr(Role, role_name) users = role_service.get_users_with_role(role) for user in users: role_service.ungrant_role(user, role) values = data[role_name] if isinstance(values, dict): values = [values] if not values: continue for user_id in glom(values, ["id"]): user = profile_repo.get_by_id(ProfileId(user_id)) role_service.grant_role(user, role) db.session.commit() cache.evict("users")
def get_workflow(self, demande: Demande) -> JSON: current_user = get_current_profile() workflow = demande.get_workflow(current_user) owners = list(workflow.current_owners()) owners.sort(key=owner_sorter) owners_dto = [{ "full_name": owner.full_name, "id": owner.id } for owner in owners] transitions_dto = [{ "id": t.id, "label": t.label, "category": t.category, "form": t.get_form(workflow), } for t in workflow.possible_transitions()] result = { "state": { "label": workflow.state.label, "next_action": workflow.state.next_action, }, "owners": owners_dto, "history": [], # TODO "transitions": transitions_dto, } return result
def search(q, **search_args): """Interface to search indexes. :param q: unparsed search string. :param search_args: any valid parameter for :meth:`whoosh.searching.Search.search`. This includes `limit`, `groupedby` and `sortedby` """ index = whoosh.index fields = {"name": 1.5, "text": 1.0} parser = DisMaxParser(fields, index.schema) query = parser.parse(q) # security access filter user = get_current_profile() if not is_membre_dri(user): pass # TODO # roles = {f"user:{user.id}", "all"} # for role in user.get_roles(): # if role.type in [RoleType.DIRECTION.value, RoleType.GDL.value]: # structure = role.context # structures = [structure] + structure.descendants() # roles |= {f"org:{s.id}" for s in structures} # # terms = [wq.Term("allowed_roles_and_users", role) for role in roles] # query &= wq.Or(terms) with index.searcher(closereader=False) as searcher: # 'closereader' is needed, else results cannot by used outside 'with' # statement return searcher.search(query, **search_args)
def wf_transition(demande_id, action, data=None): data = data or {} user = get_current_profile() db = injector.get(SQLAlchemy) demande = db.session.query(Demande).get(demande_id) workflow = demande.get_workflow(user) try: transition = workflow.get_transition_by_id(action) except IndexError: msg = ( "Action impossible. Quelqu'un a probablement effectué une action " "sur la demande en parallèle avec vous.", "danger", ) return msg workflow.execute_transition(transition, data=data) db.session.commit() msg = ( f"Votre action '{transition.label}' sur la demande '{demande.nom}' a bien été prise en compte.", "success", ) return msg
def has_read_access(demande: Demande) -> bool: user = get_current_profile() if user in (demande.porteur, demande.gestionnaire, demande.contact_labco): return True if user.has_role(Role.ADMIN_CENTRAL): return True wf_states = { state["new_state"] for state in demande.wf_history if "new_state" in state } # DRI & DRV if wf_states != {EN_EDITION.id}: if is_membre_dri(user): return True if demande.structure and is_membre_drv(user, demande.structure): return True if is_responsable_ou_gestionnaire(user, demande): return True if is_responsable_structure_concernee(user, demande): return True if is_contributeur(user, demande): return True return False
def acces_restreint(demande: Demande) -> bool: """Retourne True si l'utilisateur courant n'a pas le droit de voir les info confidentielles d'un formulaire RH.""" if not isinstance(demande, DemandeRH): return False user = get_current_profile() if user in {demande.porteur, demande.gestionnaire, demande.contact_labco}: return False if user.has_role(Role.ADMIN_CENTRAL): return False # Les responsables "ascendants" ne voient pas le détail # sauf pour les structures "concernées". # NB: le labo est toujours "concerné" if demande.structure: if user.has_role(Role.RESPONSABLE, demande.structure): return False dri = structure_repo.get_by_dn(DRI_DN) if user.has_role(Role.RESPONSABLE, dri): return False structures = demande.get_structure_concernees() for structure in structures: if user.has_role(Role.RESPONSABLE, structure): return False # Responsable facultaire et admin facultaire if demande.structure: structures.add(demande.structure) for structure in structures: ancestors = structure.ancestors for ancestor in ancestors: if ancestor.type != FA: continue if user.has_role(Role.RESPONSABLE, ancestor): return False # Admin facultaire for sous_structure in ancestor.children: if sous_structure.sigle.startswith("DRV "): if user.has_role(Role.ADMIN_LOCAL, sous_structure): return False contact_service = injector.get(ContactService) mapping = contact_service.get_mapping() for d in mapping.values(): if d.get(ContactType.CONTACT_RH) == user: return False return True
def timeline() -> JSONDict: user = get_current_profile() notifications = Notification.query.get_for_user(user) notifications_dto = NotificationSchema().dump(notifications, many=True).data user.date_derniere_notification_vue = datetime.utcnow() db.session.commit() ctx = {"notifications": notifications_dto} return ctx
def has_write_access(demande: Demande) -> bool: if not demande.editable: return False user = get_current_profile() if user in {demande.porteur, demande.gestionnaire}: return True if is_contributeur(user, demande): return True return False
def get_permissions_for_structure(structure: Structure) -> set[str]: if current_app.config.get("TESTING"): return set() user = get_current_profile() permissions = set() # cas admin central if user.has_role(Role.ADMIN_CENTRAL): return {"P1", "P2", "P3", "P4", "P5", "P6"} # P1: Infos clés - Edition # Admin local: Pour S seulement (et ses départements et équipes si S est un Labo) # Admin facultaire: De F et de toute structure descendante # P2: Hiérarchie des structures – Edition de structures existantes # Admin local: Non, à l’exception des départements et équipes si S est un Labo # Admin facultaire: De F et de toute structure descendante # P3: Hiérarchie des structures – Création / suppression de structures virtuelles (ex : Carnot) # Admin local: Non, à l’exception des départements et équipes si S est un Labo # Admin facultaire: Non, à l’exception des départements et équipes des laboratoires # descendant de F # P4: Membre – Edition pour rattachement d’un utilisateur # Admin local: Pour S seulement # Admin facultaire: Pour F et toute structure descendante # P5: Rôles - Edition # Admin local: Pour S seulement (et ses départements et équipes si S est un Labo) # Admin facultaire: Pour F et toute structure descendante # P6: Contacts Lab & Co - Edition # Admin local: Non # Admin facultaire: Pour F et toute structure descendante # Cas admin facultaire for ancestor in [structure] + structure.ancestors: if user.has_role(Role.ADMIN_LOCAL, ancestor) and ancestor.type == FA: permissions.update(["P1", "P2", "P3", "P4", "P5", "P6"]) return permissions # Cas admin local "normal" if user.has_role(Role.ADMIN_LOCAL, structure): permissions.update(["P3", "P4", "P5"]) for ancestor in structure.ancestors: if user.has_role(Role.ADMIN_LOCAL, ancestor) and ancestor.type == LA: permissions.update(["P1", "P2", "P3", "P4", "P5"]) return permissions
def preferences(): user = get_current_profile() if user.preferences_notifications is None: user.preferences_notifications = 0 if user.preferences_nb_jours_notifications is None: user.preferences_nb_jours_notifications = 0 return { "choices": CHOICES, "preferences_notifications": user.preferences_notifications, "nb_jours_notification": user.preferences_nb_jours_notifications, }
def get_contacts_for_user() -> JSONDict: user = get_current_profile() roles: dict[Role, set[Structure]] = role_service.get_roles_for_user(user) structures = list(roles[Role.MEMBRE]) structures.sort(key=lambda x: x.nom) result1 = [] for structure in structures: for contact_type in ContactType: contact = contact_service.get_contact(structure, contact_type) if contact: structure_dto = { "name": structure.nom, "id": structure.id, "type": structure.type_name, "sigle": structure.sigle, } result1.append( { "structure": structure_dto, "contacts": make_contacts_dto(structure), } ) break result2 = [] mapping: dict[Structure, dict[ContactType, Profile]] = contact_service.get_mapping() for structure, d in mapping.items(): if user in d.values(): for contact_type, user1 in d.items(): if user1 == user: structure_dto = { "name": structure.nom, "id": structure.id, "type": structure.type_name, "sigle": structure.sigle, } result2.append( { "structure": structure_dto, "bureau": contact_type.value, } ) return { "structures": result1, "mes_contacts": result2, }
def can_add_pj(demande: Demande) -> bool: user = get_current_profile() if user in (demande.porteur, demande.gestionnaire, demande.contact_labco): return True if is_gestionnaire(user, demande): return True if is_contributeur(user, demande): return True if user in demande.valideurs(): return True return False
def get_stats(**args): check_permission() args2 = {} for arg_name, arg_value in args.items(): if not arg_value: continue if isinstance(arg_value, List): value = r.map(lambda x: x["value"], arg_value) elif isinstance(arg_value, Dict): value = arg_value["value"] else: value = arg_value args2[arg_name] = value user = get_current_profile() return get_stats2(user, **args2)
def get_boxes(archives=False) -> JSON: archives = bool(archives) user = get_current_profile() if role_service.has_role(user, Role.RESPONSABLE, "*"): scopes = SCOPES_RESPONSABLE else: scopes = SCOPES result = [] for scope in scopes: table_view = get_table_view(scope, user, archives) if table_view: result.append({ "title": table_view.title, "scope": scope, "archives": archives }) return result
def post_process_form_and_model(form, model=None): user = get_current_profile() roles = role_service.get_roles_for_user(user) structures_d_affectation = roles[Role.MEMBRE_AFFECTE] structures_comme_gestionnaire = roles[Role.GESTIONNAIRE] structures_comme_porteur = roles[Role.PORTEUR] structures = [] if structures_comme_gestionnaire: structures += structures_comme_gestionnaire if structures_comme_porteur: structures += structures_comme_porteur field = form.get_field("laboratoire") field.choices = [{"value": s.id, "label": s.nom} for s in structures] porteurs = {user} for structure in structures_comme_gestionnaire: membres = role_service.get_users_with_given_role( Role.PORTEUR, structure) porteurs.update(membres) field = form.get_field("porteur") choices = [{ "value": user.id, "label": f"{user.nom}, {user.prenom}" } for user in sorted(porteurs, key=lambda x: (x.nom, x.prenom))] field.choices = choices if not model: return porteur = user model["porteur"] = { "value": porteur.id, "label": f"{user.nom}, {user.prenom}" } structure = first(structures_d_affectation) model["laboratoire"] = {"value": structure.id, "label": structure.nom}
def feuille_cout_editable(demande: Demande) -> bool: """ Feuille de coût : celle-ci devient modifiable par le Contact Lab&Co lorsqu'il a la main. Chaque enregistrement, par le Porteur, un Contributeur, un Gestionnaire ou un Contact... https://trello.com/c/O702eRzQ/ """ if not isinstance(demande, DemandeConvention): return False user = get_current_profile() if user in (demande.porteur, demande.gestionnaire, demande.contact_labco): return True if is_gestionnaire(user, demande): return True if is_contributeur(user, demande): return True return False
def create_demande(model, form): user = get_current_profile() # TODO: check permissions model = cleanup_model(model, form) form_type = form["name"] demande = demande_factory(type=form_type, demandeur=user, data=model) demande.porteur = get_porteur(model) demande.structure = get_structure(model) if user != demande.porteur: demande.gestionnaire = user else: demande.gestionnaire = None demande.form_state = form new_id = db.session.query(func.max(Demande.id)).one()[0] + 1 demande.id = new_id db.session.add(demande) db.session.commit() messages = [ ["Votre demande a été créée.", "success"], ] if not demande.is_valid(): messages.append( ["Attention, votre demande est encore incomplète.", "warning"]) whoosh.index_object(demande) return { "id": demande.id, "messages": messages, }
def is_active(self) -> bool: profile = get_current_profile() required_roles = self.get("requires_role") precondition = self.get("precondition") if precondition: return precondition() if not required_roles: return True for role in required_roles: if role == "alc" and profile.has_role(Role.ADMIN_CENTRAL): return True if role == "directeur" and profile.has_role(Role.RESPONSABLE, "*"): return True if isinstance(role, Role) and profile.has_role(role): return True return False
def preferences_post(): user = get_current_profile() payload = request.json pref = payload["preferences_notifications"] nb_jours_notification = payload["nb_jours_notification"] modified = False if pref != user.preferences_notifications: user.preferences_notifications = pref modified = True if nb_jours_notification != user.preferences_nb_jours_notifications: user.preferences_nb_jours_notifications = nb_jours_notification modified = True if modified: db.session.commit() flash("Vos préférences ont été mises à jours") return "", 204
def get_structure_choices(): l2_ids = (db.session.query( StatsLine.l2).filter(StatsLine.l2 != None).distinct().all()) l3_ids = (db.session.query( StatsLine.l3).filter(StatsLine.l3 != None).distinct().all()) l4_ids = (db.session.query( StatsLine.l4).filter(StatsLine.l4 != None).distinct().all()) l5_ids = (db.session.query( StatsLine.l5).filter(StatsLine.l5 != None).distinct().all()) l6_ids = (db.session.query( StatsLine.l6).filter(StatsLine.l6 != None).distinct().all()) user = get_current_profile() if user.has_role(Role.RESPONSABLE, "*"): structures = mes_structures(user) else: ids = l2_ids + l3_ids + l4_ids + l5_ids + l6_ids structure_ids = [x[0] for x in ids] query = db.session.query(Structure) structures = [query.get(id) for id in structure_ids] to_sort = [(structure.path + [structure.nom], structure) for structure in structures] to_sort.sort(key=lambda x: x[0]) structures = [t[1] for t in to_sort] def make_label(structure): prefix = "+-" * structure.depth return f"{prefix}{structure.nom} ({structure.type})" return [{ "value": "", "text": "-" }] + [{ "value": str(s.id), "text": make_label(s) } for s in structures]
def upload(request: Request, db: SQLAlchemy): user = get_current_profile() form = request.form demande_id = form["demande_id"] demande = db.session.query(Demande).get(demande_id) check_can_add_pj(demande) files = request.files for file in files.values(): file_name = file.filename data = file.read() blob = Blob(data) db.session.add(blob) db.session.flush() demande.attachments[file_name] = { "date": datetime.now().isoformat(), "id": blob.id, "creator": user.login, } db.session.commit() return "OK"
def get_user_context() -> JSONDict: user = get_current_profile() return get_context(user)
def get_possible_transitions(self, obj: Demande) -> JSON: user = get_current_profile() return obj.get_workflow(user).possible_transitions()