Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b28d726
Kickoff Hackathon
skelz0r Jun 30, 2026
fd5435a
Introduire le moteur d'éligibilité des entités
skelz0r Jun 30, 2026
5a252a7
Merge pull request #1659 from etalab/hackathon/entity-validation-boot…
Un3x Jun 30, 2026
d86d212
Câbler l'éligibilité API Entreprise et factoriser les règles
skelz0r Jun 30, 2026
6f3dc35
Merge pull request #1660 from etalab/hackathon/entity-validation-api-…
skelz0r Jun 30, 2026
7ec3303
Exposer activite_principale et categorie_juridique sur Organization
Un3x Jun 30, 2026
abeb109
Faire consommer activite_principale à la règle APIEntreprise
Un3x Jun 30, 2026
732fca4
Faire passer décorateur et job CRM par les accesseurs nommés
Un3x Jun 30, 2026
46f680b
Merge pull request #1661 from etalab/feature/api-6999-comment-identif…
Un3x Jun 30, 2026
d15c160
Exposer la typologie d'entité sur Organization pour le moteur d'éligi…
Un3x Jun 30, 2026
2194473
Trancher l'éligibilité aide financière selon la typologie d'entité
Un3x Jun 30, 2026
f52b277
Documenter la typologie d'entité et la règle aide financière dans le …
Un3x Jun 30, 2026
f424699
Merge pull request #1662 from etalab/feature/api-7005-bootstrap-moteu…
Un3x Jun 30, 2026
a425bd8
Ajouter une démarche Aide financière navigable
Un3x Jun 30, 2026
e348de1
Afficher le verdict d'éligibilité sous forme de badge instructeur
Un3x Jun 30, 2026
a514434
Exposer le verdict d'éligibilité sur l'écran d'instruction
Un3x Jun 30, 2026
0ddab88
Semer des demandes Aide financière couvrant les trois verdicts
Un3x Jun 30, 2026
1d1c945
Documenter le câblage de bout en bout du moteur d'éligibilité
Un3x Jun 30, 2026
9a528e7
Raccourcir les labels de verdict en « Valide » / « Invalide »
Un3x Jun 30, 2026
e4fd1f9
Déplacer le badge d'éligibilité dans la carte organisation sous le co…
Un3x Jun 30, 2026
39722e7
Expliquer la règle ayant produit le verdict d'éligibilité
Un3x Jun 30, 2026
a935a05
Merge pull request #1663 from etalab/feature/api-7005-aide-financiere…
Un3x Jun 30, 2026
b9fec14
Permettre d'activer l'auto-instruction par démarche
Un3x Jun 30, 2026
ca7ff91
Instruire automatiquement selon le verdict d'éligibilité
Un3x Jun 30, 2026
8c56a38
Déclencher l'auto-instruction à la soumission
Un3x Jun 30, 2026
7103293
Documenter l'auto-instruction selon le verdict d'éligibilité
Un3x Jun 30, 2026
1db879a
Merge pull request #1664 from etalab/feature/api-7005-auto-instructio…
Un3x Jun 30, 2026
79a9ef9
Ignorer les clés i18n dynamiques du badge d'éligibilité instructeur
skelz0r Jul 1, 2026
fddc164
Introduire le composant d'introduction d'éligibilité côté demandeur
skelz0r Jul 1, 2026
415b1ee
Câbler le verdict d'éligibilité sur l'introduction de formulaire (API…
skelz0r Jul 1, 2026
e48938b
Documenter le câblage UI demandeur et la convention i18n du moteur
skelz0r Jul 1, 2026
0fd1a7b
Griser le CTA de démarrage en inéligible plutôt qu'un bouton de contact
skelz0r Jul 1, 2026
38eeb98
Résoudre l'éligibilité par cas d'usage pour cibler un seul formulaire
Un3x Jul 1, 2026
119069e
Piloter l'auto-instruction par convention plutôt qu'un flag
Un3x Jul 1, 2026
c9c1efb
Retirer la démarche Aide financière, remplacée par le cas d'usage API…
Un3x Jul 1, 2026
506bd79
Merge pull request #1665 from etalab/feature/api-7005-cibler-eligibil…
skelz0r Jul 1, 2026
068a486
Rendre les seeds tolérants à l'auto-instruction des démarches à règle
Un3x Jul 1, 2026
f8bd4eb
Annoncer l’auto-instruction par un bandeau au-dessus du bloc organisa…
Isalafont Jul 1, 2026
b84a894
Ajouter un script de stats du moteur d'éligibilité sur données réelles
skelz0r Jul 1, 2026
c2de3f5
Ajouter une vue HTML d'exploration des stats d'éligibilité
skelz0r Jul 1, 2026
c647de5
Adapter le script de stats à la résolution de règle par cas d'usage
skelz0r Jul 1, 2026
0fbbb9c
Câbler les règles d'éligibilité API Particulier par cas d'usage
Un3x Jul 1, 2026
57d1cdb
Ajoute la démarche DDMariage au formulaire HubEE DILA
Isalafont Jun 29, 2026
d73997e
Déclarer les événements auto_approve et auto_reject
skelz0r Jul 1, 2026
e22b45b
Historiser l'auto-instruction via auto_approve / auto_reject
skelz0r Jul 1, 2026
86526d1
Afficher les événements d'auto-instruction dans l'historique
skelz0r Jul 1, 2026
52aa87c
Documenter les événements distincts de l'auto-instruction
skelz0r Jul 1, 2026
11a018d
Fusionner verdict et auto-instruction en une bannière d’éligibilité u…
Isalafont Jul 1, 2026
626f17c
Fonder la bannière d’éligibilité sur les événements d’auto-instruction
Isalafont Jul 1, 2026
227b8ee
added api entreprise marchés publics rules
Un3x Jul 1, 2026
e487bc0
Élargir l'éligibilité CCAS aux communes et au bloc communal complet
skelz0r Jul 1, 2026
ae35823
Ajouter l'ID DataPass avec lien dans le tableau d'exploration
skelz0r Jul 1, 2026
8fca4a6
Ajouter Les Lilas (21930045600015) comme seconde organisation du user…
skelz0r Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/assets/stylesheets/components/eligibility_notice.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.eligibility-notice--success {
color: var(--text-default-success);
background-color: var(--background-contrast-success);
}

.eligibility-notice--success .fr-notice__title {
color: var(--text-default-success);
}
69 changes: 69 additions & 0 deletions app/assets/stylesheets/components/entity_eligibility_intro.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* Cas favorables : encadré coloré selon le verdict, icône + texte. */
.entity-eligibility-intro {
display: flex;
gap: 0.75rem;
padding: 1rem 1.25rem;
border: 1px solid currentColor;
border-radius: 8px;
}

.entity-eligibility-intro span[class*="fr-icon"]::before {
--icon-size: 1.25rem;
}

.entity-eligibility-intro--auto {
color: var(--text-default-success);
}

.entity-eligibility-intro--info {
color: var(--text-default-info);
}

.entity-eligibility-intro--warning {
color: var(--text-default-warning);
}

/* Cas inéligible : prise en charge forte de la zone + repli fournisseur. */
.entity-eligibility-intro__block {
border: 2px solid var(--text-default-error);
border-radius: 8px;
overflow: hidden;
}

.entity-eligibility-intro__head {
display: flex;
gap: 1.25rem;
align-items: flex-start;
padding: 2rem;
background: var(--background-contrast-error);
}

.entity-eligibility-intro__head .fr-icon-error-warning-line::before {
--icon-size: 2.5rem;
color: var(--text-default-error);
}

.entity-eligibility-intro__title {
margin: 0 0 0.5rem;
color: var(--text-default-error);
}

.entity-eligibility-intro__body {
padding: 1.5rem 2rem 2rem;
background: var(--background-default-grey);
}

.entity-eligibility-intro__contact {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
padding: 1rem 1.25rem;
border-radius: 6px;
background: var(--background-alt-grey);
}

.entity-eligibility-intro__contact p {
margin: 0;
}
3 changes: 2 additions & 1 deletion app/bridges/hubee_dila_bridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ class HubEEDilaBridge < HubEEBaseBridge
depot_dossier_pacs: 'depotDossierPACS',
recensement_citoyen: 'recensementCitoyen',
hebergement_tourisme: 'HebergementTourisme',
je_change_de_coordonnees: 'JeChangeDeCoordonnees'
je_change_de_coordonnees: 'JeChangeDeCoordonnees',
depot_dossier_mariage: 'DDMariage'
}.freeze

def on_approve
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class HistoricalAuthorizationRequestEventComponent < ApplicationComponent
with_collection_parameter :authorization_request_event

REASON_EVENTS = %w[request_changes revoke refuse bulk_update cancel_reopening_from_instructor].freeze
REASON_EVENTS = %w[request_changes revoke refuse auto_reject bulk_update cancel_reopening_from_instructor].freeze
MESSAGE_EVENTS = %w[applicant_message instructor_message].freeze
CHANGELOG_EVENTS = %w[initial_submit_with_changes_on_prefilled_data submit_with_changes legacy_submit_with_changes create_by_api update_by_api].freeze

Expand Down Expand Up @@ -81,7 +81,7 @@ def copied_from_authorization_request_id
end

def external_link?
%w[approve auto_generate].include?(event.name)
%w[approve auto_approve auto_generate].include?(event.name)
end

def formatted_created_at_date
Expand Down Expand Up @@ -163,7 +163,7 @@ def name_for_cancel_reopening

def event_kind
case event.name
when 'refuse', 'revoke', 'request_changes', 'applicant_message', 'instructor_message', 'bulk_update', 'approve', 'cancel_reopening'
when 'refuse', 'auto_reject', 'revoke', 'request_changes', 'applicant_message', 'instructor_message', 'bulk_update', 'approve', 'auto_approve', 'cancel_reopening'
:message
when 'admin_change'
:details
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ class HistoricalAuthorizationRequestEventIconComponent < IconComponent
def available_icons
{
'approve' => { icon: 'checkbox-line', color: 'success' },
'auto_approve' => { icon: 'checkbox-line', color: 'success' },
'request_changes' => { icon: 'pencil-line', color: 'warning' },
'refuse' => { icon: 'close-circle-line', color: 'error' },
'auto_reject' => { icon: 'close-circle-line', color: 'error' },
'revoke' => { icon: 'close-circle-line', color: 'error' },
'admin_change' => { icon: 'settings-5-line', color: 'info' },
}.freeze
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<% if ineligible? %>
<div class="entity-eligibility-intro__block fr-mb-6w">
<div class="entity-eligibility-intro__head">
<span class="fr-icon-error-warning-line" aria-hidden="true"></span>
<div>
<p class="fr-h5 entity-eligibility-intro__title"><%= title %></p>
<p class="fr-mb-0"><%= body %></p>
</div>
</div>
<div class="entity-eligibility-intro__body">
<p class="fr-text--bold fr-mb-1w"><%= t('entity_eligibility.base.contact.question') %></p>
<p class="fr-text--sm fr-mb-2w"><%= t('entity_eligibility.base.contact.help') %></p>
<div class="entity-eligibility-intro__contact">
<p>
<span class="fr-text--bold"><%= definition_name %></span><br>
<%= mail_to support_email, support_email, class: 'fr-link' %>
</p>
<%= mail_to support_email, t('entity_eligibility.base.contact.cta'),
class: 'fr-btn fr-icon-mail-line fr-btn--icon-left' %>
</div>
</div>
</div>
<% else %>
<div class="entity-eligibility-intro entity-eligibility-intro--<%= hint[:variant] %> fr-mb-6w">
<span class="<%= hint[:icon] %>" aria-hidden="true"></span>
<p class="fr-text--sm fr-mb-0"><%= body %></p>
</div>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
class Molecules::AuthorizationRequestForms::EntityEligibilityIntroComponent < ApplicationComponent
HINT_VARIANTS = {
eligible: { variant: 'auto', icon: 'fr-icon-success-line' },
likely_eligible: { variant: 'info', icon: 'fr-icon-thumb-up-line' },
likely_ineligible: { variant: 'warning', icon: 'fr-icon-alert-line' },
}.freeze

def initialize(verdict:, authorization_request_form:)
@verdict = verdict
@form = authorization_request_form
end

def render?
!@verdict.unknown?
end

delegate :ineligible?, to: :@verdict

private

attr_reader :verdict

def hint
HINT_VARIANTS.fetch(verdict.status)
end

def title
translate_eligibility("#{verdict.status}.title")
end

def body
translate_eligibility("#{verdict.status}.body_html", reason_clause:)
end

def reason_clause
return '' if verdict.reason.blank?

fragment = translate_eligibility("reasons.#{verdict.reason}")

fragment.present? ? "#{fragment}, " : ''
end

def support_email
@form.authorization_definition.support_email
end

def definition_name
@form.authorization_definition.name
end

def rule_key
@form.authorization_request_class.name.demodulize.underscore
end

def translate_eligibility(path, **)
t(
"entity_eligibility.#{rule_key}.#{path}",
default: [:"entity_eligibility.base.#{path}", nil],
definition: definition_name,
**,
)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div class="<%= classes.join(' ') %>">
<div class="fr-container">
<div class="fr-notice__body">
<p>
<span class="fr-notice__title"><%= title %></span>
<span class="fr-notice__desc"><%= description %></span>
</p>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class Molecules::Instruction::EntityEligibilityBannerComponent < ApplicationComponent
MODIFIERS = {
validated: 'eligibility-notice--success',
refused: 'fr-notice--alert',
likely_eligible: 'fr-notice--info',
likely_ineligible: 'fr-notice--warning',
}.freeze

def initialize(status:)
@status = status
end

private

def classes
['fr-notice', 'fr-notice--no-icon', 'fr-mb-3w', MODIFIERS.fetch(@status)]
end

def title
t(".#{@status}.title")
end

def description
t(".#{@status}.description")
end
end
10 changes: 10 additions & 0 deletions app/controllers/authorization_request_forms_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def new
authorize @authorization_request_form, :new?

if user_signed_in?
@entity_eligibility_verdict = entity_eligibility_verdict
render 'authorization_request_forms/new', layout: 'form_introduction'
else
@authorization_definition = @authorization_request_form.authorization_definition
Expand Down Expand Up @@ -86,6 +87,15 @@ def summary

private

def entity_eligibility_verdict
return unless current_organization

EntityEligibility::Engine.new(
organization: current_organization,
authorization_request_form: @authorization_request_form,
).verdict
end

def redirect_on_unauthorized_summary
flash.keep

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ class Instruction::AuthorizationRequestsController < Instruction::AbstractAuthor
def show
authorize [:instruction, @authorization_request]

verdict = EntityEligibility::Engine.from_request(@authorization_request).verdict
@entity_eligibility_banner = EntityEligibility::EligibilityBanner.new(@authorization_request, verdict)

render 'show', layout: 'instruction/authorization_request'
end

Expand Down
8 changes: 1 addition & 7 deletions app/decorators/organization_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,7 @@ def address_second_line
end

def code_naf
return unless insee_payload?

unite_legale_insee_payload['activitePrincipaleUniteLegale']
end

def unite_legale_insee_payload
etablissement_insee_payload['uniteLegale']
activite_principale
end

def etablissement_insee_payload
Expand Down
36 changes: 36 additions & 0 deletions app/interactors/auto_instruct_authorization_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class AutoInstructAuthorizationRequest < ApplicationInteractor
REFUSAL_REASON = 'Refus automatique : au regard de son identité juridique, ' \
'votre organisation n’apparaît pas éligible à cette démarche.'.freeze

def call
return unless authorization_request.submitted?

verdict = EntityEligibility::Engine.from_request(authorization_request).verdict

approve if verdict.eligible?
refuse if verdict.ineligible?
end

private

def approve
ApproveAuthorizationRequest.call(authorization_request:, user: system_user, event_name: :auto_approve)
end

def refuse
RefuseAuthorizationRequest.call(
authorization_request:,
user: system_user,
event_name: :auto_reject,
denial_of_authorization_params: { reason: REFUSAL_REASON },
)
end

def authorization_request
context.authorization_request
end

def system_user
User.find_or_create_by!(email: 'systeme@datapass.api.gouv.fr')
end
end
2 changes: 1 addition & 1 deletion app/jobs/register_organization_with_contacts_on_crm_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def extract_contact_type(contact)
def organization_categorie_juridique
return if organization.insee_payload.blank?

CategorieJuridique.find(organization.insee_payload['etablissement']['uniteLegale']['categorieJuridiqueUniteLegale'])
CategorieJuridique.find(organization.categorie_juridique)
rescue ActiveRecord::RecordNotFound
nil
end
Expand Down
Loading
Loading