diff --git a/app/entreprises/__init__.py b/app/entreprises/__init__.py index 44968ffb..b211670b 100644 --- a/app/entreprises/__init__.py +++ b/app/entreprises/__init__.py @@ -4,10 +4,11 @@ from flask import Blueprint from app.scodoc import sco_etud from app.auth.models import User +from app.models import Departement bp = Blueprint("entreprises", __name__) -LOGS_LEN = 10 +LOGS_LEN = 5 @bp.app_template_filter() @@ -21,9 +22,21 @@ def format_nom(s): @bp.app_template_filter() -def get_nomcomplet(s): +def get_nomcomplet_by_username(s): user = User.query.filter_by(user_name=s).first() return user.get_nomcomplet() +@bp.app_template_filter() +def get_nomcomplet_by_id(id): + user = User.query.filter_by(id=id).first() + return user.get_nomcomplet() + + +@bp.app_template_filter() +def get_dept_acronym(id): + dept = Departement.query.filter_by(id=id).first() + return dept.acronym + + from app.entreprises import routes diff --git a/app/entreprises/app_relations_entreprises.py b/app/entreprises/app_relations_entreprises.py new file mode 100644 index 00000000..c47000ce --- /dev/null +++ b/app/entreprises/app_relations_entreprises.py @@ -0,0 +1,153 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +############################################################################## + +import os +from config import Config +import re +import requests +import glob + +from flask_login import current_user + +from app.entreprises.models import ( + Entreprise, + EntrepriseContact, + EntrepriseOffre, + EntrepriseOffreDepartement, +) + +from app.models import Departement +from app.scodoc.sco_permissions import Permission + + +def get_depts(): + """ + Retourne une liste contenant les l'id des départements des roles de l'utilisateur courant + """ + depts = [] + for role in current_user.user_roles: + dept_id = get_dept_id_by_acronym(role.dept) + if dept_id is not None: + depts.append(dept_id) + return depts + + +def get_dept_id_by_acronym(acronym): + """ + Retourne l'id d'un departement a l'aide de son acronym + """ + dept = Departement.query.filter_by(acronym=acronym).first() + if dept is not None: + return dept.id + return None + + +def check_offre_depts(depts, offre_depts): + """ + Retourne vrai si l'utilisateur a le droit de visibilité sur l'offre + """ + if current_user.has_permission(Permission.RelationsEntreprisesChange, None): + return True + for offre_dept in offre_depts: + if offre_dept.dept_id in depts: + return True + return False + + +def get_offre_files_and_depts(offre: EntrepriseOffre, depts: list): + """ + Retourne l'offre, les fichiers attachés a l'offre et les département liés + """ + offre_depts = EntrepriseOffreDepartement.query.filter_by(offre_id=offre.id).all() + if not offre_depts or check_offre_depts(depts, offre_depts): + files = [] + path = os.path.join( + Config.SCODOC_VAR_DIR, + "entreprises", + f"{offre.entreprise_id}", + f"{offre.id}", + ) + if os.path.exists(path): + for dir in glob.glob( + f"{path}/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]" + ): + for _file in glob.glob(f"{dir}/*"): + file = [os.path.basename(dir), os.path.basename(_file)] + files.append(file) + return [offre, files, offre_depts] + return None + + +def verif_contact_data(contact_data): + """ + Verifie les données d'une ligne Excel (contact) + contact_data[0]: nom + contact_data[1]: prenom + contact_data[2]: telephone + contact_data[3]: mail + contact_data[4]: poste + contact_data[5]: service + contact_data[6]: entreprise_id + """ + # champs obligatoires + if contact_data[0] == "" or contact_data[1] == "" or contact_data[6] == "": + return False + + # entreprise_id existant + entreprise = Entreprise.query.filter_by(id=contact_data[6]).first() + if entreprise is None: + return False + + # contact possède le meme nom et prénom dans la meme entreprise + contact = EntrepriseContact.query.filter_by( + nom=contact_data[0], prenom=contact_data[1], entreprise_id=contact_data[6] + ).first() + if contact is not None: + return False + + if contact_data[2] == "" and contact_data[3] == "": # 1 moyen de contact + return False + + return True + + +def verif_entreprise_data(entreprise_data): + """ + Verifie les données d'une ligne Excel (entreprise) + """ + for data in entreprise_data: # champs obligatoires + if data == "": + return False + siret = entreprise_data[0].strip() # vérification sur le siret + if re.match("^\d{14}$", siret) is None: + return False + req = requests.get(f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}") + if req.status_code != 200: + return False + entreprise = Entreprise.query.filter_by(siret=siret).first() + if entreprise is not None: + return False + return True diff --git a/app/entreprises/forms.py b/app/entreprises/forms.py index 1f76732d..7bc1a102 100644 --- a/app/entreprises/forms.py +++ b/app/entreprises/forms.py @@ -31,70 +31,76 @@ from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileAllowed, FileRequired from markupsafe import Markup from sqlalchemy import text -from wtforms import StringField, SubmitField, TextAreaField, SelectField, HiddenField -from wtforms.fields import EmailField, DateField -from wtforms.validators import ValidationError, DataRequired, Email +from wtforms import ( + StringField, + SubmitField, + TextAreaField, + SelectField, + HiddenField, + SelectMultipleField, + DateField, +) +from wtforms.validators import ValidationError, DataRequired, Email, Optional +from wtforms.widgets import ListWidget, CheckboxInput from app.entreprises.models import Entreprise, EntrepriseContact -from app.models import Identite +from app.models import Identite, Departement from app.auth.models import User CHAMP_REQUIS = "Ce champ est requis" +SUBMIT_MARGE = {"style": "margin-bottom: 10px;"} + + +def _build_string_field(label, required=True, render_kw=None): + if required: + return StringField( + label, + validators=[DataRequired(message=CHAMP_REQUIS)], + render_kw=render_kw, + ) + else: + return StringField(label, validators=[Optional()], render_kw=render_kw) class EntrepriseCreationForm(FlaskForm): - siret = StringField( + siret = _build_string_field( "SIRET", - validators=[DataRequired(message=CHAMP_REQUIS)], render_kw={"placeholder": "Numéro composé de 14 chiffres", "maxlength": "14"}, ) - nom_entreprise = StringField( - "Nom de l'entreprise", - validators=[DataRequired(message=CHAMP_REQUIS)], - ) - adresse = StringField( - "Adresse de l'entreprise", - validators=[DataRequired(message=CHAMP_REQUIS)], - ) - codepostal = StringField( - "Code postal de l'entreprise", - validators=[DataRequired(message=CHAMP_REQUIS)], - ) - ville = StringField( - "Ville de l'entreprise", - validators=[DataRequired(message=CHAMP_REQUIS)], - ) - pays = StringField( - "Pays de l'entreprise", - validators=[DataRequired(message=CHAMP_REQUIS)], - render_kw={"style": "margin-bottom: 50px;"}, - ) + nom_entreprise = _build_string_field("Nom de l'entreprise") + adresse = _build_string_field("Adresse de l'entreprise") + codepostal = _build_string_field("Code postal de l'entreprise") + ville = _build_string_field("Ville de l'entreprise") + pays = _build_string_field("Pays de l'entreprise") - nom_contact = StringField( - "Nom du contact", validators=[DataRequired(message=CHAMP_REQUIS)] - ) - prenom_contact = StringField( - "Prénom du contact", - validators=[DataRequired(message=CHAMP_REQUIS)], - ) - telephone = StringField( - "Téléphone du contact", - validators=[DataRequired(message=CHAMP_REQUIS)], - ) - mail = EmailField( + nom_contact = _build_string_field("Nom du contact") + prenom_contact = _build_string_field("Prénom du contact") + telephone = _build_string_field("Téléphone du contact", required=False) + mail = StringField( "Mail du contact", - validators=[ - DataRequired(message=CHAMP_REQUIS), - Email(message="Adresse e-mail invalide"), - ], + validators=[Optional(), Email(message="Adresse e-mail invalide")], ) - poste = StringField("Poste du contact", validators=[]) - service = StringField("Service du contact", validators=[]) - submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"}) + poste = _build_string_field("Poste du contact", required=False) + service = _build_string_field("Service du contact", required=False) + submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) + + def validate(self): + validate = True + if not FlaskForm.validate(self): + validate = False + + if not self.telephone.data and not self.mail.data: + self.telephone.errors.append( + "Saisir un moyen de contact (mail ou téléphone)" + ) + self.mail.errors.append("Saisir un moyen de contact (mail ou téléphone)") + validate = False + + return validate def validate_siret(self, siret): siret = siret.data.strip() - if re.match("^\d{14}$", siret) == None: + if re.match("^\d{14}$", siret) is None: raise ValidationError("Format incorrect") req = requests.get( f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}" @@ -110,22 +116,22 @@ class EntrepriseCreationForm(FlaskForm): class EntrepriseModificationForm(FlaskForm): - siret = StringField("SIRET", validators=[], render_kw={"disabled": ""}) - nom = StringField( - "Nom de l'entreprise", - validators=[DataRequired(message=CHAMP_REQUIS)], - ) - adresse = StringField("Adresse", validators=[DataRequired(message=CHAMP_REQUIS)]) - codepostal = StringField( - "Code postal", validators=[DataRequired(message=CHAMP_REQUIS)] - ) - ville = StringField("Ville", validators=[DataRequired(message=CHAMP_REQUIS)]) - pays = StringField("Pays", validators=[DataRequired(message=CHAMP_REQUIS)]) - submit = SubmitField("Modifier", render_kw={"style": "margin-bottom: 10px;"}) + siret = StringField("SIRET", render_kw={"disabled": ""}) + nom = _build_string_field("Nom de l'entreprise") + adresse = _build_string_field("Adresse") + codepostal = _build_string_field("Code postal") + ville = _build_string_field("Ville") + pays = _build_string_field("Pays") + submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) + + +class MultiCheckboxField(SelectMultipleField): + widget = ListWidget(prefix_label=False) + option_widget = CheckboxInput() class OffreCreationForm(FlaskForm): - intitule = StringField("Intitulé", validators=[DataRequired(message=CHAMP_REQUIS)]) + intitule = _build_string_field("Intitulé") description = TextAreaField( "Description", validators=[DataRequired(message=CHAMP_REQUIS)] ) @@ -134,15 +140,24 @@ class OffreCreationForm(FlaskForm): choices=[("Stage"), ("Alternance")], validators=[DataRequired(message=CHAMP_REQUIS)], ) - missions = TextAreaField( - "Missions", validators=[DataRequired(message=CHAMP_REQUIS)] + missions = _build_string_field("Missions") + duree = _build_string_field("Durée") + depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int) + expiration_date = DateField( + "Date expiration", validators=[DataRequired(message=CHAMP_REQUIS)] ) - duree = StringField("Durée", validators=[DataRequired(message=CHAMP_REQUIS)]) - submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"}) + submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.depts.choices = [ + (dept.id, dept.acronym) for dept in Departement.query.all() + ] class OffreModificationForm(FlaskForm): - intitule = StringField("Intitulé", validators=[DataRequired(message=CHAMP_REQUIS)]) + intitule = _build_string_field("Intitulé") description = TextAreaField( "Description", validators=[DataRequired(message=CHAMP_REQUIS)] ) @@ -151,73 +166,104 @@ class OffreModificationForm(FlaskForm): choices=[("Stage"), ("Alternance")], validators=[DataRequired(message=CHAMP_REQUIS)], ) - missions = TextAreaField( - "Missions", validators=[DataRequired(message=CHAMP_REQUIS)] + missions = _build_string_field("Missions") + duree = _build_string_field("Durée") + depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int) + expiration_date = DateField( + "Date expiration", validators=[DataRequired(message=CHAMP_REQUIS)] ) - duree = StringField("Durée", validators=[DataRequired(message=CHAMP_REQUIS)]) - submit = SubmitField("Modifier", render_kw={"style": "margin-bottom: 10px;"}) + submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.depts.choices = [ + (dept.id, dept.acronym) for dept in Departement.query.all() + ] class ContactCreationForm(FlaskForm): hidden_entreprise_id = HiddenField() - nom = StringField("Nom", validators=[DataRequired(message=CHAMP_REQUIS)]) - prenom = StringField("Prénom", validators=[DataRequired(message=CHAMP_REQUIS)]) - telephone = StringField( - "Téléphone", validators=[DataRequired(message=CHAMP_REQUIS)] - ) - mail = EmailField( + nom = _build_string_field("Nom") + prenom = _build_string_field("Prénom") + telephone = _build_string_field("Téléphone", required=False) + mail = StringField( "Mail", - validators=[ - DataRequired(message=CHAMP_REQUIS), - Email(message="Adresse e-mail invalide"), - ], + validators=[Optional(), Email(message="Adresse e-mail invalide")], ) - poste = StringField("Poste", validators=[]) - service = StringField("Service", validators=[]) - submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"}) + poste = _build_string_field("Poste", required=False) + service = _build_string_field("Service", required=False) + submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) def validate(self): - rv = FlaskForm.validate(self) - if not rv: - return False + validate = True + if not FlaskForm.validate(self): + validate = False contact = EntrepriseContact.query.filter_by( entreprise_id=self.hidden_entreprise_id.data, nom=self.nom.data, prenom=self.prenom.data, ).first() - if contact is not None: self.nom.errors.append("Ce contact existe déjà (même nom et prénom)") self.prenom.errors.append("") - return False + validate = False - return True + if not self.telephone.data and not self.mail.data: + self.telephone.errors.append( + "Saisir un moyen de contact (mail ou téléphone)" + ) + self.mail.errors.append("Saisir un moyen de contact (mail ou téléphone)") + validate = False + + return validate class ContactModificationForm(FlaskForm): - nom = StringField("Nom", validators=[DataRequired(message=CHAMP_REQUIS)]) - prenom = StringField("Prénom", validators=[DataRequired(message=CHAMP_REQUIS)]) - telephone = StringField( - "Téléphone", validators=[DataRequired(message=CHAMP_REQUIS)] - ) - mail = EmailField( + hidden_contact_id = HiddenField() + hidden_entreprise_id = HiddenField() + nom = _build_string_field("Nom") + prenom = _build_string_field("Prénom") + telephone = _build_string_field("Téléphone", required=False) + mail = StringField( "Mail", - validators=[ - DataRequired(message=CHAMP_REQUIS), - Email(message="Adresse e-mail invalide"), - ], + validators=[Optional(), Email(message="Adresse e-mail invalide")], ) - poste = StringField("Poste", validators=[]) - service = StringField("Service", validators=[]) - submit = SubmitField("Modifier", render_kw={"style": "margin-bottom: 10px;"}) + poste = _build_string_field("Poste", required=False) + service = _build_string_field("Service", required=False) + submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) + + def validate(self): + validate = True + if not FlaskForm.validate(self): + validate = False + + contact = EntrepriseContact.query.filter( + EntrepriseContact.id != self.hidden_contact_id.data, + EntrepriseContact.entreprise_id == self.hidden_entreprise_id.data, + EntrepriseContact.nom == self.nom.data, + EntrepriseContact.prenom == self.prenom.data, + ).first() + if contact is not None: + self.nom.errors.append("Ce contact existe déjà (même nom et prénom)") + self.prenom.errors.append("") + validate = False + + if not self.telephone.data and not self.mail.data: + self.telephone.errors.append( + "Saisir un moyen de contact (mail ou téléphone)" + ) + self.mail.errors.append("Saisir un moyen de contact (mail ou téléphone)") + validate = False + + return validate class HistoriqueCreationForm(FlaskForm): - etudiant = StringField( + etudiant = _build_string_field( "Étudiant", - validators=[DataRequired(message=CHAMP_REQUIS)], - render_kw={"placeholder": "Tapez le nom de l'étudiant puis selectionnez"}, + render_kw={"placeholder": "Tapez le nom de l'étudiant"}, ) type_offre = SelectField( "Type de l'offre", @@ -228,18 +274,23 @@ class HistoriqueCreationForm(FlaskForm): "Date début", validators=[DataRequired(message=CHAMP_REQUIS)] ) date_fin = DateField("Date fin", validators=[DataRequired(message=CHAMP_REQUIS)]) - submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"}) + submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) def validate(self): - rv = FlaskForm.validate(self) - if not rv: - return False + validate = True + if not FlaskForm.validate(self): + validate = False - if self.date_debut.data > self.date_fin.data: + if ( + self.date_debut.data + and self.date_fin.data + and self.date_debut.data > self.date_fin.data + ): self.date_debut.errors.append("Les dates sont incompatibles") self.date_fin.errors.append("Les dates sont incompatibles") - return False - return True + validate = False + + return validate def validate_etudiant(self, etudiant): etudiant_data = etudiant.data.upper().strip() @@ -254,11 +305,11 @@ class HistoriqueCreationForm(FlaskForm): class EnvoiOffreForm(FlaskForm): - responsable = StringField( + responsable = _build_string_field( "Responsable de formation", - validators=[DataRequired(message=CHAMP_REQUIS)], + render_kw={"placeholder": "Tapez le nom du responsable de formation"}, ) - submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"}) + submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) def validate_responsable(self, responsable): responsable_data = responsable.data.upper().strip() @@ -282,8 +333,23 @@ class AjoutFichierForm(FlaskForm): FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"), ], ) - submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"}) + submit = SubmitField("Ajouter", render_kw=SUBMIT_MARGE) class SuppressionConfirmationForm(FlaskForm): - submit = SubmitField("Supprimer", render_kw={"style": "margin-bottom: 10px;"}) + submit = SubmitField("Supprimer", render_kw=SUBMIT_MARGE) + + +class ValidationConfirmationForm(FlaskForm): + submit = SubmitField("Valider", render_kw=SUBMIT_MARGE) + + +class ImportForm(FlaskForm): + fichier = FileField( + "Fichier", + validators=[ + FileRequired(message=CHAMP_REQUIS), + FileAllowed(["xlsx"], "Fichier .xlsx uniquement"), + ], + ) + submit = SubmitField("Importer", render_kw=SUBMIT_MARGE) diff --git a/app/entreprises/models.py b/app/entreprises/models.py index e743df3d..9a99f4bc 100644 --- a/app/entreprises/models.py +++ b/app/entreprises/models.py @@ -2,7 +2,7 @@ from app import db class Entreprise(db.Model): - __tablename__ = "entreprises" + __tablename__ = "are_entreprises" id = db.Column(db.Integer, primary_key=True) siret = db.Column(db.Text) nom = db.Column(db.Text) @@ -10,6 +10,7 @@ class Entreprise(db.Model): codepostal = db.Column(db.Text) ville = db.Column(db.Text) pays = db.Column(db.Text) + visible = db.Column(db.Boolean, default=False) contacts = db.relationship( "EntrepriseContact", backref="entreprise", @@ -28,17 +29,17 @@ class Entreprise(db.Model): "siret": self.siret, "nom": self.nom, "adresse": self.adresse, - "codepostal": self.codepostal, + "code_postal": self.codepostal, "ville": self.ville, "pays": self.pays, } class EntrepriseContact(db.Model): - __tablename__ = "entreprise_contact" + __tablename__ = "are_entreprise_contact" id = db.Column(db.Integer, primary_key=True) entreprise_id = db.Column( - db.Integer, db.ForeignKey("entreprises.id", ondelete="cascade") + db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade") ) nom = db.Column(db.Text) prenom = db.Column(db.Text) @@ -55,31 +56,15 @@ class EntrepriseContact(db.Model): "mail": self.mail, "poste": self.poste, "service": self.service, - } - - def to_dict_export(self): - entreprise = Entreprise.query.get(self.entreprise_id) - return { - "nom": self.nom, - "prenom": self.prenom, - "telephone": self.telephone, - "mail": self.mail, - "poste": self.poste, - "service": self.service, - "siret": entreprise.siret, - "nom_entreprise": entreprise.nom, - "adresse_entreprise": entreprise.adresse, - "codepostal": entreprise.codepostal, - "ville": entreprise.ville, - "pays": entreprise.pays, + "entreprise_id": self.entreprise_id, } class EntrepriseOffre(db.Model): - __tablename__ = "entreprise_offre" + __tablename__ = "are_entreprise_offre" id = db.Column(db.Integer, primary_key=True) entreprise_id = db.Column( - db.Integer, db.ForeignKey("entreprises.id", ondelete="cascade") + db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade") ) date_ajout = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) intitule = db.Column(db.Text) @@ -87,6 +72,7 @@ class EntrepriseOffre(db.Model): type_offre = db.Column(db.Text) missions = db.Column(db.Text) duree = db.Column(db.Text) + expiration_date = db.Column(db.Date) def to_dict(self): return { @@ -99,7 +85,7 @@ class EntrepriseOffre(db.Model): class EntrepriseLog(db.Model): - __tablename__ = "entreprise_log" + __tablename__ = "are_entreprise_log" id = db.Column(db.Integer, primary_key=True) date = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) authenticated_user = db.Column(db.Text) @@ -108,9 +94,11 @@ class EntrepriseLog(db.Model): class EntrepriseEtudiant(db.Model): - __tablename__ = "entreprise_etudiant" + __tablename__ = "are_entreprise_etudiant" id = db.Column(db.Integer, primary_key=True) - entreprise_id = db.Column(db.Integer, db.ForeignKey("entreprises.id")) + entreprise_id = db.Column( + db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade") + ) etudid = db.Column(db.Integer) type_offre = db.Column(db.Text) date_debut = db.Column(db.Date) @@ -120,18 +108,33 @@ class EntrepriseEtudiant(db.Model): class EntrepriseEnvoiOffre(db.Model): - __tablename__ = "entreprise_envoi_offre" + __tablename__ = "are_entreprise_envoi_offre" id = db.Column(db.Integer, primary_key=True) - sender_id = db.Column(db.Integer, db.ForeignKey("user.id")) - receiver_id = db.Column(db.Integer, db.ForeignKey("user.id")) - offre_id = db.Column(db.Integer, db.ForeignKey("entreprise_offre.id")) + sender_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="cascade")) + receiver_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="cascade")) + offre_id = db.Column( + db.Integer, db.ForeignKey("are_entreprise_offre.id", ondelete="cascade") + ) date_envoi = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) class EntrepriseEnvoiOffreEtudiant(db.Model): - __tablename__ = "entreprise_envoi_offre_etudiant" + __tablename__ = "are_entreprise_envoi_offre_etudiant" id = db.Column(db.Integer, primary_key=True) - sender_id = db.Column(db.Integer, db.ForeignKey("user.id")) - receiver_id = db.Column(db.Integer, db.ForeignKey("identite.id")) - offre_id = db.Column(db.Integer, db.ForeignKey("entreprise_offre.id")) + sender_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="cascade")) + receiver_id = db.Column( + db.Integer, db.ForeignKey("identite.id", ondelete="cascade") + ) + offre_id = db.Column( + db.Integer, db.ForeignKey("are_entreprise_offre.id", ondelete="cascade") + ) date_envoi = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) + + +class EntrepriseOffreDepartement(db.Model): + __tablename__ = "are_entreprise_offre_departement" + id = db.Column(db.Integer, primary_key=True) + offre_id = db.Column( + db.Integer, db.ForeignKey("are_entreprise_offre.id", ondelete="cascade") + ) + dept_id = db.Column(db.Integer, db.ForeignKey("departement.id", ondelete="cascade")) diff --git a/app/entreprises/routes.py b/app/entreprises/routes.py index f99fa8ee..e3226c71 100644 --- a/app/entreprises/routes.py +++ b/app/entreprises/routes.py @@ -1,6 +1,6 @@ import os from config import Config -from datetime import datetime, timedelta +from datetime import datetime, date import glob import shutil @@ -22,6 +22,8 @@ from app.entreprises.forms import ( HistoriqueCreationForm, EnvoiOffreForm, AjoutFichierForm, + ValidationConfirmationForm, + ImportForm, ) from app.entreprises import bp from app.entreprises.models import ( @@ -31,7 +33,9 @@ from app.entreprises.models import ( EntrepriseLog, EntrepriseEtudiant, EntrepriseEnvoiOffre, + EntrepriseOffreDepartement, ) +from app.entreprises import app_relations_entreprises as are from app.models import Identite from app.auth.models import User from app.scodoc.sco_permissions import Permission @@ -44,105 +48,95 @@ from werkzeug.utils import secure_filename @bp.route("/", methods=["GET"]) +@permission_required(Permission.RelationsEntreprisesView) def index(): """ Permet d'afficher une page avec la liste des entreprises et une liste des dernières opérations - - Retourne: template de la page (entreprises.html) - Arguments du template: - title: - titre de la page - entreprises: - liste des entreprises - logs: - liste des logs """ - entreprises = Entreprise.query.all() + page = request.args.get("page", 1, type=int) + entreprises = Entreprise.query.filter_by(visible=True).paginate( + page=page, per_page=10 + ) logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all() return render_template( "entreprises/entreprises.html", - title=("Entreprises"), + title="Entreprises", entreprises=entreprises, logs=logs, ) +@bp.route("/logs", methods=["GET"]) +@permission_required(Permission.RelationsEntreprisesView) +def logs(): + """ + Permet d'afficher les logs (toutes les entreprises) + """ + page = request.args.get("page", 1, type=int) + logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).paginate( + page=page, per_page=20 + ) + return render_template( + "entreprises/logs.html", + title="Logs", + logs=logs, + ) + + +@bp.route("/validation", methods=["GET"]) +@permission_required(Permission.RelationsEntreprisesValidate) +def validation(): + """ + Permet d'afficher une page avec la liste des entreprises a valider + """ + entreprises = Entreprise.query.filter_by(visible=False).all() + return render_template( + "entreprises/entreprises_validation.html", + title="Validation entreprises", + entreprises=entreprises, + ) + + @bp.route("/contacts", methods=["GET"]) +@permission_required(Permission.RelationsEntreprisesView) def contacts(): """ Permet d'afficher une page la liste des contacts et une liste des dernières opérations - - Retourne: template de la page (contacts.html) - Arguments du template: - title: - titre de la page - contacts: - liste des contacts - logs: - liste des logs """ + page = request.args.get("page", 1, type=int) contacts = ( db.session.query(EntrepriseContact, Entreprise) .join(Entreprise, EntrepriseContact.entreprise_id == Entreprise.id) - .all() + .filter_by(visible=True) + .paginate(page=page, per_page=10) ) logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all() return render_template( - "entreprises/contacts.html", title=("Contacts"), contacts=contacts, logs=logs + "entreprises/contacts.html", + title="Contacts", + contacts=contacts, + logs=logs, ) @bp.route("/fiche_entreprise/", methods=["GET"]) +@permission_required(Permission.RelationsEntreprisesView) def fiche_entreprise(id): """ Permet d'afficher la fiche entreprise d'une entreprise avec une liste des dernières opérations et l'historique des étudiants ayant réaliser un stage ou une alternance dans cette entreprise. La fiche entreprise comporte les informations de l'entreprise, les contacts de l'entreprise et les offres de l'entreprise. - - Arguments: - id: - l'id de l'entreprise - - Retourne: template de la page (fiche_entreprise.html) - Arguments du template: - title: - titre de la page - entreprise: - un objet entreprise - contacts: - liste des contacts de l'entreprise - offres: - liste des offres de l'entreprise avec leurs fichiers - logs: - liste des logs - historique: - liste des étudiants ayant réaliser un stage ou une alternance dans l'entreprise """ - entreprise = Entreprise.query.filter_by(id=id).first_or_404() - offres = entreprise.offres + entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404() offres_with_files = [] - for offre in offres: - if datetime.now() - offre.date_ajout.replace(tzinfo=None) >= timedelta( - days=90 - ): # pour une date d'expiration ? - break - files = [] - path = os.path.join( - Config.SCODOC_VAR_DIR, - "entreprises", - f"{offre.entreprise_id}", - f"{offre.id}", - ) - if os.path.exists(path): - for dir in glob.glob( - f"{path}/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]" - ): - for file in glob.glob(f"{dir}/*"): - file = [os.path.basename(dir), os.path.basename(file)] - files.append(file) - offres_with_files.append([offre, files]) - contacts = entreprise.contacts + depts = are.get_depts() + for offre in entreprise.offres: + if date.today() < offre.expiration_date: + offre_with_files = are.get_offre_files_and_depts(offre, depts) + if offre_with_files is not None: + offres_with_files.append(offre_with_files) + contacts = entreprise.contacts[:] logs = ( EntrepriseLog.query.order_by(EntrepriseLog.date.desc()) .filter_by(object=id) @@ -158,7 +152,7 @@ def fiche_entreprise(id): ) return render_template( "entreprises/fiche_entreprise.html", - title=("Fiche entreprise"), + title="Fiche entreprise", entreprise=entreprise, contacts=contacts, offres=offres_with_files, @@ -167,17 +161,48 @@ def fiche_entreprise(id): ) -@bp.route("/offres", methods=["GET"]) -def offres(): +@bp.route("/logs/", methods=["GET"]) +@permission_required(Permission.RelationsEntreprisesView) +def logs_entreprise(id): + """ + Permet d'afficher les logs (toutes les entreprises) + """ + page = request.args.get("page", 1, type=int) + entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404() + logs = ( + EntrepriseLog.query.order_by(EntrepriseLog.date.desc()) + .filter_by(object=id) + .paginate(page=page, per_page=20) + ) + return render_template( + "entreprises/logs_entreprise.html", + title="Logs", + logs=logs, + entreprise=entreprise, + ) + + +@bp.route("/fiche_entreprise_validation/", methods=["GET"]) +@permission_required(Permission.RelationsEntreprisesValidate) +def fiche_entreprise_validation(id): + """ + Permet d'afficher la fiche entreprise d'une entreprise a valider + """ + entreprise = Entreprise.query.filter_by(id=id, visible=False).first_or_404() + contacts = entreprise.contacts + return render_template( + "entreprises/fiche_entreprise_validation.html", + title="Validation fiche entreprise", + entreprise=entreprise, + contacts=contacts, + ) + + +@bp.route("/offres_recues", methods=["GET"]) +@permission_required(Permission.RelationsEntreprisesView) +def offres_recues(): """ Permet d'afficher la page où l'on recoit les offres - - Retourne: template de la page (offres.html) - Arguments du template: - title: - titre de la page - offres_recus: - liste des offres reçues """ offres_recues = ( db.session.query(EntrepriseEnvoiOffre, EntrepriseOffre) @@ -185,12 +210,54 @@ def offres(): .join(EntrepriseOffre, EntrepriseOffre.id == EntrepriseEnvoiOffre.offre_id) .all() ) + offres_recues_with_files = [] + for offre in offres_recues: + files = [] + path = os.path.join( + Config.SCODOC_VAR_DIR, + "entreprises", + f"{offre[1].entreprise_id}", + f"{offre[1].id}", + ) + if os.path.exists(path): + for dir in glob.glob( + f"{path}/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]" + ): + for file in glob.glob(f"{dir}/*"): + file = [os.path.basename(dir), os.path.basename(file)] + files.append(file) + offres_recues_with_files.append([offre[0], offre[1], files]) return render_template( - "entreprises/offres.html", title=("Offres"), offres_recues=offres_recues + "entreprises/offres_recues.html", + title="Offres reçues", + offres_recues=offres_recues_with_files, + ) + + +@bp.route("/fiche_entreprise//offres_expirees") +@permission_required(Permission.RelationsEntreprisesView) +def offres_expirees(id): + """ + Permet d'afficher la liste des offres expirés d'une entreprise + """ + entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404() + offres_expirees_with_files = [] + depts = are.get_depts() + for offre in entreprise.offres: + if date.today() > offre.expiration_date: + offre_expiree_with_files = are.get_offre_files_and_depts(offre, depts) + if offre_expiree_with_files is not None: + offres_expirees_with_files.append(offre_expiree_with_files) + return render_template( + "entreprises/offres_expirees.html", + title="Offres expirées", + entreprise=entreprise, + offres_expirees=offres_expirees_with_files, ) @bp.route("/add_entreprise", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def add_entreprise(): """ Permet d'ajouter une entreprise dans la base avec un formulaire @@ -218,32 +285,36 @@ def add_entreprise(): service=form.service.data.strip(), ) db.session.add(contact) - nom_entreprise = f"{entreprise.nom}" - log = EntrepriseLog( - authenticated_user=current_user.user_name, - text=f"{nom_entreprise} - Création de la fiche entreprise ({entreprise.nom}) avec un contact", - ) - db.session.add(log) - db.session.commit() - flash("L'entreprise a été ajouté à la liste.") - return redirect(url_for("entreprises.index")) + if current_user.has_permission(Permission.RelationsEntreprisesValidate, None): + entreprise.visible = True + nom_entreprise = f"{entreprise.nom}" + log = EntrepriseLog( + authenticated_user=current_user.user_name, + text=f"{nom_entreprise} - Création de la fiche entreprise ({entreprise.nom}) avec un contact", + ) + db.session.add(log) + db.session.commit() + flash("L'entreprise a été ajouté à la liste.") + return redirect(url_for("entreprises.index")) + else: + entreprise.visible = False + db.session.commit() + flash("L'entreprise a été ajouté à la liste pour la validation.") + return redirect(url_for("entreprises.index")) return render_template( "entreprises/ajout_entreprise.html", - title=("Ajout entreprise + contact"), + title="Ajout entreprise avec contact", form=form, ) @bp.route("/edit_entreprise/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def edit_entreprise(id): """ Permet de modifier une entreprise de la base avec un formulaire - - Arguments: - id: - l'id de l'entreprise """ - entreprise = Entreprise.query.filter_by(id=id).first_or_404() + entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404() form = EntrepriseModificationForm() if form.validate_on_submit(): nom_entreprise = f"{form.nom.data.strip()}" @@ -298,23 +369,30 @@ def edit_entreprise(id): form.ville.data = entreprise.ville form.pays.data = entreprise.pays return render_template( - "entreprises/form.html", title=("Modification entreprise"), form=form + "entreprises/form.html", + title="Modification entreprise", + form=form, ) @bp.route("/delete_entreprise/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def delete_entreprise(id): """ Permet de supprimer une entreprise de la base avec un formulaire de confirmation - - Arguments: - id: - l'id de l'entreprise """ - entreprise = Entreprise.query.filter_by(id=id).first_or_404() + entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404() form = SuppressionConfirmationForm() if form.validate_on_submit(): db.session.delete(entreprise) + # supprime les fichiers attachés aux offres + path = os.path.join( + Config.SCODOC_VAR_DIR, + "entreprises", + f"{entreprise.id}", + ) + if os.path.isdir(path): + shutil.rmtree(path) log = EntrepriseLog( authenticated_user=current_user.user_name, object=entreprise.id, @@ -326,21 +404,64 @@ def delete_entreprise(id): return redirect(url_for("entreprises.index")) return render_template( "entreprises/delete_confirmation.html", - title=("Supression entreprise"), + title="Supression entreprise", + form=form, + ) + + +@bp.route("/validate_entreprise/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesValidate) +def validate_entreprise(id): + """ + Permet de valider une entreprise + """ + form = ValidationConfirmationForm() + entreprise = Entreprise.query.filter_by(id=id, visible=False).first_or_404() + if form.validate_on_submit(): + entreprise.visible = True + nom_entreprise = f"{entreprise.nom}" + log = EntrepriseLog( + authenticated_user=current_user.user_name, + text=f"{nom_entreprise} - Validation de la fiche entreprise ({entreprise.nom}) avec un contact", + ) + db.session.add(log) + db.session.commit() + flash("L'entreprise a été validé et ajouté à la liste.") + return redirect(url_for("entreprises.index")) + return render_template( + "entreprises/validate_confirmation.html", + title="Validation entreprise", + form=form, + ) + + +@bp.route("/delete_validation_entreprise/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesValidate) +def delete_validation_entreprise(id): + """ + Permet de supprimer une entreprise en attente de validation avec une formulaire de validation + """ + entreprise = Entreprise.query.filter_by(id=id, visible=False).first_or_404() + form = SuppressionConfirmationForm() + if form.validate_on_submit(): + db.session.delete(entreprise) + db.session.commit() + flash("L'entreprise a été supprimé de la liste des entreprise à valider.") + return redirect(url_for("entreprises.validation")) + return render_template( + "entreprises/delete_confirmation.html", + title="Supression entreprise", form=form, ) @bp.route("/add_offre/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def add_offre(id): """ Permet d'ajouter une offre a une entreprise - - Arguments: - id: - l'id de l'entreprise """ - entreprise = Entreprise.query.filter_by(id=id).first_or_404() + entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404() form = OffreCreationForm() if form.validate_on_submit(): offre = EntrepriseOffre( @@ -350,37 +471,64 @@ def add_offre(id): type_offre=form.type_offre.data.strip(), missions=form.missions.data.strip(), duree=form.duree.data.strip(), + expiration_date=form.expiration_date.data, ) + db.session.add(offre) + db.session.commit() + db.session.refresh(offre) + for dept in form.depts.data: + offre_dept = EntrepriseOffreDepartement( + offre_id=offre.id, + dept_id=dept, + ) + db.session.add(offre_dept) log = EntrepriseLog( authenticated_user=current_user.user_name, object=entreprise.id, text="Création d'une offre", ) - db.session.add(offre) db.session.add(log) db.session.commit() flash("L'offre a été ajouté à la fiche entreprise.") return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id)) - return render_template("entreprises/form.html", title=("Ajout offre"), form=form) + return render_template( + "entreprises/form.html", + title="Ajout offre", + form=form, + ) @bp.route("/edit_offre/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def edit_offre(id): """ Permet de modifier une offre - - Arguments: - id: - l'id de l'offre """ offre = EntrepriseOffre.query.filter_by(id=id).first_or_404() + offre_depts = EntrepriseOffreDepartement.query.filter_by(offre_id=offre.id).all() form = OffreModificationForm() + offre_depts_list = [(offre_dept.dept_id) for offre_dept in offre_depts] if form.validate_on_submit(): offre.intitule = form.intitule.data.strip() offre.description = form.description.data.strip() offre.type_offre = form.type_offre.data.strip() offre.missions = form.missions.data.strip() offre.duree = form.duree.data.strip() + offre.expiration_date = form.expiration_date.data + if offre_depts_list != form.depts.data: + for dept in form.depts.data: + if dept not in offre_depts_list: + offre_dept = EntrepriseOffreDepartement( + offre_id=offre.id, + dept_id=dept, + ) + db.session.add(offre_dept) + for dept in offre_depts_list: + if dept not in form.depts.data: + offre_dept = EntrepriseOffreDepartement.query.filter_by( + offre_id=offre.id, dept_id=dept + ).first_or_404() + db.session.delete(offre_dept) log = EntrepriseLog( authenticated_user=current_user.user_name, object=offre.entreprise_id, @@ -396,25 +544,34 @@ def edit_offre(id): form.type_offre.data = offre.type_offre form.missions.data = offre.missions form.duree.data = offre.duree + form.expiration_date.data = offre.expiration_date + form.depts.data = offre_depts_list return render_template( - "entreprises/form.html", title=("Modification offre"), form=form + "entreprises/form.html", + title="Modification offre", + form=form, ) @bp.route("/delete_offre/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def delete_offre(id): """ Permet de supprimer une offre - - Arguments: - id: - l'id de l'offre """ offre = EntrepriseOffre.query.filter_by(id=id).first_or_404() entreprise_id = offre.entreprise.id form = SuppressionConfirmationForm() if form.validate_on_submit(): db.session.delete(offre) + path = os.path.join( + Config.SCODOC_VAR_DIR, + "entreprises", + f"{entreprise_id}", + f"{offre.id}", + ) + if os.path.isdir(path): + shutil.rmtree(path) log = EntrepriseLog( authenticated_user=current_user.user_name, object=offre.entreprise_id, @@ -425,20 +582,30 @@ def delete_offre(id): flash("L'offre a été supprimé de la fiche entreprise.") return redirect(url_for("entreprises.fiche_entreprise", id=entreprise_id)) return render_template( - "entreprises/delete_confirmation.html", title=("Supression offre"), form=form + "entreprises/delete_confirmation.html", + title="Supression offre", + form=form, ) +@bp.route("/delete_offre_recue/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesView) +def delete_offre_recue(id): + offre_recue = EntrepriseEnvoiOffre.query.filter_by( + id=id, receiver_id=current_user.id + ).first_or_404() + db.session.delete(offre_recue) + db.session.commit() + return redirect(url_for("entreprises.offres_recues")) + + @bp.route("/add_contact/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def add_contact(id): """ Permet d'ajouter un contact a une entreprise - - Arguments: - id: - l'id de l'entreprise """ - entreprise = Entreprise.query.filter_by(id=id).first_or_404() + entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404() form = ContactCreationForm(hidden_entreprise_id=entreprise.id) if form.validate_on_submit(): contact = EntrepriseContact( @@ -460,20 +627,24 @@ def add_contact(id): db.session.commit() flash("Le contact a été ajouté à la fiche entreprise.") return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id)) - return render_template("entreprises/form.html", title=("Ajout contact"), form=form) + return render_template( + "entreprises/form.html", + title="Ajout contact", + form=form, + ) @bp.route("/edit_contact/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def edit_contact(id): """ Permet de modifier un contact - - Arguments: - id: - l'id du contact """ contact = EntrepriseContact.query.filter_by(id=id).first_or_404() - form = ContactModificationForm() + form = ContactModificationForm( + hidden_entreprise_id=contact.entreprise_id, + hidden_contact_id=contact.id, + ) if form.validate_on_submit(): contact.nom = form.nom.data.strip() contact.prenom = form.prenom.data.strip() @@ -500,31 +671,31 @@ def edit_contact(id): form.poste.data = contact.poste form.service.data = contact.service return render_template( - "entreprises/form.html", title=("Modification contact"), form=form + "entreprises/form.html", + title="Modification contact", + form=form, ) @bp.route("/delete_contact/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def delete_contact(id): """ Permet de supprimer un contact - - Arguments: - id: - l'id du contact """ contact = EntrepriseContact.query.filter_by(id=id).first_or_404() - entreprise_id = contact.entreprise.id form = SuppressionConfirmationForm() if form.validate_on_submit(): contact_count = EntrepriseContact.query.filter_by( - entreprise_id=contact.entreprise.id + entreprise_id=contact.entreprise_id ).count() if contact_count == 1: flash( "Le contact n'a pas été supprimé de la fiche entreprise. (1 contact minimum)" ) - return redirect(url_for("entreprises.fiche_entreprise", id=entreprise_id)) + return redirect( + url_for("entreprises.fiche_entreprise", id=contact.entreprise_id) + ) else: db.session.delete(contact) log = EntrepriseLog( @@ -535,22 +706,23 @@ def delete_contact(id): db.session.add(log) db.session.commit() flash("Le contact a été supprimé de la fiche entreprise.") - return redirect(url_for("entreprises.fiche_entreprise", id=entreprise_id)) + return redirect( + url_for("entreprises.fiche_entreprise", id=contact.entreprise_id) + ) return render_template( - "entreprises/delete_confirmation.html", title=("Supression contact"), form=form + "entreprises/delete_confirmation.html", + title="Supression contact", + form=form, ) @bp.route("/add_historique/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def add_historique(id): """ Permet d'ajouter un étudiant ayant réalisé un stage ou une alternance sur la fiche entreprise de l'entreprise - - Arguments: - id: - l'id de l'entreprise """ - entreprise = Entreprise.query.filter_by(id=id).first_or_404() + entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404() form = HistoriqueCreationForm() if form.validate_on_submit(): etudiant_nomcomplet = form.etudiant.data.upper().strip() @@ -581,18 +753,17 @@ def add_historique(id): flash("L'étudiant a été ajouté sur la fiche entreprise.") return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id)) return render_template( - "entreprises/ajout_historique.html", title=("Ajout historique"), form=form + "entreprises/ajout_historique.html", + title="Ajout historique", + form=form, ) @bp.route("/envoyer_offre/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesSend) def envoyer_offre(id): """ Permet d'envoyer une offre à un utilisateur - - Arguments: - id: - l'id de l'offre """ offre = EntrepriseOffre.query.filter_by(id=id).first_or_404() form = EnvoiOffreForm() @@ -607,36 +778,34 @@ def envoyer_offre(id): .first() ) envoi_offre = EntrepriseEnvoiOffre( - sender_id=current_user.id, receiver_id=responsable.id, offre_id=offre.id + sender_id=current_user.id, + receiver_id=responsable.id, + offre_id=offre.id, ) db.session.add(envoi_offre) db.session.commit() flash(f"L'offre a été envoyé à {responsable.get_nomplogin()}.") return redirect(url_for("entreprises.fiche_entreprise", id=offre.entreprise_id)) return render_template( - "entreprises/envoi_offre_form.html", title=("Envoyer une offre"), form=form + "entreprises/envoi_offre_form.html", + title="Envoyer une offre", + form=form, ) @bp.route("/etudiants") +@permission_required(Permission.RelationsEntreprisesChange) def json_etudiants(): """ Permet de récuperer un JSON avec tous les étudiants - - Arguments: - term: - le terme utilisé pour le filtre de l'autosuggest - - Retourne: - le JSON de tous les étudiants (nom, prenom, formation actuelle?) correspondant au terme """ if request.args.get("term") == None: abort(400) term = request.args.get("term").strip() etudiants = Identite.query.filter(Identite.nom.ilike(f"%{term}%")).all() list = [] - content = {} for etudiant in etudiants: + content = {} value = f"{sco_etud.format_nom(etudiant.nom)} {sco_etud.format_prenom(etudiant.prenom)}" if etudiant.inscription_courante() is not None: content = { @@ -647,21 +816,14 @@ def json_etudiants(): else: content = {"id": f"{etudiant.id}", "value": value} list.append(content) - content = {} return jsonify(results=list) @bp.route("/responsables") +@permission_required(Permission.RelationsEntreprisesChange) def json_responsables(): """ Permet de récuperer un JSON avec tous les étudiants - - Arguments: - term: - le terme utilisé pour le filtre de l'autosuggest - - Retourne: - le JSON de tous les utilisateurs (nom, prenom, login) correspondant au terme """ if request.args.get("term") == None: abort(400) @@ -670,29 +832,30 @@ def json_responsables(): User.nom.ilike(f"%{term}%"), User.nom.is_not(None), User.prenom.is_not(None) ).all() list = [] - content = {} + for responsable in responsables: + content = {} value = f"{responsable.get_nomplogin()}" content = {"id": f"{responsable.id}", "value": value, "info": ""} list.append(content) - content = {} return jsonify(results=list) @bp.route("/export_entreprises") +@permission_required(Permission.RelationsEntreprisesExport) def export_entreprises(): """ Permet d'exporter la liste des entreprises sous format excel (.xlsx) """ - entreprises = Entreprise.query.all() + entreprises = Entreprise.query.filter_by(visible=True).all() if entreprises: - keys = ["siret", "nom", "adresse", "ville", "codepostal", "pays"] + keys = ["siret", "nom", "adresse", "ville", "code_postal", "pays"] titles = keys[:] L = [ [entreprise.to_dict().get(k, "") for k in keys] for entreprise in entreprises ] - title = "entreprises" + title = "Entreprises" xlsx = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title) filename = title return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE) @@ -700,30 +863,117 @@ def export_entreprises(): abort(404) +@bp.route("/get_import_entreprises_file_sample") +@permission_required(Permission.RelationsEntreprisesExport) +def get_import_entreprises_file_sample(): + """ + Permet de récupérer un fichier pour pouvoir importer des entreprises + """ + keys = [ + "siret", + "nom_entreprise", + "adresse", + "ville", + "codepostal", + "pays", + ] + titles = keys[:] + title = "ImportEntreprises" + xlsx = sco_excel.excel_simple_table(titles=titles, sheet_name="Entreprises") + filename = title + return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE) + + +@bp.route("/import_entreprises", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesExport) +def import_entreprises(): + """ + Permet d'importer des entreprises a l'aide d'un fichier excel (.xlsx) + """ + form = ImportForm() + if form.validate_on_submit(): + file = form.fichier.data + file_path = os.path.join( + Config.SCODOC_VAR_DIR, "tmp", secure_filename(file.filename) + ) + file.save(file_path) + data = sco_excel.excel_file_to_list(file_path) + os.remove(file_path) + entreprises_import = [] + siret_list = [] + ligne = 0 + titles = ["siret", "nom_entreprise", "adresse", "ville", "codepostal", "pays"] + if data[1][0] != titles: + flash("Veuillez utilisez la feuille excel à remplir") + return render_template( + "entreprises/import_entreprises.html", + title="Importation entreprises", + form=form, + ) + for entreprise_data in data[1][1:]: + ligne += 1 + if ( + are.verif_entreprise_data(entreprise_data) + and entreprise_data[0] not in siret_list + ): + siret_list.append(entreprise_data[0]) + entreprise = Entreprise( + siret=entreprise_data[0], + nom=entreprise_data[1], + adresse=entreprise_data[2], + ville=entreprise_data[3], + codepostal=entreprise_data[4], + pays=entreprise_data[5], + visible=True, + ) + entreprises_import.append(entreprise) + + else: + flash(f"Erreur lors de l'importation (ligne {ligne})") + return render_template( + "entreprises/import_entreprises.html", + title="Importation entreprises", + form=form, + ) + + if len(entreprises_import) > 0: + for entreprise in entreprises_import: + db.session.add(entreprise) + log = EntrepriseLog( + authenticated_user=current_user.user_name, + text=f"Importation de {len(entreprises_import)} entreprise(s)", + ) + db.session.add(log) + db.session.commit() + flash(f"Importation réussie de {len(entreprises_import)} entreprise(s)") + return render_template( + "entreprises/import_entreprises.html", + title="Importation entreprises", + form=form, + entreprises_import=entreprises_import, + ) + else: + flash('Feuille "Entreprises" vide') + + return render_template( + "entreprises/import_entreprises.html", + title="Importation entreprises", + form=form, + ) + + @bp.route("/export_contacts") +@permission_required(Permission.RelationsEntreprisesExport) def export_contacts(): """ Permet d'exporter la liste des contacts sous format excel (.xlsx) """ - contacts = EntrepriseContact.query.all() - if contacts: - keys = ["nom", "prenom", "telephone", "mail", "poste", "service"] - titles = keys[:] - L = [[contact.to_dict().get(k, "") for k in keys] for contact in contacts] - title = "contacts" - xlsx = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title) - filename = title - return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE) - else: - abort(404) - - -@bp.route("/export_contacts_bis") -def export_contacts_bis(): - """ - Permet d'exporter la liste des contacts avec leur entreprise sous format excel (.xlsx) - """ - contacts = EntrepriseContact.query.all() + contacts = ( + db.session.query(EntrepriseContact) + .join(Entreprise, EntrepriseContact.entreprise_id == Entreprise.id) + .filter_by(visible=True) + .all() + ) if contacts: keys = [ "nom", @@ -732,18 +982,11 @@ def export_contacts_bis(): "mail", "poste", "service", - "nom_entreprise", - "siret", - "adresse_entreprise", - "ville", - "codepostal", - "pays", + "entreprise_id", ] titles = keys[:] - L = [ - [contact.to_dict_export().get(k, "") for k in keys] for contact in contacts - ] - title = "contacts" + L = [[contact.to_dict().get(k, "") for k in keys] for contact in contacts] + title = "Contacts" xlsx = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title) filename = title return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE) @@ -751,22 +994,120 @@ def export_contacts_bis(): abort(404) +@bp.route("/get_import_contacts_file_sample") +@permission_required(Permission.RelationsEntreprisesExport) +def get_import_contacts_file_sample(): + """ + Permet de récupérer un fichier pour pouvoir importer des contacts + """ + keys = [ + "nom", + "prenom", + "telephone", + "mail", + "poste", + "service", + "entreprise_id", + ] + titles = keys[:] + title = "ImportContacts" + xlsx = sco_excel.excel_simple_table(titles=titles, sheet_name="Contacts") + filename = title + return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE) + + +@bp.route("/import_contacts", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesExport) +def import_contacts(): + """ + Permet d'importer des contacts a l'aide d'un fichier excel (.xlsx) + """ + form = ImportForm() + if form.validate_on_submit(): + file = form.fichier.data + file_path = os.path.join( + Config.SCODOC_VAR_DIR, "tmp", secure_filename(file.filename) + ) + file.save(file_path) + data = sco_excel.excel_file_to_list(file_path) + os.remove(file_path) + contacts_import = [] + contact_list = [] + ligne = 0 + titles = [ + "nom", + "prenom", + "telephone", + "mail", + "poste", + "service", + "entreprise_id", + ] + if data[1][0] != titles: + flash("Veuillez utilisez la feuille excel à remplir") + return render_template( + "entreprises/import_contacts.html", + title="Importation contacts", + form=form, + ) + for contact_data in data[1][1:]: + ligne += 1 + if ( + are.verif_contact_data(contact_data) + and (contact_data[0], contact_data[1], contact_data[6]) + not in contact_list + ): + contact_list.append((contact_data[0], contact_data[1], contact_data[6])) + contact = EntrepriseContact( + nom=contact_data[0], + prenom=contact_data[1], + telephone=contact_data[2], + mail=contact_data[3], + poste=contact_data[4], + service=contact_data[5], + entreprise_id=contact_data[6], + ) + contacts_import.append(contact) + else: + flash(f"Erreur lors de l'importation (ligne {ligne})") + return render_template( + "entreprises/import_contacts.html", + title="Importation contacts", + form=form, + ) + + if len(contacts_import) > 0: + for contact in contacts_import: + db.session.add(contact) + log = EntrepriseLog( + authenticated_user=current_user.user_name, + text=f"Importation de {len(contacts_import)} contact(s)", + ) + db.session.add(log) + db.session.commit() + flash(f"Importation réussie de {len(contacts_import)} contact(s)") + return render_template( + "entreprises/import_contacts.html", + title="Importation Contacts", + form=form, + contacts_import=contacts_import, + ) + else: + flash('Feuille "Contacts" vide') + return render_template( + "entreprises/import_contacts.html", + title="Importation contacts", + form=form, + ) + + @bp.route( "/get_offre_file////" ) +@permission_required(Permission.RelationsEntreprisesView) def get_offre_file(entreprise_id, offre_id, filedir, filename): """ Permet de télécharger un fichier d'une offre - - Arguments: - entreprise_id: - l'id de l'entreprise - offre_id: - l'id de l'offre - filedir: - le répertoire du fichier - filename: - le nom du fichier """ if os.path.isfile( os.path.join( @@ -794,13 +1135,10 @@ def get_offre_file(entreprise_id, offre_id, filedir, filename): @bp.route("/add_offre_file/", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def add_offre_file(offre_id): """ Permet d'ajouter un fichier à une offre - - Arguments: - offre_id: - l'id de l'offre """ offre = EntrepriseOffre.query.filter_by(id=offre_id).first_or_404() form = AjoutFichierForm() @@ -820,20 +1158,17 @@ def add_offre_file(offre_id): flash("Le fichier a été ajouté a l'offre.") return redirect(url_for("entreprises.fiche_entreprise", id=offre.entreprise_id)) return render_template( - "entreprises/form.html", title=("Ajout fichier à une offre"), form=form + "entreprises/form.html", + title="Ajout fichier à une offre", + form=form, ) @bp.route("/delete_offre_file//", methods=["GET", "POST"]) +@permission_required(Permission.RelationsEntreprisesChange) def delete_offre_file(offre_id, filedir): """ Permet de supprimer un fichier d'une offre - - Arguments: - offre_id: - l'id de l'offre - filedir: - le répertoire du fichier """ offre = EntrepriseOffre.query.filter_by(id=offre_id).first_or_404() form = SuppressionConfirmationForm() @@ -853,6 +1188,6 @@ def delete_offre_file(offre_id, filedir): ) return render_template( "entreprises/delete_confirmation.html", - title=("Suppression fichier d'une offre"), + title="Suppression fichier d'une offre", form=form, ) diff --git a/app/static/css/entreprises.css b/app/static/css/entreprises.css new file mode 100644 index 00000000..d2dc5cf5 --- /dev/null +++ b/app/static/css/entreprises.css @@ -0,0 +1,88 @@ +.nav-entreprise>ul { + padding-left: 0; +} + +.nav-entreprise li{ + list-style: none; + display: inline-block; + padding: 10px; +} + +.nav-entreprise>ul>li>a { + text-decoration: none; + color: black; + padding: 15px; + +} + +.nav-entreprise>ul>li>a:hover { + color: red; +} + +.boutons .btn { + margin-top: 5px; + margin-bottom: 5px; +} + +.btn-inverse { + color: #ffffff; + text-shadow: 0 -1px 0 rgb(0 0 0 / 25%); + background-color: #363636; +} + +.btn-inverse:hover { + color: #ffffff; + background-color: #222222; + *background-color: #151515; +} + +.fiche-entreprise .btn { + margin-top: 5px; + margin-bottom: 5px; +} + +.offre .btn { + margin-top: 5px; + margin-bottom: 5px; +} + +.parent-btn { + margin-bottom: -5px; +} + +.entreprise, .contact, .offre { + border: solid 2px; + border-radius: 10px; + padding: 10px; + margin-bottom: 10px; +} + +.contacts-et-offres { + display: flex; + justify-content: space-between; +} + +.contacts-et-offres > div { + flex: 1 0 0; +} + +.contacts-et-offres > div:nth-child(2) { + margin-left: 20px; +} + +#depts { + list-style: none; + padding-left: 10px; +} + +.offre-depts { + display: inline-block; + border: black solid 2px; + border-radius: 5px; + padding: 1px; +} + +.offre-recue { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index adf70171..176e4b99 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -4,6 +4,7 @@ {% block styles %} {{super()}} + {% endblock %} {% block title %} @@ -35,6 +36,9 @@ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}">Dept. {{ g.scodoc_dept }} {% endif %} + {% if not current_user.is_anonymous and current_user.has_permission(current_user.Permission.RelationsEntreprisesView, None) %} +
  • Entreprises
  • + {% endif %}