From 5cb296ba8d3e81c117b951023b265e5be933b8da Mon Sep 17 00:00:00 2001
From: Caillou <6117264+JeSuisUnCaillou@users.noreply.github.com>
Date: Mon, 15 Jun 2026 14:31:51 +0200
Subject: [PATCH 1/5] explore prototype
---
.../display formulaires/Explore prototype.md | 110 ++++++++++++++++++
1 file changed, 110 insertions(+)
create mode 100644 docs/shaping/display formulaires/Explore prototype.md
diff --git a/docs/shaping/display formulaires/Explore prototype.md b/docs/shaping/display formulaires/Explore prototype.md
new file mode 100644
index 0000000000..9d39b7849a
--- /dev/null
+++ b/docs/shaping/display formulaires/Explore prototype.md
@@ -0,0 +1,110 @@
+# Context
+
+Read this PR : https://github.com/etalab/data_pass/pull/1595/
+This was a prototype of rough display of the formulaires (definitions) and cas d'usages (forms).
+
+Then read this PR : https://github.com/etalab/data_pass/pull/1607.
+This was built over the previous PR, and is a prototype of pretty display of the formulaires and cas d'usages.
+
+Now my plan is to start over from develop to make these features, and I'll use the inspiration of this design : /home/caillou/Downloads/DataPass%20-%20Fiche%20&%20cas%20d'usage%20(standalone).html
+
+But first, we are going to explore the code of the prototypes.
+
+# Explore the prototypes
+
+Explore the 2 PRs and tell me what should be reconsidered in the controllers and models to make it clean and respect the datapass' guidelines.
+
+> Response was a very detailed review, but actually I first need a summary of what was done to the models
+
+## Actually
+
+Actually, I first need a summary of the PR's changes to the models
+
+## Summary of the model changes
+
+> Claude's answer
+
+All model changes are in PR 1595 only — PR 1607 touched no models. Here's the summary:
+
+---
+
+### New model: `FormTemplate`
+
+A new ActiveRecord model backed by the `form_templates` table. It sits between `HabilitationType` (DB) and `AuthorizationRequestForm` (façade).
+
+**Schema:** `slug`, `name`, `description`, `introduction`, `use_case`, `default`, `public`, `startable_by_applicant`, `single_page_view`, `service_provider_id` (string, FK to YAML-backed `ServiceProvider`), plus jsonb columns `steps`, `static_blocks`, `scopes_config`, `initialize_with`.
+
+**Key behaviors:**
+- `friendly_id` on `name`, `paper_trail` versioning
+- `belongs_to :habilitation_type`
+- Validates that the slug doesn't collide with any YAML-defined form uid (`slug_not_taken_by_yaml`)
+- Enforces exactly one `default: true` per `HabilitationType` (via `only_one_default_per_habilitation_type`, `ht_keeps_at_least_one_default`, and `ensure_not_last_default` on `before_destroy`)
+- `after_commit :reset_arf_cache` — invalidates the `AuthorizationRequestForm` in-memory cache (via Redis) whenever a template is created, updated, or destroyed
+- `service_provider` is resolved at read time via `ServiceProvider.find(service_provider_id)`, returning `nil` gracefully if not found
+
+---
+
+### Modified: `HabilitationType`
+
+- Added `has_many :form_templates, dependent: :destroy`
+- Added `after_create :ensure_default_form_template!` — when a new `HabilitationType` is created, it immediately creates one default `FormTemplate` with slug `"#{slug}-default"`, seeding it with empty jsonb columns (steps/scopes cascade from the HT at render time)
+- `after_save :reset_static_caches` and `after_destroy :reset_static_caches` replaced by a single `after_commit :reset_static_caches, on: %i[create update destroy]` — avoids firing before the transaction is committed
+
+---
+
+### Modified: `AuthorizationRequestForm` (the YAML/DB façade)
+
+The façade previously iterated `HabilitationType` directly; it now iterates `FormTemplate`.
+
+- `db_records` now does `FormTemplate.includes(:habilitation_type).filter_map { build_form_from_template(template) }` instead of building one form per `HabilitationType`. This means one `HabilitationType` can now produce **N forms** (one per `FormTemplate`) rather than always exactly one.
+- `build_form_from_template` maps all `FormTemplate` columns to form attributes, with cascading fallback to `HabilitationType` for `name`, `description`, `introduction`, and `steps` when the template columns are blank.
+- New attribute `form_template` exposed on the façade to carry the originating `FormTemplate` record.
+- `inherited?(field)` — returns `true` if the given field (`name`, `description`, `introduction`, `steps`) is blank on the underlying `FormTemplate` (i.e. it fell back to the `HabilitationType` value).
+- `from_database?` — returns `true` if the form came from a `FormTemplate` (vs a YAML record).
+
+
+---
+
+## We dont' want the in-db cas d'usages (form_templates) yet.
+
+Ok, so I understand the PR 1595 introduces a in-db model for the cas d'usages (form_templates), but I don't want to do that yet. The work I want to do is to _only_ display formulaires (definitions) and cas d'usages (forms) for the instructors and managers.
+
+Can you summarize the model changes left if we ignore this ? (I think there should be none). Then summarize the controllers changes if we ignore this.
+
+## Model changes without `FormTemplate`
+
+> Claude's answer
+
+None. Every model change in PR 1595 exists purely to introduce and support `FormTemplate`:
+- `HabilitationType` changes (`has_many :form_templates`, `after_create :ensure_default_form_template!`, callback timing fix) are all in service of `FormTemplate`
+- `AuthorizationRequestForm` changes (`build_form_from_template`, `inherited?`, `from_database?`, `form_template` attribute) are all wiring `FormTemplate` into the façade
+
+The existing `AuthorizationDefinition` + `AuthorizationRequestForm` (YAML-backed) are already sufficient to display formulaires and cas d'usages. Zero model changes needed.
+
+---
+
+## Controller changes, ignoring `FormTemplate`
+
+> Claude's answer
+
+Everything still applies — the controllers only read `AuthorizationDefinition` and `AuthorizationRequestForm`, neither of which requires `FormTemplate`.
+
+**From PR 1595 (new infrastructure):**
+- `Instruction::DataProvidersController` — `index` listing providers the current user has reporter access to
+- `Instruction::AbstractCatalogueController` — `before_action :set_data_provider` (find by slug + authorize), shared parent for the two below
+- `Instruction::FormulairesController` — `index` filtering `authorization_definitions` by policy, `show` loading `available_forms` and counts
+- `Instruction::CasUsagesController` — `show` finding a specific form by uid within a formulaire
+- 3 Pundit policies (`DataProviderPolicy`, `AuthorizationDefinitionPolicy`, `AuthorizationRequestFormPolicy`)
+- Routes: `/instruction/fournisseurs-donnees` + nested formulaires + cas-usage
+
+**From PR 1607 (visual prototype built on top):**
+- `FormulairesController#show` extended with: `@authorization_request` stub (for WYSIWYG preview), stats counts, email count
+- `CasUsagesController#index` added, `show` extended with stats counts
+- `Instruction::EmailsAutomatiquesController` + `AutomaticEmailsCatalog` service — entirely independent of `FormTemplate`
+- `DashboardController` — navigation shortcut to the provider/formulaire list
+- Various views, partials, CSS, locales
+
+
+
+
+
From 4f7abbdfe5b4e28ed16b5800795c360c563f0a6c Mon Sep 17 00:00:00 2001
From: Caillou <6117264+JeSuisUnCaillou@users.noreply.github.com>
Date: Mon, 15 Jun 2026 15:01:23 +0200
Subject: [PATCH 2/5] display data providers
---
.../components/data_provider_card.css | 14 ++
.../instructor_menu_component.html.erb | 9 +
app/components/instructor_menu_component.rb | 7 +-
.../data_providers/card_component.html.erb | 24 +++
.../data_providers/card_component.rb | 12 ++
.../instruction/data_providers_controller.rb | 13 ++
app/models/data_provider.rb | 2 +-
.../instruction/data_provider_policy.rb | 18 ++
.../skip_links_implemented_checker.rb | 2 +
.../instruction/data_providers/index.html.erb | 15 ++
app/views/layouts/header/_menu.html.erb | 3 +-
config/locales/fr.yml | 1 +
config/locales/instruction.fr.yml | 9 +
config/locales/page_titles.fr.yml | 1 +
config/routes.rb | 2 +
...e prototype.md => 1. Explore prototype.md} | 5 +
.../2. List data providers.md | 39 ++++
...e & cas d'usage (standalone) (V0) (1).html | 183 ++++++++++++++++++
.../liste_fournisseurs_donnees.feature | 28 +++
.../liste_fournisseurs_donnees_vide.feature | 11 ++
.../step_definitions/instructions_steps.rb | 10 +
.../data_providers/card_component_preview.rb | 6 +
.../instruction/data_provider_policy_spec.rb | 90 +++++++++
23 files changed, 499 insertions(+), 5 deletions(-)
create mode 100644 app/assets/stylesheets/components/data_provider_card.css
create mode 100644 app/components/molecules/instruction/data_providers/card_component.html.erb
create mode 100644 app/components/molecules/instruction/data_providers/card_component.rb
create mode 100644 app/controllers/instruction/data_providers_controller.rb
create mode 100644 app/policies/instruction/data_provider_policy.rb
create mode 100644 app/views/instruction/data_providers/index.html.erb
rename docs/shaping/display formulaires/{Explore prototype.md => 1. Explore prototype.md} (94%)
create mode 100644 docs/shaping/display formulaires/2. List data providers.md
create mode 100644 docs/shaping/display formulaires/designs/DataPass - Fiche & cas d'usage (standalone) (V0) (1).html
create mode 100644 features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees.feature
create mode 100644 features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees_vide.feature
create mode 100644 spec/components/previews/molecules/instruction/data_providers/card_component_preview.rb
create mode 100644 spec/policies/instruction/data_provider_policy_spec.rb
diff --git a/app/assets/stylesheets/components/data_provider_card.css b/app/assets/stylesheets/components/data_provider_card.css
new file mode 100644
index 0000000000..00096e6e29
--- /dev/null
+++ b/app/assets/stylesheets/components/data_provider_card.css
@@ -0,0 +1,14 @@
+.data-provider-card-img {
+ background: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+ min-height: 9rem;
+}
+
+.data-provider-card-img img {
+ max-height: 6rem;
+ max-width: 80%;
+ object-fit: contain;
+}
diff --git a/app/components/instructor_menu_component.html.erb b/app/components/instructor_menu_component.html.erb
index e5aa3c95e0..7412c0671f 100644
--- a/app/components/instructor_menu_component.html.erb
+++ b/app/components/instructor_menu_component.html.erb
@@ -4,6 +4,14 @@
+<% if show_definitions %>
+
+<% end %>
diff --git a/app/views/layouts/header/_menu.html.erb b/app/views/layouts/header/_menu.html.erb
index 439f8e1d46..ada9940f39 100644
--- a/app/views/layouts/header/_menu.html.erb
+++ b/app/views/layouts/header/_menu.html.erb
@@ -11,7 +11,8 @@
<%= render InstructorMenuComponent.new(
show_drafts: policy([:instruction, :instructor_draft_request]).enabled?,
show_templates: policy([:instruction, :message_template]).index?,
- show_user_rights: policy([:instruction, :user_right]).index?
+ show_user_rights: policy([:instruction, :user_right]).index?,
+ show_definitions: policy([:instruction, :data_provider]).index?
) %>
<% end %>
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index f1e4aa5109..26f18e2412 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -111,6 +111,7 @@ fr:
authorizations_and_requests: Demandes / habilitations
message_templates: Modèles de message
user_rights: Gestion des droits
+ formulaires: Formulaires
footer:
tagline: L'outil de gestion des habilitations juridiques pour les données à accès restreint.
external_links:
diff --git a/config/locales/instruction.fr.yml b/config/locales/instruction.fr.yml
index 2316e1a04b..8ff51124cf 100644
--- a/config/locales/instruction.fr.yml
+++ b/config/locales/instruction.fr.yml
@@ -6,6 +6,15 @@ fr:
edit_templates_link: modifier les modèles
email_preview_accordion:
title: Voir un aperçu de l'email
+ data_providers:
+ index:
+ title: Fournisseurs de données
+ empty: Aucun fournisseur de données disponible.
+ card:
+ formulaires_count:
+ zero: Aucun formulaire
+ one: 1 formulaire
+ other: "%{count} formulaires"
dashboard:
authorization_requests:
search:
diff --git a/config/locales/page_titles.fr.yml b/config/locales/page_titles.fr.yml
index 12955c19f3..4ae96a92c1 100644
--- a/config/locales/page_titles.fr.yml
+++ b/config/locales/page_titles.fr.yml
@@ -35,6 +35,7 @@ fr:
admin_user_organization_verifications: Vérification lien utilisateur / organisation
instruction_dashboard: Tableau de bord instructeur
+ instruction_data_providers: Formulaires
instruction_show: "Instruction %{definition_name} - %{authorization_request_name}"
instruction_initiated_requests: Demandes initiées par les instructeurs
instruction_draft_requests_new: Initier une demande d’habilitation
diff --git a/config/routes.rb b/config/routes.rb
index b75f1eaa9a..f74586968e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -114,6 +114,8 @@
namespace :instruction do
get '/tableau-de-bord/:id', to: 'dashboard#show', as: :dashboard_show
+ resources :data_providers, only: [:index], path: 'fournisseurs-donnees'
+
resources :message_templates, only: %i[index new create edit update destroy], path: 'modeles-messages'
resources :authorization_requests, only: %w[show], path: 'demandes' do
diff --git a/docs/shaping/display formulaires/Explore prototype.md b/docs/shaping/display formulaires/1. Explore prototype.md
similarity index 94%
rename from docs/shaping/display formulaires/Explore prototype.md
rename to docs/shaping/display formulaires/1. Explore prototype.md
index 9d39b7849a..10886aa0c6 100644
--- a/docs/shaping/display formulaires/Explore prototype.md
+++ b/docs/shaping/display formulaires/1. Explore prototype.md
@@ -105,6 +105,11 @@ Everything still applies — the controllers only read `AuthorizationDefinition`
- Various views, partials, CSS, locales
+# Let's go
+We are ready to implement the display of definitions (formulaires) and forms (cas d'usages).
+Understand that in French and in the UI, we are naming authorization_definitions "Formulaires" and forms "Cas d'usages". And we don't rename them in the codebase yet.
+
+In the following prompts, I'll call "the prototype" the code in the 2 Pull Request #1595 and #1607 (mostly 1607).
diff --git a/docs/shaping/display formulaires/2. List data providers.md b/docs/shaping/display formulaires/2. List data providers.md
new file mode 100644
index 0000000000..8bd7ade6f0
--- /dev/null
+++ b/docs/shaping/display formulaires/2. List data providers.md
@@ -0,0 +1,39 @@
+
+> Read `/docs/shaping/display_formulaires/Explore prototype.md` for context.
+
+# List data providers and their authorization_definitions
+
+First, we are going to make the routes, controllers and views to list the data providers.
+We will also add the button "Gérer mes formulaires" in /instruction/tableau-de-bord/demandes
+
+Use the prototype as an example for the UI and the wordings, but you can rethink all the code.
+
+Make ViewComponents like describe in @CLAUDE.md:133-141
+
+## Submenu entry instead of button in dashboard
+
+Instead of the button in the dashboard, I want a new submenu entry called "Formulaires", like in this design : file:///home/caillou/Apps/datagouv/data_pass/docs/shaping/display%20formulaires/designs/DataPass%20-%20Fiche%20&%20cas%20d'usage%20(standalone)%20(V0)%20(1).html
+
+(the deisgn call it "Formulaires et cas d'usages", but we will just name it "Formulaires").
+
+## A page to list the providers only
+
+Ok, forget about the list of definitions, we will just list the providers. I want the design to look like this : https://sandbox.datapass.api.gouv.fr/instruction/fournisseurs-donnees (which is the PR #1607 you should have take as a model actually)
+
+## Tweaks
+
+- We are missing the cursor:pointer on hover, and the blue arrow bottom right. Check out #1607 for that.
+
+- We are not renaming authorization_definitions into formulaires yet ! Don't use "formulaire" where we are actually referencing authorization_definitions in the code.
+
+- Don't use inlince css, make a class and put the css in a stylesheet.
+
+- rename accessible_count into definitions_count in the card component
+
+- I moved the feature file under /features/instructeurs/gestion_des_formulaires/
+
+- the view component preview without logo has an error : Couldn't find DataProvider with [WHERE "logo_attachment"."id" IS NULL]. It should instead render a card without logo.
+
+- why does the view component preview doesn't look like what we see on /instruction/fournisseurs-donnees ? The logo is not centered, and overflows from its container.
+
+- Add a feature to check if an instructor with only 2 data_providers in his roles sees only these two providers in the list, and another to check if an instructor with no data_providers in his roles but one definition, sees the provider of this definition.
\ No newline at end of file
diff --git a/docs/shaping/display formulaires/designs/DataPass - Fiche & cas d'usage (standalone) (V0) (1).html b/docs/shaping/display formulaires/designs/DataPass - Fiche & cas d'usage (standalone) (V0) (1).html
new file mode 100644
index 0000000000..23a7bdf174
--- /dev/null
+++ b/docs/shaping/display formulaires/designs/DataPass - Fiche & cas d'usage (standalone) (V0) (1).html
@@ -0,0 +1,183 @@
+
+
+
+
+ DataPass — Fiche du formulaire & cas d'usage
+
+
+
+
+
+
+
+
Unpacking...
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees.feature b/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees.feature
new file mode 100644
index 0000000000..b22fd0b0ba
--- /dev/null
+++ b/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees.feature
@@ -0,0 +1,28 @@
+# language: fr
+
+Fonctionnalité: Liste des fournisseurs de données pour les instructeurs
+ En tant qu'instructeur, je peux consulter la liste des fournisseurs de données
+ dont je suis en charge, afin de gérer mes formulaires.
+
+ Contexte:
+ Soit un fournisseur de données "DINUM" existe
+ Sachant que je suis un rapporteur "API Entreprise"
+ Et que je me connecte
+
+ Scénario: Je peux accéder à la liste des fournisseurs de données
+ Quand je me rends sur la liste des formulaires
+ Alors la page contient "DINUM"
+
+ Scénario: Je vois le lien vers les formulaires dans le menu de navigation
+ Quand je me rends sur mon tableau de bord instructeur
+ Alors le menu de navigation contient "Formulaires"
+
+ Scénario: Je ne vois que les fournisseurs de données pour lesquels j'ai un rôle
+ Soit un fournisseur de données "CNAM" existe
+ Et un fournisseur de données "DGFIP" existe
+ Et je suis un rapporteur "API Indemnités Journalières de la CNAM"
+ Quand je me rends sur la liste des formulaires
+ Alors la page contient "DINUM"
+ Et la page contient "CNAM"
+ Et la page ne contient pas "DGFIP"
+
diff --git a/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees_vide.feature b/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees_vide.feature
new file mode 100644
index 0000000000..d5223b8cb2
--- /dev/null
+++ b/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees_vide.feature
@@ -0,0 +1,11 @@
+# language: fr
+
+Fonctionnalité: Liste vide des fournisseurs de données pour les instructeurs
+ En tant qu'instructeur sans fournisseur de données accessible, je vois un
+ message m'indiquant qu'aucun fournisseur n'est disponible.
+
+ Scénario: Je vois un message si aucun fournisseur de données n'est accessible
+ Sachant que je suis un rapporteur "API Entreprise"
+ Et que je me connecte
+ Quand je me rends sur la liste des formulaires
+ Alors la page contient "Aucun fournisseur de données disponible."
diff --git a/features/step_definitions/instructions_steps.rb b/features/step_definitions/instructions_steps.rb
index bd3c54a6a1..7696522b63 100644
--- a/features/step_definitions/instructions_steps.rb
+++ b/features/step_definitions/instructions_steps.rb
@@ -1,3 +1,13 @@
Alors("je suis sur l'espace instruction") do
expect(page).to have_current_path(/instruction/)
end
+
+Quand('je me rends sur la liste des formulaires') do
+ visit instruction_data_providers_path
+end
+
+Alors('le menu de navigation contient {string}') do |text|
+ within('#navigation-header-menu') do
+ expect(page).to have_text(text)
+ end
+end
diff --git a/spec/components/previews/molecules/instruction/data_providers/card_component_preview.rb b/spec/components/previews/molecules/instruction/data_providers/card_component_preview.rb
new file mode 100644
index 0000000000..e3562636b5
--- /dev/null
+++ b/spec/components/previews/molecules/instruction/data_providers/card_component_preview.rb
@@ -0,0 +1,6 @@
+class Molecules::Instruction::DataProviders::CardComponentPreview < ApplicationPreview
+ def default
+ data_provider = DataProvider.first!
+ render Molecules::Instruction::DataProviders::CardComponent.new(data_provider:, definitions_count: 12)
+ end
+end
diff --git a/spec/policies/instruction/data_provider_policy_spec.rb b/spec/policies/instruction/data_provider_policy_spec.rb
new file mode 100644
index 0000000000..1181c9a436
--- /dev/null
+++ b/spec/policies/instruction/data_provider_policy_spec.rb
@@ -0,0 +1,90 @@
+RSpec.describe Instruction::DataProviderPolicy do
+ subject(:policy) { described_class.new(UserContext.new(user), data_provider) }
+
+ let(:data_provider) { create(:data_provider, :dinum) }
+
+ describe '#index?' do
+ subject { policy.index? }
+
+ context 'when user is a reporter' do
+ let(:user) { create(:user, :reporter) }
+
+ it { is_expected.to be true }
+ end
+
+ context 'when user is an instructor' do
+ let(:user) { create(:user, :instructor) }
+
+ it { is_expected.to be true }
+ end
+
+ context 'when user is a manager' do
+ let(:user) { create(:user, :manager) }
+
+ it { is_expected.to be true }
+ end
+
+ context 'when user has no role' do
+ let(:user) { create(:user) }
+
+ it { is_expected.to be false }
+ end
+ end
+
+ describe 'Scope' do
+ subject(:scope) { described_class::Scope.new(UserContext.new(user), DataProvider).resolve }
+
+ let!(:dinum_provider) { create(:data_provider, :dinum) }
+ let!(:dgfip_provider) { create(:data_provider, :dgfip) }
+
+ context 'when user is an admin' do
+ let(:user) { create(:user, :admin) }
+
+ it 'returns all data providers' do
+ expect(scope).to include(dinum_provider, dgfip_provider)
+ end
+ end
+
+ context 'when user has fd_reporter role on the provider' do
+ let(:user) { create(:user, :fd_reporter, data_provider_slugs: ['dinum']) }
+
+ it 'returns only the providers the user has access to' do
+ expect(scope).to include(dinum_provider)
+ expect(scope).not_to include(dgfip_provider)
+ end
+ end
+
+ context 'when user has fd_instructor role on the provider' do
+ let(:user) { create(:user, :fd_instructor, data_provider_slugs: ['dinum']) }
+
+ it 'returns only the providers the user has access to' do
+ expect(scope).to include(dinum_provider)
+ expect(scope).not_to include(dgfip_provider)
+ end
+ end
+
+ context 'when user has fd_manager role on the provider' do
+ let(:user) { create(:user, :fd_manager, data_provider_slugs: ['dinum']) }
+
+ it 'returns only the providers the user has access to' do
+ expect(scope).to include(dinum_provider)
+ expect(scope).not_to include(dgfip_provider)
+ end
+ end
+
+ context 'when user has a definition-level reporter role' do
+ let(:user) { create(:user, :reporter, authorization_request_types: %w[api_entreprise]) }
+
+ it 'returns the provider derived from the definition, not unrelated ones' do
+ expect(scope).to include(dinum_provider)
+ expect(scope).not_to include(dgfip_provider)
+ end
+ end
+
+ context 'when user has no roles' do
+ let(:user) { create(:user) }
+
+ it { is_expected.to be_empty }
+ end
+ end
+end
From 384c355efd861412dae692205caa9e2b03fe478b Mon Sep 17 00:00:00 2001
From: Caillou <6117264+JeSuisUnCaillou@users.noreply.github.com>
Date: Mon, 22 Jun 2026 09:53:58 +0200
Subject: [PATCH 3/5] order providers
---
app/controllers/instruction/data_providers_controller.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/controllers/instruction/data_providers_controller.rb b/app/controllers/instruction/data_providers_controller.rb
index 6c5589bb0d..1f7389ebc3 100644
--- a/app/controllers/instruction/data_providers_controller.rb
+++ b/app/controllers/instruction/data_providers_controller.rb
@@ -1,7 +1,7 @@
class Instruction::DataProvidersController < InstructionController
def index
authorize %i[instruction data_provider], :index?
- @data_providers = policy_scope([:instruction, DataProvider]).with_attached_logo
+ @data_providers = policy_scope([:instruction, DataProvider]).with_attached_logo.order(:name)
@definitions_counts = @data_providers.index_with { |data_provider| reporter_definitions_count(data_provider) }
end
From 33d4a3ce9f49b1c0b9ce1407d0b47198a851c34f Mon Sep 17 00:00:00 2001
From: Caillou <6117264+JeSuisUnCaillou@users.noreply.github.com>
Date: Mon, 22 Jun 2026 09:57:18 +0200
Subject: [PATCH 4/5] move provider_slugs to role_set
---
app/models/role_set.rb | 4 ++++
.../instruction/data_provider_policy.rb | 7 +-----
spec/models/role_set_spec.rb | 23 +++++++++++++++++++
3 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/app/models/role_set.rb b/app/models/role_set.rb
index 3a278d8620..5ef80129dc 100644
--- a/app/models/role_set.rb
+++ b/app/models/role_set.rb
@@ -19,6 +19,10 @@ def covers?(definition_id = nil)
delegate :any?, to: :@roles
+ def provider_slugs
+ @roles.filter_map(&:provider_slug).uniq
+ end
+
def definition_ids
@definition_ids ||= @roles.flat_map { |parsed|
if parsed.fd_level?
diff --git a/app/policies/instruction/data_provider_policy.rb b/app/policies/instruction/data_provider_policy.rb
index 9a714027d9..a0bea9ef9a 100644
--- a/app/policies/instruction/data_provider_policy.rb
+++ b/app/policies/instruction/data_provider_policy.rb
@@ -3,12 +3,7 @@ class Scope < Scope
def resolve
return scope.all if user.admin?
- provider_slugs = user.roles.filter_map { |r|
- parsed_role = ParsedRole.parse(r)
- parsed_role.provider_slug if parsed_role.provider_slug && parsed_role.role.in?(User::ROLES)
- }.uniq
-
- scope.where(slug: provider_slugs)
+ scope.where(slug: user.roles_for(:reporter).provider_slugs)
end
end
diff --git a/spec/models/role_set_spec.rb b/spec/models/role_set_spec.rb
index 9b899136ad..b1f374aac1 100644
--- a/spec/models/role_set_spec.rb
+++ b/spec/models/role_set_spec.rb
@@ -97,6 +97,29 @@
end
end
+ describe '#provider_slugs' do
+ it 'returns unique provider slugs for matching roles' do
+ role_set = described_class.new(
+ %w[dinum:api_entreprise:reporter dinum:api_particulier:reporter dgfip:api_impot_particulier_fc_sandbox:reporter],
+ :reporter,
+ )
+
+ expect(role_set.provider_slugs).to match_array(%w[dinum dgfip])
+ end
+
+ it 'includes FD-level wildcard provider slugs' do
+ role_set = described_class.new(%w[dinum:*:reporter], :reporter)
+
+ expect(role_set.provider_slugs).to eq(%w[dinum])
+ end
+
+ it 'returns empty array when no matching roles' do
+ role_set = described_class.new(%w[], :reporter)
+
+ expect(role_set.provider_slugs).to be_empty
+ end
+ end
+
describe '#authorization_request_types' do
it 'returns classified authorization request types' do
role_set = described_class.new(%w[dinum:api_entreprise:instructor], :instructor)
From 1876a2f979c0ce0afcca88d5f2c8a22b730197ea Mon Sep 17 00:00:00 2001
From: Caillou <6117264+JeSuisUnCaillou@users.noreply.github.com>
Date: Mon, 22 Jun 2026 15:04:41 +0200
Subject: [PATCH 5/5] fature flag to display formulaires menu
---
app/components/instructor_menu_component.html.erb | 2 +-
app/controllers/authenticated_user_controller.rb | 6 ++++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/app/components/instructor_menu_component.html.erb b/app/components/instructor_menu_component.html.erb
index 7412c0671f..0fda255f42 100644
--- a/app/components/instructor_menu_component.html.erb
+++ b/app/components/instructor_menu_component.html.erb
@@ -4,7 +4,7 @@
-<% if show_definitions %>
+<% if show_definitions && helpers.feature_flag_formulaires_management_visible? %>