Aller au contenu

Module Missions

Objectif

Le module Missions gere une place de marche interne Solutravo : une societe publie une mission, d'autres societes la consultent, candidatent, echangent et peuvent etre acceptees. Le module couvre aussi la revue administrateur, les filtres, les pieces jointes, la galerie, les notifications email et les regles d'acces selon le plan d'abonnement.

Ce module ne doit pas etre traite comme une simple liste CRUD. Il porte des regles produit sensibles : visibilite publique, validation admin, quotas de candidature, fermeture automatique apres acceptation et isolation entre societes.

Architecture fonctionnelle

flowchart LR
  Router[Router.php] --> Controller[MissionController]
  Controller --> Model[MissionModel]
  Model --> AdminNotif[MissionAdminNotificationService]
  Model --> AudienceNotif[MissionAudienceNotificationService]
  Model --> Messaging[MessagingService / MessagingModel]
  Model --> MailQueue[Email queue / Brevo templates]
  Model --> DB[(MySQL)]
  Controller --> Views[missions.php + partials]
  Views --> JS[public/assets/js/missions/*]

Perimetre documente

Cette page documente les fichiers listes dans les points d'entree ci-dessous. Avant une correction, ouvrir ces fichiers dans la branche de travail concernee et verifier l'historique git du module si le comportement observe ne correspond pas a la documentation.

Points d'entree

Backend

  • app/controllers/MissionController.php : endpoints HTTP, normalisation des filtres, reponses JSON, chargement de la page.
  • app/models/MissionModel.php : regles metier, requetes, permissions, transitions de statut, fichiers, candidatures, notifications.
  • app/services/MissionAdminNotificationService.php : alertes administrateur quand une mission attend validation.
  • app/services/MissionAudienceNotificationService.php : briefs email envoyes aux societes eligibles quand une mission publiee correspond a leur activite.
  • app/services/MessagingService.php et app/models/MessagingModel.php : creation de conversation liee a une candidature.

Front

  • app/views/missions.php
  • app/views/partials/missions/filter-form.php
  • app/views/partials/missions/list-section.php
  • app/views/partials/missions/mission-card.php
  • app/views/partials/missions/tab-navigation.php
  • app/views/partials/modals/mission-application-modal.php
  • app/views/partials/modals/mission-applications-drawer.php
  • app/views/partials/modals/mission-details-modal.php
  • app/views/partials/modals/mission-edit-modal.php
  • app/views/partials/modals/mission-publish-modal.php
  • app/views/partials/modals/mission-candidates-modal.php
  • app/views/partials/modals/mission-gallery-viewer-modal.php
  • app/views/partials/modals/mission-share-email-modal.php
  • app/views/partials/modals/mission-application-details-modal.php
  • app/views/partials/modals/mission-confirm-action-modal.php
  • app/views/partials/modals/mission-personal-list-modal.php
  • app/views/partials/modals/mission-review-list-modal.php
  • public/assets/js/missions/app.js
  • public/assets/js/missions/actions.js
  • public/assets/js/missions/core.js
  • public/assets/js/missions/pickers.js
  • public/assets/js/missions/renderers.js
  • public/assets/css/missions.css

Routes

Les routes sont declarees dans app/core/Router.php.

Pages

  • missions : redirige vers missions/all en conservant la query string.
  • missions/all : catalogue global des missions visibles.
  • missions/personal : missions personnelles de la societe courante.
  • missions/review : file de revue administrateur, uniquement pour les administrateurs.

Actions mission

  • mission/add : creation.
  • mission/update : modification.
  • mission/delete : suppression definitive, reservee aux administrateurs selon le modele.
  • mission/reopen : reouverture d'une mission cloturee.
  • mission/archive : archivage.
  • mission/unarchive : desarchivage.
  • mission/approve : validation admin et publication.
  • mission/view : enregistrement d'une vue.
  • mission/share-email : partage par email.
  • mission/activities/search : recherche d'activites.
  • mission/companies/search : recherche de societes pour filtre/admin.

Actions candidature

  • mission/apply : candidature a une mission.
  • mission/application/update : modification de sa propre candidature.
  • mission/application/delete : suppression de sa propre candidature.
  • mission/application/status : changement de statut par le proprietaire de mission.
  • mission/applications : liste des candidatures recues.
  • mission/application : detail d'une candidature recue.

Permissions, features et plans

Le module combine permissions applicatives et features de plan.

Permissions appelees par le controleur ou le modele :

  • missions:view_all
  • missions:create
  • missions:edit
  • missions:apply_to_mission
  • missions:view_applications

Features de plan appelees par le modele :

  • FEATURE_CONSULTER_MISSION
  • FEATURE_POSTULER_MISSION

Plans utilises par les regles de candidature :

  • GRATUIT
  • TPE
  • PME
  • ENTREPRISE

Regle importante : le plan gratuit est limite a une candidature par mois pour les non-admins. Les plans superieurs sont ordonnes par rang pour verifier si une societe peut candidater a une mission exigeant un plan minimum.

Statuts mission

Statuts techniques :

  • draft : brouillon.
  • pending_review : en attente de validation admin.
  • published : visible dans le catalogue.
  • closed : cloturee, notamment apres acceptation d'une candidature.
  • archived : archivee.

Transitions principales :

  • creation par admin : publication directe en published.
  • creation par non-admin : passage en pending_review.
  • modification par admin avec demande de publication : published.
  • modification par non-admin d'une mission ouverte : retour en pending_review.
  • validation admin : published avec published_at, reviewed_by, reviewed_at.
  • acceptation d'une candidature : mission en closed.
  • desarchivage admin : published.
  • desarchivage non-admin : pending_review.

La logique de visibilite depend du scope :

  • all : missions publiees et cloturees pour les utilisateurs standards ; l'admin voit aussi des etats internes.
  • personal : toutes les missions de la societe courante, y compris brouillon, attente, publiee, cloturee, archivee.
  • review : missions pending_review, reserve admin.

Methodes de transition

Les transitions sont centralisees dans MissionModel :

  • resolveMissionStatusContextOnCreate : admin vers published, non-admin vers pending_review.
  • resolveMissionStatusContextOnUpdate : admin peut publier ; non-admin renvoie en revue sauf mission deja closed.
  • resolveMissionStatusContextOnUnarchive : admin republie, non-admin renvoie en revue.
  • approve : publication admin depuis pending_review.
  • archive / unarchive / reopen : transitions explicites appelees par le controleur.

Garde-fou important : un changement annonceur sur une mission ouverte retire la mission du flux public. C'est une regle produit de moderation, pas un detail d'interface.

Donnees

Tables directement manipulees par le module :

  • missions : mission principale.
  • mission_activities : relation mission / activites.
  • mission_departements : relation mission / departements.
  • mission_files : images et pieces jointes.
  • mission_applications : candidatures.
  • mission_views : vues journalieres par membre/societe.
  • mission_brief_email_recipients : reservation/idempotence des briefs email.
  • societes, membres, plans, activites, departements : tables referencees.

Scripts SQL actuellement presents :

  • app/bd/missions-departements.sql : cree mission_departements, rend city nullable, retire postal_code, ajoute is_nationwide.
  • app/bd/missions-nationwide.sql : ajoute is_nationwide.
  • app/bd/missions-notifications.sql : cree mission_brief_email_recipients.
  • app/bd/missions-original-societe.sql : ajoute original_societe_id et backfill depuis societe_id.

Point de reprise important : la fiche historique mentionnait app/bd/missions.sql, mais ce fichier n'est pas present dans le checkout actuel. Avant d'installer le module sur une nouvelle base, retrouver la migration initiale ou reconstruire explicitement le schema de base (missions, mission_activities, mission_files, mission_applications, mission_views) depuis la base de reference.

Etat verifie des migrations

Recherche locale effectuee dans app/bd :

  • aucune migration initiale CREATE TABLE missions trouvee ;
  • aucune migration initiale CREATE TABLE mission_applications trouvee ;
  • aucune migration initiale CREATE TABLE mission_files trouvee ;
  • aucune migration initiale CREATE TABLE mission_views trouvee ;
  • aucune migration initiale CREATE TABLE mission_activities trouvee ;
  • migration trouvee pour mission_departements ;
  • migration trouvee pour mission_brief_email_recipients.

Consequence : le code actuel depend de tables qui existent probablement deja dans les bases de travail, mais le repository ne documente pas totalement une installation neuve. C'est un risque majeur de reprise.

Colonnes attendues par missions

Le modele lit ou ecrit notamment :

  • id
  • societe_id : proprietaire courant de la mission.
  • original_societe_id : societe d'origine, ajoutee par missions-original-societe.sql.
  • created_by : membre createur.
  • title
  • description
  • city
  • is_nationwide
  • budget_amount
  • is_budget_on_quote
  • budget_scope
  • is_budget_negotiable
  • execution_deadline
  • minimum_candidate_plan_id
  • is_urgent
  • status
  • published_at
  • reviewed_by
  • reviewed_at
  • closed_at
  • created_at
  • updated_at

Ces colonnes doivent etre confirmees dans la base de reference avant tout deploiement. La migration initiale manquante doit etre reconstituee a partir de la base et du code, pas devinee.

Tables de liaison et d'activite

mission_activities

  • Relation entre une mission et une activite.
  • Utilisee pour les filtres, la recherche d'activites et les briefs audience.
  • Le modele supprime puis reinsere les activites lors d'une synchronisation.

mission_departements

  • Relation entre une mission et un departement.
  • Cle primaire composite mission_id, departement_id.
  • Suppression en cascade quand la mission est supprimee.
  • Si is_nationwide est vrai, le modele supprime les departements et n'en reinsere pas.

mission_files

  • Stocke file_type, file_path, original_name, mime_type, file_size, sort_order.
  • Les chemins physiques sont reconstruits depuis public/ + file_path.
  • Les suppressions partielles passent par removed_file_ids.

mission_views

  • Enregistre mission_id, viewer_societe_id, viewer_membre_id, viewed_at.
  • Le modele evite de compter plusieurs vues le meme jour pour un meme membre.

mission_applications

  • Stocke la candidature : mission, societe candidate, membre candidat, message, statut.
  • Le modele utilise au moins mission_id, candidate_societe_id, candidate_membre_id, message, status, created_at.
  • Les statuts geres par le code sont new, viewed, accepted, rejected.

mission_brief_email_recipients

  • Table d'idempotence des briefs audience.
  • Cle unique mission_id, societe_id.
  • Colonnes : email, template_brevo_id, email_queue_id, status, skip_reason, created_at.

Localisation et audience

La localisation repose sur :

  • city facultatif ;
  • mission_departements pour les departements cibles ;
  • is_nationwide pour les missions "toute la France".

Quand is_nationwide est actif, les departements ne doivent pas limiter la visibilite. Quand il est inactif, les departements deviennent structurants pour les filtres, les emails de brief et la recherche.

Budget et delais

Scopes budgetaires :

  • supply_and_installation : fourniture + pose.
  • installation_only : pose uniquement.

Delais techniques :

  • under_10_days
  • between_10_days_and_1_month
  • between_1_and_3_months
  • over_3_months

Regle importante : si is_budget_on_quote est actif, le montant fixe, le scope et la negociabilite sont neutralises. Si la mission n'est pas "sur devis", le montant et le scope deviennent obligatoires.

Normalisation budget

normalizeMissionBudgetContext applique exactement cette logique :

  • si is_budget_on_quote est vrai, budget_amount devient null, is_budget_on_quote vaut 1, budget_scope devient null et is_budget_negotiable vaut 0 ;
  • si is_budget_on_quote est faux et que le montant est absent, le modele renvoie une erreur car le forfait est obligatoire sauf attente d'un devis ;
  • si le montant existe mais que le scope est invalide ou absent, le modele renvoie une erreur demandant de preciser fourniture + pose ou pose uniquement.

Ne pas valider uniquement cote JS : le modele porte deja la regle et doit rester la source de verite.

Fichiers et galerie

Les uploads mission utilisent :

  • module d'upload : missions ;
  • taille maximum : 5 242 880 octets, soit 5 Mio ;
  • stockage logique : uploads/missions/... ;
  • type image pour les mimes image/*, sinon attachment.

Les fichiers sont references en base dans mission_files. La suppression definitive d'une mission recupere les chemins puis supprime les fichiers physiques. Les suppressions partielles de fichiers passent par removed_file_ids.

Avant de modifier ce flux, tester :

  • upload multiple ;
  • retrait d'un fichier existant ;
  • suppression definitive de mission ;
  • affichage galerie ;
  • piece jointe non image ;
  • fichier trop lourd.

Cycle de vie des fichiers

syncMissionUploads fait deux choses :

  • supprimer les fichiers demandes via removed_file_ids ;
  • ajouter les nouveaux fichiers via storeMissionUploads.

storeMissionUploads attend les fichiers sous la cle files. Une integration front qui envoie une autre cle peut donner l'impression que l'upload fonctionne cote formulaire mais ne rien enregistrer en base.

uploadSingleMissionFile classe les fichiers en :

  • image si le mime commence par image/ ;
  • attachment sinon.

Le chemin stocke en base commence par uploads/missions/. Le chemin disque de suppression est reconstruit avec dirname(__DIR__, 2) . '/public/'.

Flux : creation et publication

Creation :

  1. Le controleur lit les champs societe_id, title, description, city, is_nationwide, department_ids, budget, delai, plan minimum, urgence, statut et activites.
  2. Le modele valide les features, permissions, budget, departements, activites et societe proprietaire.
  3. Les administrateurs peuvent choisir la societe proprietaire.
  4. Les non-admins publient pour leur societe active.
  5. Le statut final depend du role : admin publie directement, non-admin passe en revue.
  6. Les activites, departements et fichiers sont synchronises.
  7. Si la mission passe en attente de validation, une notification admin est tentee hors transaction critique.

Piege a eviter : ne pas rendre une mission non revue visible dans all. Une modification par annonceur sur une mission ouverte doit la retirer du flux public jusqu'a validation.

Champs lus par le controleur

MissionController::add et MissionController::update collectent :

  • societe_id
  • title
  • description
  • city
  • is_nationwide
  • department_ids
  • budget_amount
  • is_budget_on_quote
  • budget_scope
  • is_budget_negotiable
  • execution_deadline
  • minimum_candidate_plan_id
  • is_urgent
  • status
  • activities
  • removed_file_ids en edition.

Une evolution de formulaire doit donc etre raccordee a ces noms exacts ou au mapping du controleur.

Societe proprietaire

La resolution de societe proprietaire est volontairement differente selon le role :

  • admin : peut choisir societe_id, avec validation que la societe existe ;
  • non-admin : force la societe active ;
  • edition par non-admin : conserve le proprietaire courant.

original_societe_id conserve l'origine historique. Ne pas le modifier pour transferer une mission : utiliser societe_id.

Flux : revue administrateur

Une mission en pending_review est visible dans le scope review. L'admin peut la valider via mission/approve.

MissionAdminNotificationService gere :

  • notification immediate de mission en attente ;
  • rappel hebdomadaire des missions en attente ;
  • limitation de l'aperçu email aux cinq dernieres missions ;
  • destinataires : membres actifs de type admin, non collaborateurs, avec email valide.

La notification ne doit jamais bloquer la sauvegarde d'une mission. Le modele loggue les echecs et laisse la mission comme source de verite.

Notifications admin

MissionAdminNotificationService :

  • cherche les membres type = admin ;
  • exige statut = actif ;
  • exclut COALESCE(is_collaborator, 0) = 0 ;
  • dedoublonne les emails ;
  • envoie au premier admin en destinataire principal et les autres en copie ;
  • utilise un lien de revue /missions/all?missions_modal=review.

Le service n'utilise pas la table mission_brief_email_recipients, reservee aux briefs audience.

Flux : consultation, filtres et vues

Les filtres supportes cote controleur incluent :

  • recherche texte ;
  • dates de publication ;
  • urgence ;
  • missions personnelles ;
  • activites ;
  • departements ;
  • societes, pour admin uniquement.

registerMissionView enregistre une vue dans mission_views si le membre n'a pas deja vu la mission le meme jour. Cela evite de gonfler artificiellement les compteurs.

Scopes et filtres

Le controleur accepte seulement :

  • all
  • personal
  • review, pour admin.

Filtres normalises :

  • search
  • date_publication_debut
  • date_publication_fin
  • is_urgent
  • mine_only
  • activity_ids
  • department_ids
  • societe_ids, admin uniquement.

Le controleur force actuellement l'appel principal a getPaginatedMissions(..., 'all') pour la page, puis charge les listes modales personal et review a part. Si un futur developpeur modifie les onglets pour charger chaque scope directement, il doit verifier ce comportement et les compteurs.

Flux : candidature

Le flux candidature passe par mission/apply.

Regles a respecter :

  • permission missions:apply_to_mission obligatoire ;
  • feature FEATURE_POSTULER_MISSION obligatoire ;
  • la societe active doit etre valide ;
  • impossible de candidater a sa propre mission ;
  • impossible de candidater deux fois a la meme mission ;
  • impossible de candidater si le plan courant est inferieur au plan minimum de la mission ;
  • le plan gratuit non-admin est limite a une candidature par mois ;
  • une candidature cree une conversation mission, mais l'echec de messagerie ne doit pas annuler la candidature.

Statuts candidature :

  • new
  • viewed
  • accepted
  • rejected

L'acceptation est transactionnelle : le statut de la candidature passe a accepted, la mission est fermee, puis une notification d'acceptation est planifiee. Le modele protege le cas ou une autre candidature serait deja acceptee.

Politique de candidature

getApplicationPolicyContext calcule :

  • plan courant ;
  • si le plan est gratuit ;
  • limite mensuelle ;
  • nombre utilise ce mois ;
  • reste disponible ;
  • feature d'upgrade FEATURE_POSTULER_MISSION.

Le quota gratuit est calcule sur mission_applications avec candidate_societe_id = activeSocieteId et created_at >= premier jour du mois courant. Le code ne filtre pas explicitement les candidatures supprimees dans ce compteur ; verifier le comportement attendu avant de changer la suppression de candidature.

Acceptation et concurrence

updateApplicationStatus ouvre une transaction, relit le contexte candidature avec verrou FOR UPDATE, puis appelle getApplicationProcessingGuard.

Garde-fous :

  • impossible de traiter manuellement accepted ou rejected si la mission est deja closed ;
  • accepter une candidature deja acceptee est idempotent ;
  • accepter une autre candidature est refuse si une candidature acceptee existe deja ;
  • closeMissionAfterAcceptedApplication met missions.status = 'closed' et closed_at = NOW().

La notification d'acceptation est envoyee apres commit. Si elle echoue, la candidature acceptee et la mission cloturee restent la verite.

Flux : gestion des candidatures recues

Le proprietaire d'une mission, ou un admin, peut lister et traiter les candidatures. L'acces passe par missions:view_applications et par userCanManageMission.

Regles sensibles :

  • une societe ne doit jamais gerer les candidatures d'une mission qui ne lui appartient pas ;
  • une mission closed ne doit plus permettre acceptation/refus manuel ;
  • une seule candidature acceptee par mission ;
  • les conversations liees aux candidatures doivent rester accessibles aux participants autorises seulement.

Messagerie

Apres creation de candidature, createMissionApplicationConversation tente MessagingService::createForMissionApplication.

Le commentaire du code est important : la candidature reste la source de verite et la messagerie ne doit pas bloquer le depot. En cas d'echec, le modele loggue l'erreur. Une reprise doit donc prevoir un outil de diagnostic ou de recreation de conversation si une candidature existe sans conversation.

app/bd/messaging.sql contient des vues/requetes liees a mission_applications. Le module Missions et la Messagerie sont donc couples par la structure de candidature.

Flux : partage email

mission/share-email partage une mission par email.

Protections existantes :

  • permission missions:view_all ;
  • email destinataire obligatoire ;
  • rate limit session : 5 partages maximum par fenetre de 15 minutes ;
  • template Brevo source mission_share_email.

Les URLs absolues sont construites via APP_URL, puis APP_BASE_URL, puis l'hote HTTP courant. Ne pas hardcoder de domaine dans ce module.

Rate limit partage

allowMissionShareEmailRequest stocke les timestamps en session sous mission_share_email_timestamps.

  • fenetre : 900 secondes ;
  • maximum : 5 tentatives ;
  • le nettoyage garde seulement les timestamps encore dans la fenetre.

Ce rate limit est par session PHP, pas global. Il limite les abus simples mais ne remplace pas une protection serveur globale si le partage devient critique.

Flux : briefs audience

MissionAudienceNotificationService envoie des briefs aux societes eligibles quand une mission publiee correspond a leurs activites.

Parametres importants :

  • source email : mission_brief_matched_activity ;
  • variable d'activation : MISSION_BRIEF_EMAILS_ENABLED ;
  • batch : 50 destinataires ;
  • delai entre batches : 300 secondes ;
  • limite quotidienne : 3 briefs par societe ;
  • table d'idempotence : mission_brief_email_recipients.

La table d'idempotence empeche de renvoyer le meme brief a la meme societe pour une meme mission. Les statuts possibles dans cette table sont queued, skipped et failed.

Selection et idempotence audience

MissionAudienceNotificationService est volontairement opt-in :

  • si MISSION_BRIEF_EMAILS_ENABLED est absent ou invalide, aucun envoi ;
  • reserveRecipient fait un INSERT IGNORE dans mission_brief_email_recipients ;
  • si la reservation existe deja, le destinataire est ignore ;
  • si la limite quotidienne est atteinte, la ligne est reservee avec status = skipped et skip_reason = daily_limit ;
  • si la mise en file email echoue, la ligne passe en failed.

La limite quotidienne compte les lignes queued creees depuis UTC_DATE(). Elle ne compte pas les failed ni les skipped.

Integrations internes

  • Messagerie : conversation creee pour une candidature via MessagingService::createForMissionApplication.
  • Emails : MailerService, file email et templates Brevo.
  • Plans/features : getCurrentPlanName, hasFeatureAccess, constantes feature.
  • Permissions : PermissionMiddleware.
  • Societes et membres : ownership, candidature, destinataires.
  • Activites et departements : matching, filtres, audience.

Risques et pieges

  • Ne pas confondre societe_id et original_societe_id : societe_id represente le proprietaire courant, original_societe_id conserve l'origine historique.
  • Ne pas exposer les missions en attente dans le flux public.
  • Ne pas permettre a une societe de candidater a sa propre mission.
  • Ne pas autoriser plusieurs candidatures acceptees pour une meme mission.
  • Ne pas casser la limite de candidature du plan gratuit.
  • Ne pas bloquer la creation/modification d'une mission si l'email admin ou audience echoue.
  • Ne pas supprimer physiquement les fichiers sans nettoyer mission_files, et inversement.
  • Ne pas reconstruire le schema depuis les seules migrations partielles : la migration initiale manque dans le checkout actuel.
  • Ne pas considerer le rate limit email comme global : il est base session.
  • Ne pas rendre bloquants les emails ou la messagerie si le code les traite comme secondaires.
  • Ne pas modifier les transitions sans verifier les statuts visibles par scope.
  • Ne pas oublier que les fichiers physiques peuvent rester sur disque si une suppression base echoue ou inversement.

Tests recommandes avant livraison

  • Creation mission admin : publication directe.
  • Creation mission non-admin : pending_review.
  • Modification non-admin d'une mission publiee : retour en revue.
  • Validation admin : publication, dates et reviewer renseignes.
  • Filtres par activite, departement, urgence, dates et societe admin.
  • Mission nationale avec is_nationwide.
  • Candidature acceptee : mission fermee.
  • Deux candidatures concurrentes : une seule acceptee.
  • Candidature plan gratuit : quota mensuel applique.
  • Plan minimum requis : blocage si plan insuffisant.
  • Candidature a sa propre mission : refusee.
  • Upload image et piece jointe.
  • Suppression definitive admin avec fichiers associes.
  • Partage email : rate limit session.
  • Brief audience : idempotence par mission/societe.

Commandes utiles

Il n'y a pas encore de test automatise dedie au module Missions dans le checkout actuel. Pour une reprise serieuse, ajouter au minimum des tests PHP de modele sur :

  • transitions de statut ;
  • candidature et fermeture automatique ;
  • quota plan gratuit ;
  • filtrage multi-societe ;
  • idempotence email audience.

Checklist de reprise

  • Lire MissionController.php et MissionModel.php ensemble.
  • Verifier la presence du schema initial en base avant de lancer les migrations partielles.
  • Tester avec un admin, une societe annonceuse et une societe candidate.
  • Tester avec au moins deux societes pour detecter les fuites de donnees.
  • Verifier les constantes de feature et les permissions en base.
  • Verifier les templates Brevo attendus pour mission_application_created, mission_application_accepted, mission_share_email, mission_brief_matched_activity.
  • Verifier que MISSION_BRIEF_EMAILS_ENABLED est volontairement active seulement dans les environnements ou l'envoi est souhaite.

Questions ouvertes

  • Ou se trouve la migration initiale du module Missions pour une installation neuve ?
  • Le statut draft est-il encore cree depuis l'interface ou seulement conserve pour compatibilite ?
  • Une mission archivee doit-elle rester visible aux candidats ayant deja postule ?
  • Le quota gratuit doit-il compter les candidatures supprimees ou seulement les candidatures actives ?
  • La fermeture automatique apres acceptation doit-elle notifier les autres candidats non retenus ?