diff --git a/.gitignore b/.gitignore
index 87df45de6..b60367ec8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -173,3 +173,6 @@ Thumbs.db
.idea/
copy
+
+# Symlinks static ScoDoc
+app/static/links/[0-9]*.*[0-9]
diff --git a/app/api/departements.py b/app/api/departements.py
index fca5d6a82..853dc7120 100644
--- a/app/api/departements.py
+++ b/app/api/departements.py
@@ -153,4 +153,4 @@ def liste_semestres_courant(dept_ident: str):
FormSemestre.date_fin >= app.db.func.now(),
)
- return jsonify([d.to_dict() for d in formsemestres])
+ return jsonify([d.to_dict(convert_parcours=True) for d in formsemestres])
diff --git a/app/entreprises/__init__.py b/app/entreprises/__init__.py
index 7f100d51e..44fd128e7 100644
--- a/app/entreprises/__init__.py
+++ b/app/entreprises/__init__.py
@@ -1,6 +1,7 @@
"""entreprises.__init__
"""
+from datetime import datetime
from flask import Blueprint
from app.scodoc import sco_etud
from app.auth.models import User
@@ -47,4 +48,12 @@ def get_civilité(civ):
return "Madame"
+@bp.app_template_filter()
+def check_taxe_now(taxes):
+ for taxe in taxes:
+ if taxe.annee == int(datetime.now().strftime("%Y")):
+ return True
+ return False
+
+
from app.entreprises import routes
diff --git a/app/entreprises/app_relations_entreprises.py b/app/entreprises/app_relations_entreprises.py
index 95f1264cf..b5b14dab7 100644
--- a/app/entreprises/app_relations_entreprises.py
+++ b/app/entreprises/app_relations_entreprises.py
@@ -30,6 +30,7 @@ import re
import requests
import glob
+from flask import flash
from flask_login import current_user
from app.entreprises.models import (
@@ -38,14 +39,61 @@ from app.entreprises.models import (
EntrepriseOffre,
EntrepriseOffreDepartement,
EntreprisePreferences,
+ EntrepriseSite,
+ EntrepriseHistorique,
)
-
-from app import email
+from app import email, db
from app.scodoc import sco_preferences
+from app.scodoc import sco_excel
from app.models import Departement
from app.scodoc.sco_permissions import Permission
+ENTREPRISES_KEYS = [
+ "siret",
+ "nom_entreprise",
+ "adresse",
+ "ville",
+ "code_postal",
+ "pays",
+]
+SITES_KEYS = [
+ [
+ "siret_entreprise",
+ "id_site",
+ "nom_site",
+ "adresse",
+ "ville",
+ "code_postal",
+ "pays",
+ ],
+ [
+ "civilite",
+ "nom",
+ "prenom",
+ "telephone",
+ "mail",
+ "poste",
+ "service",
+ "origine",
+ "notes",
+ ],
+]
+CORRESPONDANTS_KEYS = [
+ "id",
+ "civilite",
+ "nom",
+ "prenom",
+ "telephone",
+ "mail",
+ "poste",
+ "service",
+ "origine",
+ "notes",
+ "nom_site",
+]
+
+
def get_depts():
"""
Retourne une liste contenant les l'id des départements des roles de l'utilisateur courant
@@ -58,7 +106,7 @@ def get_depts():
return depts
-def get_dept_id_by_acronym(acronym):
+def get_dept_id_by_acronym(acronym: str):
"""
Retourne l'id d'un departement a l'aide de son acronym
"""
@@ -68,7 +116,14 @@ def get_dept_id_by_acronym(acronym):
return None
-def check_offre_depts(depts, offre_depts):
+def get_dept_acronym_by_id(id: int):
+ dept = Departement.query.filter_by(id=id).first()
+ if dept is not None:
+ return dept.acronym
+ return None
+
+
+def check_offre_depts(depts: list, offre_depts: list):
"""
Retourne vrai si l'utilisateur a le droit de visibilité sur l'offre
"""
@@ -107,7 +162,7 @@ def get_offre_files_and_depts(offre: EntrepriseOffre, depts: list):
return None
-def send_email_notifications_entreprise(subject, entreprise: Entreprise):
+def send_email_notifications_entreprise(subject: str, entreprise: Entreprise):
txt = [
"Une entreprise est en attente de validation",
"Entreprise:",
@@ -128,69 +183,168 @@ def send_email_notifications_entreprise(subject, entreprise: Entreprise):
return txt
-def verif_correspondant_data(correspondant_data):
+def get_excel_book_are(export: bool = False):
"""
- Verifie les données d'une ligne Excel (correspondant)
- correspondant_data[0]: civilite
- correspondant_data[1]: nom
- correspondant_data[2]: prenom
- correspondant_data[3]: telephone
- correspondant_data[4]: mail
- correspondant_data[5]: poste
- correspondant_data[6]: service
- correspondant_data[7]: origine
- correspondant_data[8]: notes
- correspondant_data[9]: entreprise_siret
+ Retourne un Excel avec les 3 feuilles "Entreprises", "Sites" et "Correspondants"
"""
- # champs obligatoires
- if (
- correspondant_data[0].strip() == ""
- or correspondant_data[1].strip() == ""
- or correspondant_data[2].strip() == ""
- or correspondant_data[9].strip() == ""
- ):
+ entreprises_titles = ENTREPRISES_KEYS[:]
+ sites_titles = SITES_KEYS[:]
+ correspondants_titles = CORRESPONDANTS_KEYS[:]
+ wb = sco_excel.ScoExcelBook()
+ ws1 = wb.create_sheet("Entreprises")
+ ws1.append_row(
+ [
+ ws1.make_cell(it, style)
+ for (it, style) in zip(
+ entreprises_titles,
+ [sco_excel.excel_make_style(bold=True)] * len(entreprises_titles),
+ )
+ ]
+ )
+ ws2 = wb.create_sheet("Sites")
+ ws2.append_row(
+ [
+ ws2.make_cell(it, style)
+ for (it, style) in zip(
+ sites_titles[0],
+ [sco_excel.excel_make_style(bold=True)] * len(sites_titles[0]),
+ )
+ ]
+ + [
+ ws2.make_cell(it, style)
+ for (it, style) in zip(
+ sites_titles[1],
+ [sco_excel.excel_make_style(bold=True, color=sco_excel.COLORS.RED)]
+ * len(sites_titles[1]),
+ )
+ ]
+ )
+ ws3 = wb.create_sheet("Correspondants")
+ ws3.append_row(
+ [
+ ws3.make_cell(it, style)
+ for (it, style) in zip(
+ correspondants_titles,
+ [sco_excel.excel_make_style(bold=True)] * len(correspondants_titles),
+ )
+ ]
+ )
+ if export:
+ entreprises = Entreprise.query.filter_by(visible=True).all()
+ sites = (
+ db.session.query(EntrepriseSite)
+ .join(Entreprise, EntrepriseSite.entreprise_id == Entreprise.id)
+ .filter_by(visible=True)
+ .all()
+ )
+ correspondants = (
+ db.session.query(EntrepriseCorrespondant)
+ .join(Entreprise, EntrepriseCorrespondant.entreprise_id == Entreprise.id)
+ .filter_by(visible=True)
+ .all()
+ )
+ entreprises_lines = [
+ [entreprise.to_dict().get(k, "") for k in ENTREPRISES_KEYS]
+ for entreprise in entreprises
+ ]
+ sites_lines = [
+ [site.to_dict().get(k, "") for k in SITES_KEYS[0]] for site in sites
+ ]
+ correspondants_lines = [
+ [correspondant.to_dict().get(k, "") for k in CORRESPONDANTS_KEYS]
+ for correspondant in correspondants
+ ]
+ for line in entreprises_lines:
+ cells = []
+ for it in line:
+ cells.append(ws1.make_cell(it))
+ ws1.append_row(cells)
+ for line in sites_lines:
+ cells = []
+ for it in line:
+ cells.append(ws2.make_cell(it))
+ ws2.append_row(cells)
+ for line in correspondants_lines:
+ cells = []
+ for it in line:
+ cells.append(ws3.make_cell(it))
+ ws3.append_row(cells)
+ return wb
+
+
+def check_entreprises_import(m):
+ """
+ Verifie la feuille Excel "Entreprises" de l'importation données
+ """
+ entreprises_import = []
+ siret_list = []
+ ligne = 1
+ if m[0] != ENTREPRISES_KEYS:
+ flash(
+ f'Veuillez utilisez la feuille excel à remplir (Feuille "Entreprises", ligne {ligne})'
+ )
return False
+ l = list_to_dict(m)
+ for entreprise_data in l:
+ ligne += 1
+ entreprise_data["siret"] = entreprise_data["siret"].replace(" ", "")
+ if (
+ check_entreprise_import(entreprise_data)
+ and entreprise_data["siret"] not in siret_list
+ ):
+ siret_list.append(entreprise_data["siret"])
+ entreprise = Entreprise.query.filter_by(
+ siret=entreprise_data["siret"], visible=True
+ ).first()
+ if entreprise is None:
+ entreprise_import = Entreprise(
+ siret=entreprise_data["siret"],
+ nom=entreprise_data["nom_entreprise"],
+ adresse=entreprise_data["adresse"],
+ ville=entreprise_data["ville"],
+ codepostal=entreprise_data["code_postal"],
+ pays=entreprise_data["pays"]
+ if entreprise_data["pays"]
+ else "FRANCE",
+ visible=True,
+ )
+ entreprises_import.append(entreprise_import)
+ else:
+ entreprise.nom = entreprise_data["nom_entreprise"]
+ entreprise.adresse = entreprise_data["adresse"]
+ entreprise.ville = entreprise_data["ville"]
+ entreprise.codepostal = entreprise_data["code_postal"]
+ entreprise.pays = (
+ entreprise_data["pays"] if entreprise_data["pays"] else "FRANCE"
+ )
+ else:
+ flash(
+ f'Erreur lors de l\'importation (Feuille "Entreprises", ligne {ligne})'
+ )
+ return False
- # civilite entre H ou F
- if correspondant_data[0].strip() not in ["H", "F"]:
- return False
+ if len(entreprises_import) > 0:
+ log = EntrepriseHistorique(
+ authenticated_user=current_user.user_name,
+ text=f"Importation de {len(entreprises_import)} entreprise(s)",
+ )
+ db.session.add(log)
- # entreprise_id existant
- entreprise = Entreprise.query.filter_by(siret=correspondant_data[9].strip()).first()
- if entreprise is None:
- return False
-
- # correspondant possède le meme nom et prénom dans la meme entreprise
- correspondant = EntrepriseCorrespondant.query.filter_by(
- nom=correspondant_data[1].strip(),
- prenom=correspondant_data[2].strip(),
- entreprise_id=entreprise.id,
- ).first()
- if correspondant is not None:
- return False
-
- if (
- correspondant_data[3].strip() == "" and correspondant_data[4].strip() == ""
- ): # 1 moyen de contact
- return False
-
- return True
+ return entreprises_import
-def verif_entreprise_data(entreprise_data):
+def check_entreprise_import(entreprise_data):
"""
Verifie les données d'une ligne Excel (entreprise)
"""
+ champs_obligatoires = ["siret", "nom_entreprise", "adresse", "ville", "code_postal"]
+ for key, value in entreprise_data.items(): # champs obligatoires
+ if key in champs_obligatoires and value == "":
+ return False
+
+ siret = entreprise_data["siret"]
+
if EntreprisePreferences.get_check_siret():
- for data in entreprise_data: # champs obligatoires
- if data.strip() == "":
- return False
- else:
- for data in entreprise_data[1:]: # champs obligatoires
- if data.strip() == "":
- return False
- if EntreprisePreferences.get_check_siret():
- siret = entreprise_data[0].strip().replace(" ", "") # vérification sur le siret
if re.match("^\d{14}$", siret) is None:
return False
try:
@@ -201,7 +355,210 @@ def verif_entreprise_data(entreprise_data):
return False
except requests.ConnectionError:
return False
- entreprise = Entreprise.query.filter_by(siret=siret).first()
- if entreprise is not None:
+ return True
+
+
+def check_sites_import(m):
+ """
+ Verifie la feuille Excel "Sites" de l'importation données
+ """
+ sites_import = []
+ correspondants_import = []
+ ligne = 1
+ if m[0] != sum(SITES_KEYS, []):
+ flash(
+ f'Veuillez utilisez la feuille excel à remplir (Feuille "Sites", ligne {ligne})'
+ )
+ return False, False
+ l = list_to_dict(m)
+ for site_data in l:
+ ligne += 1
+ site_data["siret_entreprise"] = site_data["siret_entreprise"].replace(" ", "")
+ if check_site_import(site_data):
+ entreprise = Entreprise.query.filter_by(
+ siret=site_data["siret_entreprise"], visible=True
+ ).first()
+ if site_data["id_site"] == "":
+ site_import = EntrepriseSite(
+ entreprise_id=entreprise.id,
+ nom=site_data["nom_site"],
+ adresse=site_data["adresse"],
+ codepostal=site_data["code_postal"],
+ ville=site_data["ville"],
+ pays=site_data["pays"],
+ )
+ if site_data["civilite"] == "":
+ sites_import.append(site_import)
+ else:
+ correspondant_import = EntrepriseCorrespondant(
+ entreprise_id=entreprise.id,
+ civilite=site_data["civilite"],
+ nom=site_data["nom"],
+ prenom=site_data["prenom"],
+ telephone=site_data["telephone"],
+ mail=site_data["mail"],
+ poste=site_data["poste"],
+ service=site_data["service"],
+ origine=site_data["origine"],
+ notes=site_data["notes"],
+ )
+ sites_import.append(site_import)
+ correspondants_import.append([site_import, correspondant_import])
+ else:
+ site = EntrepriseSite.query.filter_by(id=site_data["id_site"]).first()
+ site.nom = site_data["nom_site"]
+ site.adresse = site_data["adresse"]
+ site.codepostal = site_data["code_postal"]
+ site.ville = site_data["ville"]
+ site.pays = site_data["pays"]
+
+ if site_data["civilite"] != "":
+ correspondant_import = EntrepriseCorrespondant(
+ entreprise_id=entreprise.id,
+ site_id=site.id,
+ civilite=site_data["civilite"],
+ nom=site_data["nom"],
+ prenom=site_data["prenom"],
+ telephone=site_data["telephone"],
+ mail=site_data["mail"],
+ poste=site_data["poste"],
+ service=site_data["service"],
+ origine=site_data["origine"],
+ notes=site_data["notes"],
+ )
+ correspondants_import.append([None, correspondant_import])
+ else:
+ flash(f'Erreur lors de l\'importation (Feuille "Sites", ligne {ligne})')
+ return False, False
+
+ if len(sites_import) > 0:
+ log = EntrepriseHistorique(
+ authenticated_user=current_user.user_name,
+ text=f"Importation de {len(sites_import)} site(s)",
+ )
+ db.session.add(log)
+
+ if len(correspondants_import) > 0:
+ log = EntrepriseHistorique(
+ authenticated_user=current_user.user_name,
+ text=f"Importation de {len(correspondants_import)} correspondant(s)",
+ )
+ db.session.add(log)
+
+ return sites_import, correspondants_import
+
+
+def check_site_import(site_data):
+ """
+ Verifie les données d'une ligne Excel (sites)
+ """
+ champs_obligatoires = [
+ "siret_entreprise",
+ "nom_site",
+ "adresse",
+ "ville",
+ "code_postal",
+ "pays",
+ ]
+ for key, value in site_data.items(): # champs obligatoires
+ if key in champs_obligatoires and value == "":
+ return False
+
+ if site_data["civilite"] != "":
+ if check_correspondant_import(site_data) is False:
+ return False
+
+ entreprise = Entreprise.query.filter_by(
+ siret=site_data["siret_entreprise"], visible=True
+ ).first()
+ if entreprise is None:
+ return False
+
+ site = EntrepriseSite.query.filter_by(nom=site_data["nom_site"]).first()
+ if site_data["id_site"] == "" and site is not None:
+ return False
+
+ return True
+
+
+def check_correspondants_import(m):
+ ligne = 1
+ if m[0] != CORRESPONDANTS_KEYS:
+ flash(
+ f'Veuillez utilisez la feuille excel à remplir (Feuille "Correspondants", ligne {ligne})'
+ )
+ return False
+ l = list_to_dict(m)
+ for correspondant_data in l:
+ ligne += 1
+ if correspondant_data["id"] == "":
+ flash(
+ f'Erreur lors de l\'importation (Feuille "Correspondants", ligne {ligne})'
+ )
+ return False
+ correspondant = EntrepriseCorrespondant.query.filter_by(
+ id=correspondant_data["id"]
+ ).first()
+ if correspondant is not None and check_correspondant_import(correspondant_data):
+ correspondant.civilite = correspondant_data["civilite"]
+ correspondant.nom = correspondant_data["nom"]
+ correspondant.prenom = correspondant_data["prenom"]
+ correspondant.telephone = correspondant_data["telephone"]
+ correspondant.mail = correspondant_data["mail"]
+ correspondant.poste = correspondant_data["poste"]
+ correspondant.service = correspondant_data["service"]
+ correspondant.origine = correspondant_data["origine"]
+ correspondant.notes = correspondant_data["notes"]
+ else:
+ flash(
+ f'Erreur lors de l\'importation (Feuille "Correspondants", ligne {ligne})'
+ )
return False
return True
+
+
+def check_correspondant_import(correspondant_data):
+ """
+ Verifie les données d'une ligne Excel (correspondant)
+ """
+ champs_obligatoires = ["civilite", "nom", "prenom"]
+ for key, value in correspondant_data.items(): # champs obligatoires
+ if key in champs_obligatoires and value == "":
+ return False
+
+ # civilite entre H ou F
+ if correspondant_data["civilite"] not in ["H", "F"]:
+ return False
+
+ if (
+ correspondant_data["telephone"] == "" and correspondant_data["mail"] == ""
+ ): # 1 moyen de contact
+ return False
+
+ if "siret_entreprise" in correspondant_data:
+ # entreprise_id existant
+ entreprise = Entreprise.query.filter_by(
+ siret=correspondant_data["siret_entreprise"], visible=True
+ ).first()
+ if entreprise is None:
+ return False
+
+ # correspondant possède le meme nom et prénom dans la meme entreprise
+ correspondant = EntrepriseCorrespondant.query.filter_by(
+ nom=correspondant_data["nom"],
+ prenom=correspondant_data["prenom"],
+ entreprise_id=entreprise.id,
+ ).first()
+ if correspondant is not None:
+ return False
+
+ return True
+
+
+def list_to_dict(m):
+ l = []
+ for data in m[1:]:
+ new_dict = {title: value.strip() for title, value in zip(m[0], data)}
+ l.append(new_dict)
+
+ return l
diff --git a/app/entreprises/forms.py b/app/entreprises/forms.py
index a16190070..61d517b7c 100644
--- a/app/entreprises/forms.py
+++ b/app/entreprises/forms.py
@@ -26,6 +26,7 @@
import re
import requests
+from datetime import datetime
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed, FileRequired
@@ -33,6 +34,7 @@ from markupsafe import Markup
from sqlalchemy import text
from wtforms import (
StringField,
+ IntegerField,
SubmitField,
TextAreaField,
SelectField,
@@ -42,14 +44,23 @@ from wtforms import (
BooleanField,
FieldList,
FormField,
+ BooleanField,
+)
+from wtforms.validators import (
+ ValidationError,
+ DataRequired,
+ Email,
+ Optional,
+ NumberRange,
)
-from wtforms.validators import ValidationError, DataRequired, Email, Optional
from wtforms.widgets import ListWidget, CheckboxInput
from app.entreprises.models import (
Entreprise,
EntrepriseCorrespondant,
EntreprisePreferences,
+ EntrepriseSite,
+ EntrepriseTaxeApprentissage,
)
from app.models import Identite, Departement
from app.auth.models import User
@@ -69,11 +80,17 @@ def _build_string_field(label, required=True, render_kw=None):
return StringField(label, validators=[Optional()], render_kw=render_kw)
+class EntreprisesFilterForm(FlaskForm):
+ active = BooleanField("Toutes les entreprises")
+ association = BooleanField("Seulement les associations partenaires")
+
+
class EntrepriseCreationForm(FlaskForm):
siret = _build_string_field(
"SIRET (*)",
render_kw={"placeholder": "Numéro composé de 14 chiffres"},
)
+ association = BooleanField("Association")
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 (*)")
@@ -153,8 +170,8 @@ class EntrepriseCreationForm(FlaskForm):
class EntrepriseModificationForm(FlaskForm):
- hidden_entreprise_siret = HiddenField()
siret = StringField("SIRET (*)")
+ association = BooleanField("Association")
nom = _build_string_field("Nom de l'entreprise (*)")
adresse = _build_string_field("Adresse (*)")
codepostal = _build_string_field("Code postal (*)")
@@ -164,13 +181,11 @@ class EntrepriseModificationForm(FlaskForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.siret.render_kw = {
- "disabled": "",
- "value": self.hidden_entreprise_siret.data,
- }
+ self.siret.render_kw = {"disabled": ""}
class SiteCreationForm(FlaskForm):
+ hidden_entreprise_id = HiddenField()
nom = _build_string_field("Nom du site (*)")
adresse = _build_string_field("Adresse (*)")
codepostal = _build_string_field("Code postal (*)")
@@ -178,6 +193,49 @@ class SiteCreationForm(FlaskForm):
pays = _build_string_field("Pays", required=False)
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
+ def validate(self):
+ validate = True
+ if not FlaskForm.validate(self):
+ validate = False
+
+ site = EntrepriseSite.query.filter_by(
+ entreprise_id=self.hidden_entreprise_id.data, nom=self.nom.data
+ ).first()
+
+ if site is not None:
+ self.nom.errors.append("Ce site existe déjà (même nom)")
+ validate = False
+
+ return validate
+
+
+class SiteModificationForm(FlaskForm):
+ hidden_entreprise_id = HiddenField()
+ hidden_site_id = HiddenField()
+ nom = _build_string_field("Nom du site (*)")
+ adresse = _build_string_field("Adresse (*)")
+ codepostal = _build_string_field("Code postal (*)")
+ ville = _build_string_field("Ville (*)")
+ pays = _build_string_field("Pays", required=False)
+ submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
+
+ def validate(self):
+ validate = True
+ if not FlaskForm.validate(self):
+ validate = False
+
+ site = EntrepriseSite.query.filter(
+ EntrepriseSite.entreprise_id == self.hidden_entreprise_id.data,
+ EntrepriseSite.id != self.hidden_site_id.data,
+ EntrepriseSite.nom == self.nom.data,
+ ).first()
+
+ if site is not None:
+ self.nom.errors.append("Ce site existe déjà (même nom)")
+ validate = False
+
+ return validate
+
class MultiCheckboxField(SelectMultipleField):
widget = ListWidget(prefix_label=False)
@@ -214,7 +272,7 @@ class OffreCreationForm(FlaskForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.correspondant.choices = [
+ self.correspondant.choices = [("", "")] + [
(correspondant.id, f"{correspondant.nom} {correspondant.prenom}")
for correspondant in EntrepriseCorrespondant.query.filter_by(
entreprise_id=self.hidden_entreprise_id.data
@@ -260,7 +318,7 @@ class OffreModificationForm(FlaskForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.correspondant.choices = [
+ self.correspondant.choices = [("", "")] + [
(correspondant.id, f"{correspondant.nom} {correspondant.prenom}")
for correspondant in EntrepriseCorrespondant.query.filter_by(
entreprise_id=self.hidden_entreprise_id.data
@@ -290,8 +348,16 @@ class CorrespondantCreationForm(FlaskForm):
validators=[DataRequired(message=CHAMP_REQUIS)],
render_kw={"class": "form-control"},
)
- nom = _build_string_field("Nom (*)", render_kw={"class": "form-control"})
- prenom = _build_string_field("Prénom (*)", render_kw={"class": "form-control"})
+ nom = StringField(
+ "Nom (*)",
+ validators=[DataRequired('Le champ "Nom" est requis')],
+ render_kw={"class": "form-control"},
+ )
+ prenom = StringField(
+ "Prénom (*)",
+ validators=[DataRequired('Le champ "Prénom" est requis')],
+ render_kw={"class": "form-control"},
+ )
telephone = _build_string_field(
"Téléphone (*)", required=False, render_kw={"class": "form-control"}
)
@@ -321,7 +387,6 @@ class CorrespondantCreationForm(FlaskForm):
if not self.telephone.data and not self.mail.data:
msg = "Saisir un moyen de contact (mail ou téléphone)"
self.telephone.errors.append(msg)
- self.mail.errors.append(msg)
validate = False
return validate
@@ -558,9 +623,75 @@ class StageApprentissageModificationForm(FlaskForm):
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
+class TaxeApprentissageForm(FlaskForm):
+ hidden_entreprise_id = HiddenField()
+ annee = IntegerField(
+ "Année (*)",
+ validators=[
+ DataRequired(message=CHAMP_REQUIS),
+ NumberRange(
+ min=1900,
+ max=int(datetime.now().strftime("%Y")),
+ message=f"L'année doit être inférieure ou égale à {int(datetime.now().strftime('%Y'))}",
+ ),
+ ],
+ default=int(datetime.now().strftime("%Y")),
+ )
+ montant = IntegerField(
+ "Montant (*)",
+ validators=[
+ DataRequired(message=CHAMP_REQUIS),
+ NumberRange(
+ min=1,
+ message="Le montant doit être supérieur à 0",
+ ),
+ ],
+ default=1,
+ )
+ notes = TextAreaField("Notes")
+ submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
+
+ def validate(self):
+ validate = True
+ if not FlaskForm.validate(self):
+ validate = False
+
+ taxe = EntrepriseTaxeApprentissage.query.filter_by(
+ entreprise_id=self.hidden_entreprise_id.data, annee=self.annee.data
+ ).first()
+ if taxe is not None:
+ self.annee.errors.append(
+ "Une taxe d'apprentissage a déjà été versé pour cette année"
+ )
+ validate = False
+
+ return validate
+
+
+class TaxeApprentissageModificationForm(FlaskForm):
+ annee = IntegerField("Année (*)")
+ montant = IntegerField(
+ "Montant (*)",
+ validators=[
+ DataRequired(message=CHAMP_REQUIS),
+ NumberRange(
+ min=1,
+ message="Le montant doit être supérieur à 0",
+ ),
+ ],
+ default=1,
+ )
+ notes = TextAreaField("Notes")
+ submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.annee.render_kw = {"disabled": ""}
+
+
class EnvoiOffreForm(FlaskForm):
responsables = FieldList(
- _build_string_field(
+ StringField(
"Responsable (*)",
render_kw={
"placeholder": "Tapez le nom du responsable de formation",
@@ -573,6 +704,8 @@ class EnvoiOffreForm(FlaskForm):
def validate(self):
validate = True
+ list_select = True
+
if not FlaskForm.validate(self):
validate = False
@@ -588,8 +721,12 @@ class EnvoiOffreForm(FlaskForm):
.first()
)
if responsable is None:
- entry.errors.append("Champ incorrect (selectionnez dans la liste)")
- validate = False
+ validate, list_select = False, False
+
+ if list_select is False:
+ self.responsables.errors.append(
+ "Champ incorrect (selectionnez dans la liste)"
+ )
return validate
@@ -611,7 +748,11 @@ class SuppressionConfirmationForm(FlaskForm):
class DesactivationConfirmationForm(FlaskForm):
notes_active = TextAreaField("Notes sur la désactivation", validators=[Optional()])
- submit = SubmitField("Désactiver", render_kw=SUBMIT_MARGE)
+ submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
+
+
+class ActivationConfirmationForm(FlaskForm):
+ submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
class ValidationConfirmationForm(FlaskForm):
@@ -633,6 +774,7 @@ class PreferencesForm(FlaskForm):
mail_entreprise = StringField(
"Mail notifications",
validators=[Optional(), Email(message="Adresse e-mail invalide")],
+ description="utilisé pour envoi mail notification application relations entreprises",
)
check_siret = BooleanField("Vérification SIRET")
submit = SubmitField("Valider", render_kw=SUBMIT_MARGE)
diff --git a/app/entreprises/models.py b/app/entreprises/models.py
index 4a318d24a..4edbe458b 100644
--- a/app/entreprises/models.py
+++ b/app/entreprises/models.py
@@ -10,6 +10,7 @@ class Entreprise(db.Model):
codepostal = db.Column(db.Text)
ville = db.Column(db.Text)
pays = db.Column(db.Text)
+ association = db.Column(db.Boolean, default=False)
visible = db.Column(db.Boolean, default=False)
active = db.Column(db.Boolean, default=True)
notes_active = db.Column(db.Text)
@@ -36,6 +37,10 @@ class Entreprise(db.Model):
"code_postal": self.codepostal,
"ville": self.ville,
"pays": self.pays,
+ "association": self.association,
+ "visible": self.visible,
+ "active": self.active,
+ "notes_active": self.notes_active,
}
@@ -58,6 +63,18 @@ class EntrepriseSite(db.Model):
cascade="all, delete-orphan",
)
+ def to_dict(self):
+ entreprise = Entreprise.query.get_or_404(self.entreprise_id)
+ return {
+ "siret_entreprise": entreprise.siret,
+ "id_site": self.id,
+ "nom_site": self.nom,
+ "adresse": self.adresse,
+ "code_postal": self.codepostal,
+ "ville": self.ville,
+ "pays": self.pays,
+ }
+
class EntrepriseCorrespondant(db.Model):
__tablename__ = "are_correspondants"
@@ -77,8 +94,9 @@ class EntrepriseCorrespondant(db.Model):
notes = db.Column(db.Text)
def to_dict(self):
- entreprise = Entreprise.query.filter_by(id=self.entreprise_id).first()
+ site = EntrepriseSite.query.get_or_404(self.site_id)
return {
+ "id": self.id,
"civilite": self.civilite,
"nom": self.nom,
"prenom": self.prenom,
@@ -88,7 +106,7 @@ class EntrepriseCorrespondant(db.Model):
"service": self.service,
"origine": self.origine,
"notes": self.notes,
- "entreprise_siret": entreprise.siret,
+ "nom_site": site.nom,
}
@@ -131,12 +149,14 @@ class EntrepriseOffre(db.Model):
}
-class EntrepriseLog(db.Model):
- __tablename__ = "are_logs"
+class EntrepriseHistorique(db.Model):
+ __tablename__ = "are_historique"
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)
- object = db.Column(db.Integer)
+ entreprise_id = db.Column(db.Integer)
+ object = db.Column(db.Text)
+ object_id = db.Column(db.Integer)
text = db.Column(db.Text)
@@ -155,6 +175,17 @@ class EntrepriseStageApprentissage(db.Model):
notes = db.Column(db.Text)
+class EntrepriseTaxeApprentissage(db.Model):
+ __tablename__ = "are_taxe_apprentissage"
+ id = db.Column(db.Integer, primary_key=True)
+ entreprise_id = db.Column(
+ db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
+ )
+ annee = db.Column(db.Integer)
+ montant = db.Column(db.Integer)
+ notes = db.Column(db.Text)
+
+
class EntrepriseEnvoiOffre(db.Model):
__tablename__ = "are_envoi_offre"
id = db.Column(db.Integer, primary_key=True)
diff --git a/app/entreprises/routes.py b/app/entreprises/routes.py
index b815b2297..6a831d866 100644
--- a/app/entreprises/routes.py
+++ b/app/entreprises/routes.py
@@ -12,11 +12,14 @@ from app.decorators import permission_required
from app.entreprises import LOGS_LEN
from app.entreprises.forms import (
+ ActivationConfirmationForm,
CorrespondantsCreationForm,
DesactivationConfirmationForm,
EntrepriseCreationForm,
EntrepriseModificationForm,
+ EntreprisesFilterForm,
SiteCreationForm,
+ SiteModificationForm,
SuppressionConfirmationForm,
OffreCreationForm,
OffreModificationForm,
@@ -27,6 +30,8 @@ from app.entreprises.forms import (
StageApprentissageModificationForm,
EnvoiOffreForm,
AjoutFichierForm,
+ TaxeApprentissageForm,
+ TaxeApprentissageModificationForm,
ValidationConfirmationForm,
ImportForm,
PreferencesForm,
@@ -36,13 +41,14 @@ from app.entreprises.models import (
Entreprise,
EntrepriseOffre,
EntrepriseCorrespondant,
- EntrepriseLog,
+ EntrepriseHistorique,
EntrepriseContact,
EntrepriseSite,
EntrepriseStageApprentissage,
EntrepriseEnvoiOffre,
EntrepriseOffreDepartement,
EntreprisePreferences,
+ EntrepriseTaxeApprentissage,
)
from app.entreprises import app_relations_entreprises as are
from app.models import Identite
@@ -52,18 +58,40 @@ from app.scodoc import sco_etud, sco_excel
import app.scodoc.sco_utils as scu
from app import db
-from sqlalchemy import text
+from sqlalchemy import text, sql
from werkzeug.utils import secure_filename
-@bp.route("/", methods=["GET"])
+@bp.route("/", methods=["GET", "POST"])
@permission_required(Permission.RelationsEntreprisesView)
def index():
"""
Permet d'afficher une page avec la liste des entreprises (visible) et une liste des dernières opérations
"""
entreprises = Entreprise.query.filter_by(visible=True, active=True)
- logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all()
+ logs = (
+ EntrepriseHistorique.query.order_by(EntrepriseHistorique.date.desc())
+ .limit(LOGS_LEN)
+ .all()
+ )
+ if current_user.has_permission(Permission.RelationsEntreprisesChange, None):
+ form = EntreprisesFilterForm()
+ checked = [False, False]
+ if request.method == "POST":
+ checked[0] = form.active.data
+ checked[1] = form.association.data
+ if checked[0]:
+ entreprises = Entreprise.query.filter_by(visible=True)
+ if checked[1]:
+ entreprises = Entreprise.query.filter_by(association=True)
+ return render_template(
+ "entreprises/entreprises.html",
+ title="Entreprises",
+ entreprises=entreprises,
+ logs=logs,
+ form=form,
+ checked=checked,
+ )
return render_template(
"entreprises/entreprises.html",
title="Entreprises",
@@ -79,9 +107,9 @@ 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
- )
+ logs = EntrepriseHistorique.query.order_by(
+ EntrepriseHistorique.date.desc()
+ ).paginate(page=page, per_page=20)
return render_template(
"entreprises/logs.html",
title="Logs",
@@ -110,11 +138,17 @@ def correspondants():
Permet d'afficher une page avec la liste des correspondants des entreprises visibles et une liste des dernières opérations
"""
correspondants = (
- db.session.query(EntrepriseCorrespondant, Entreprise)
- .join(Entreprise, EntrepriseCorrespondant.entreprise_id == Entreprise.id)
+ db.session.query(EntrepriseCorrespondant, EntrepriseSite)
+ .join(EntrepriseSite, EntrepriseCorrespondant.site_id == EntrepriseSite.id)
+ .join(Entreprise, EntrepriseSite.entreprise_id == Entreprise.id)
.filter_by(visible=True, active=True)
+ .all()
+ )
+ logs = (
+ EntrepriseHistorique.query.order_by(EntrepriseHistorique.date.desc())
+ .limit(LOGS_LEN)
+ .all()
)
- logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all()
return render_template(
"entreprises/correspondants.html",
title="Correspondants",
@@ -132,9 +166,9 @@ def fiche_entreprise(id):
La fiche entreprise comporte les informations de l'entreprise, les correspondants de l'entreprise et
les offres de l'entreprise.
"""
- entreprise = Entreprise.query.filter_by(
- id=id, visible=True, active=True
- ).first_or_404(description=f"fiche entreprise {id} inconnue")
+ entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
+ description=f"fiche entreprise {id} inconnue"
+ )
offres_with_files = []
depts = are.get_depts()
for offre in entreprise.offres:
@@ -150,8 +184,8 @@ def fiche_entreprise(id):
offres_with_files.append(offre_with_files)
sites = entreprise.sites[:]
logs = (
- EntrepriseLog.query.order_by(EntrepriseLog.date.desc())
- .filter_by(object=id)
+ EntrepriseHistorique.query.order_by(EntrepriseHistorique.date.desc())
+ .filter(EntrepriseHistorique.entreprise_id == id)
.limit(LOGS_LEN)
.all()
)
@@ -162,6 +196,11 @@ def fiche_entreprise(id):
.join(Identite, Identite.id == EntrepriseStageApprentissage.etudid)
.all()
)
+ taxes = (
+ EntrepriseTaxeApprentissage.query.filter_by(entreprise_id=id)
+ .order_by(EntrepriseTaxeApprentissage.annee.desc())
+ .all()
+ )
return render_template(
"entreprises/fiche_entreprise.html",
title="Fiche entreprise",
@@ -170,6 +209,7 @@ def fiche_entreprise(id):
offres=offres_with_files,
logs=logs,
stages_apprentissages=stages_apprentissages,
+ taxes=taxes,
)
@@ -184,8 +224,8 @@ def logs_entreprise(id):
description=f"logs fiche entreprise {id} inconnu"
)
logs = (
- EntrepriseLog.query.order_by(EntrepriseLog.date.desc())
- .filter_by(object=id)
+ EntrepriseHistorique.query.order_by(EntrepriseHistorique.date.desc())
+ .filter(EntrepriseHistorique.entreprise_id == entreprise.id)
.paginate(page=page, per_page=20)
)
return render_template(
@@ -205,12 +245,12 @@ def fiche_entreprise_validation(id):
entreprise = Entreprise.query.filter_by(id=id, visible=False).first_or_404(
description=f"fiche entreprise (validation) {id} inconnue"
)
- correspondants = entreprise.correspondants
+ sites = entreprise.sites[:]
return render_template(
"entreprises/fiche_entreprise_validation.html",
title="Validation fiche entreprise",
entreprise=entreprise,
- correspondants=correspondants,
+ sites=sites,
)
@@ -290,6 +330,7 @@ def add_entreprise():
entreprise = Entreprise(
nom=form.nom_entreprise.data.strip(),
siret=form.siret.data.strip(),
+ association=form.association.data,
adresse=form.adresse.data.strip(),
codepostal=form.codepostal.data.strip(),
ville=form.ville.data.strip(),
@@ -327,9 +368,10 @@ def add_entreprise():
if current_user.has_permission(Permission.RelationsEntreprisesValidate, None):
entreprise.visible = True
nom_entreprise = f"{entreprise.nom}"
- log = EntrepriseLog(
+ log = EntrepriseHistorique(
authenticated_user=current_user.user_name,
text=f"{nom_entreprise} - Création de la fiche entreprise ({entreprise.nom})",
+ entreprise_id=entreprise.id,
)
db.session.add(log)
db.session.commit()
@@ -360,51 +402,52 @@ def edit_entreprise(id):
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
description=f"entreprise {id} inconnue"
)
- form = EntrepriseModificationForm(hidden_entreprise_siret=entreprise.siret)
+ form = EntrepriseModificationForm(siret=entreprise.siret)
if form.validate_on_submit():
nom_entreprise = f"{form.nom.data.strip()}"
if entreprise.nom != form.nom.data.strip():
- log = EntrepriseLog(
+ log = EntrepriseHistorique(
authenticated_user=current_user.user_name,
- object=entreprise.id,
+ entreprise_id=entreprise.id,
text=f"{nom_entreprise} - Modification du nom (ancien nom: {entreprise.nom})",
)
entreprise.nom = form.nom.data.strip()
db.session.add(log)
if entreprise.adresse != form.adresse.data.strip():
- log = EntrepriseLog(
+ log = EntrepriseHistorique(
authenticated_user=current_user.user_name,
- object=entreprise.id,
+ entreprise_id=entreprise.id,
text=f"{nom_entreprise} - Modification de l'adresse (ancienne adresse: {entreprise.adresse})",
)
entreprise.adresse = form.adresse.data.strip()
db.session.add(log)
if entreprise.codepostal != form.codepostal.data.strip():
- log = EntrepriseLog(
+ log = EntrepriseHistorique(
authenticated_user=current_user.user_name,
- object=entreprise.id,
+ entreprise_id=entreprise.id,
text=f"{nom_entreprise} - Modification du code postal (ancien code postal: {entreprise.codepostal})",
)
entreprise.codepostal = form.codepostal.data.strip()
db.session.add(log)
if entreprise.ville != form.ville.data.strip():
- log = EntrepriseLog(
+ log = EntrepriseHistorique(
authenticated_user=current_user.user_name,
- object=entreprise.id,
+ entreprise_id=entreprise.id,
text=f"{nom_entreprise} - Modification de la ville (ancienne ville: {entreprise.ville})",
)
entreprise.ville = form.ville.data.strip()
db.session.add(log)
if entreprise.pays != form.pays.data.strip() or not form.pays.data.strip():
- log = EntrepriseLog(
+ log = EntrepriseHistorique(
authenticated_user=current_user.user_name,
- object=entreprise.id,
+ entreprise_id=entreprise.id,
text=f"{nom_entreprise} - Modification du pays (ancien pays: {entreprise.pays})",
)
entreprise.pays = (
form.pays.data.strip() if form.pays.data.strip() else "FRANCE"
)
db.session.add(log)
+ entreprise.association = form.association.data
db.session.commit()
flash("L'entreprise a été modifié.")
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
@@ -415,6 +458,7 @@ def edit_entreprise(id):
form.codepostal.data = entreprise.codepostal
form.ville.data = entreprise.ville
form.pays.data = entreprise.pays
+ form.association.data = entreprise.association
return render_template(
"entreprises/form_modification_entreprise.html",
title="Modification entreprise",
@@ -428,21 +472,128 @@ def fiche_entreprise_desactiver(id):
"""
Permet de désactiver une entreprise
"""
- entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
- description=f"entreprise {id} inconnue"
- )
+ entreprise = Entreprise.query.filter_by(
+ id=id, visible=True, active=True
+ ).first_or_404(description=f"entreprise {id} inconnue")
form = DesactivationConfirmationForm()
if form.validate_on_submit():
entreprise.notes_active = form.notes_active.data.strip()
entreprise.active = False
db.session.commit()
flash("L'entreprise a été désactivé.")
- return redirect(url_for("entreprises.index"))
+ return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
return render_template(
"entreprises/confirmation_form.html",
title="Désactiver entreprise",
form=form,
- info_message="Cliquez sur le bouton Désactiver pour confirmer la désactivation",
+ info_message="Cliquez sur le bouton Modifier pour confirmer la désactivation",
+ )
+
+
+@bp.route("/fiche_entreprise/activer/