From 758b739aa39a0c627a5b0ccdd0b3b88f23990a69 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 28 Aug 2023 14:25:45 +0200 Subject: [PATCH 01/13] amelioration(procedure.chorus): ajoute le feature flag `chorus` aux procedure pour activer/desactiver cette nouvelle feature --- app/models/concerns/procedure_chorus_concern.rb | 9 +++++++++ app/models/procedure.rb | 1 + 2 files changed, 10 insertions(+) create mode 100644 app/models/concerns/procedure_chorus_concern.rb diff --git a/app/models/concerns/procedure_chorus_concern.rb b/app/models/concerns/procedure_chorus_concern.rb new file mode 100644 index 000000000..5de495d1e --- /dev/null +++ b/app/models/concerns/procedure_chorus_concern.rb @@ -0,0 +1,9 @@ +module ProcedureChorusConcern + extend ActiveSupport::Concern + + included do + def chorusable? + feature_enabled?(:chorus) + end + end +end diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 742b92343..79b1bc2ed 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -4,6 +4,7 @@ class Procedure < ApplicationRecord include InitiationProcedureConcern include ProcedureGroupeInstructeurAPIHackConcern include ProcedureSVASVRConcern + include ProcedureChorusConcern include Discard::Model self.discard_column = :hidden_at From 7d7a741a1ae09b31632be6e52a9c2cb826a6c677 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 28 Aug 2023 14:31:12 +0200 Subject: [PATCH 02/13] amelioration(procedure.chorus): ajoute la tuile chorus quand cette fonction est active sur la procedure --- .../procedure/card/chorus_component.rb | 13 ++++++++++ .../chorus_component.html.haml | 20 ++++++++++++++++ .../administrateurs/procedures/show.html.haml | 1 + .../procedures/card/chorus_component_spec.rb | 24 +++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 app/components/procedure/card/chorus_component.rb create mode 100644 app/components/procedure/card/chorus_component/chorus_component.html.haml create mode 100644 spec/components/procedures/card/chorus_component_spec.rb diff --git a/app/components/procedure/card/chorus_component.rb b/app/components/procedure/card/chorus_component.rb new file mode 100644 index 000000000..e4830ce70 --- /dev/null +++ b/app/components/procedure/card/chorus_component.rb @@ -0,0 +1,13 @@ +class Procedure::Card::ChorusComponent < ApplicationComponent + def initialize(procedure:) + @procedure = procedure + end + + def render? + @procedure.chorusable? + end + + def error_messages + [] + end +end diff --git a/app/components/procedure/card/chorus_component/chorus_component.html.haml b/app/components/procedure/card/chorus_component/chorus_component.html.haml new file mode 100644 index 000000000..7f85ec850 --- /dev/null +++ b/app/components/procedure/card/chorus_component/chorus_component.html.haml @@ -0,0 +1,20 @@ +.fr-col-6.fr-col-md-4.fr-col-lg-3.chorus-component + = link_to edit_admin_procedure_chorus_path(@procedure), class: 'fr-tile fr-enlarge-link', title: error_messages do + .fr-tile__body.flex.column.align-center.justify-between + - if error_messages.present? + %div + %span.icon.refuse + %p.fr-tile-status-error À modifier + - elsif @count == 0 + %div + %span.icon.clock + %p.fr-tile-status-todo À compléter + - else + %div + %span.icon.accept + %p.fr-tile-status-accept Configuré + %div + %h3.fr-h6.fr-mt-10v + Connecteur Chorus + %p.fr-tile-subtitle Vous traitez des données de subvention d'état ? + %p.fr-btn.fr-btn--tertiary Configurer diff --git a/app/views/administrateurs/procedures/show.html.haml b/app/views/administrateurs/procedures/show.html.haml index bb4ad9d22..0df9bb013 100644 --- a/app/views/administrateurs/procedures/show.html.haml +++ b/app/views/administrateurs/procedures/show.html.haml @@ -66,3 +66,4 @@ = render Procedure::Card::SVASVRComponent.new(procedure: @procedure) if @procedure.sva_svr_enabled? || @procedure.feature_enabled?(:sva) = render Procedure::Card::MonAvisComponent.new(procedure: @procedure) = render Procedure::Card::DossierSubmittedMessageComponent.new(procedure: @procedure) + = render Procedure::Card::ChorusComponent.new(procedure: @procedure) diff --git a/spec/components/procedures/card/chorus_component_spec.rb b/spec/components/procedures/card/chorus_component_spec.rb new file mode 100644 index 000000000..b5148c376 --- /dev/null +++ b/spec/components/procedures/card/chorus_component_spec.rb @@ -0,0 +1,24 @@ +describe Procedure::Card::ChorusComponent, type: :component do + describe 'render' do + let(:procedure) { create(:procedure) } + + subject do + render_inline(described_class.new(procedure: procedure)) + end + + context 'feature flag not active' do + it 'does not render' do + subject + expect(page).not_to have_text('Connecteur Chorus') + end + end + context 'feature flag active' do + before { Flipper.enable_actor :chorus, procedure } + + it 'render the template' do + subject + expect(page).to have_text('Connecteur Chorus') + end + end + end +end From fea0cb1c6045da738b03f5b3e31e883ba40eb8a3 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 28 Aug 2023 14:56:52 +0200 Subject: [PATCH 03/13] amelioration(procedure.chorus): connecte la tuile a un controller basique ayant les ACL --- .../administrateurs/chorus_controller.rb | 8 +++++++ .../administrateurs/chorus/edit.html.haml | 9 ++++++++ config/routes.rb | 1 + .../administrateurs/chorus_controller_spec.rb | 22 +++++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 app/controllers/administrateurs/chorus_controller.rb create mode 100644 app/views/administrateurs/chorus/edit.html.haml create mode 100644 spec/controllers/administrateurs/chorus_controller_spec.rb diff --git a/app/controllers/administrateurs/chorus_controller.rb b/app/controllers/administrateurs/chorus_controller.rb new file mode 100644 index 000000000..ebf5b682f --- /dev/null +++ b/app/controllers/administrateurs/chorus_controller.rb @@ -0,0 +1,8 @@ +module Administrateurs + class ChorusController < AdministrateurController + before_action :retrieve_procedure + + def edit + end + end +end diff --git a/app/views/administrateurs/chorus/edit.html.haml b/app/views/administrateurs/chorus/edit.html.haml new file mode 100644 index 000000000..6d6e2327a --- /dev/null +++ b/app/views/administrateurs/chorus/edit.html.haml @@ -0,0 +1,9 @@ += render partial: 'administrateurs/breadcrumbs', + locals: { steps: [['Démarches', admin_procedures_path], + [@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)], + ['Connecteur Chorus']] } + + +.container + %h1.mb-2 + Cadre budgétaire diff --git a/config/routes.rb b/config/routes.rb index 1e0d2d225..9c2e0f460 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -601,6 +601,7 @@ Rails.application.routes.draw do resource :attestation_template, only: [:show, :edit, :update, :create] do get 'preview', on: :member end + resource :chorus, only: [:edit] resource :dossier_submitted_message, only: [:edit, :update, :create] # ADDED TO ACCESS IT FROM THE IFRAME get 'attestation_template/preview' => 'attestation_templates#preview' diff --git a/spec/controllers/administrateurs/chorus_controller_spec.rb b/spec/controllers/administrateurs/chorus_controller_spec.rb new file mode 100644 index 000000000..0e0005626 --- /dev/null +++ b/spec/controllers/administrateurs/chorus_controller_spec.rb @@ -0,0 +1,22 @@ +describe Administrateurs::ChorusController, type: :controller do + describe 'edit' do + let(:user) { create(:user) } + let(:admin) { create(:administrateur, user: create(:user)) } + let(:procedure) { create(:procedure, administrateurs: [admin]) } + subject { get :edit, params: { procedure_id: procedure.id } } + + context 'not signed in' do + it { is_expected.to redirect_to(new_user_session_path) } + end + + context 'signed in but not admin of procedure' do + before { sign_in(user) } + it { is_expected.to redirect_to(new_user_session_path) } + end + + context 'signed as admin' do + before { sign_in(admin.user) } + it { is_expected.to have_http_status(200) } + end + end +end From d8f50700fcb87af7982ba6df035030882170d8bf Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 28 Aug 2023 17:18:54 +0200 Subject: [PATCH 04/13] tech(model.procedure): ajoute une a la table `procedures` la colonne `chorus` (jsonb) --- db/migrate/20230828131618_add_chorus_column_to_procedure.rb | 5 +++++ db/schema.rb | 1 + 2 files changed, 6 insertions(+) create mode 100644 db/migrate/20230828131618_add_chorus_column_to_procedure.rb diff --git a/db/migrate/20230828131618_add_chorus_column_to_procedure.rb b/db/migrate/20230828131618_add_chorus_column_to_procedure.rb new file mode 100644 index 000000000..fd17d5254 --- /dev/null +++ b/db/migrate/20230828131618_add_chorus_column_to_procedure.rb @@ -0,0 +1,5 @@ +class AddChorusColumnToProcedure < ActiveRecord::Migration[7.0] + def change + add_column :procedures, :chorus, :jsonb, default: {}, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index e9ba894e6..ac4ed8490 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -776,6 +776,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_10_083144) do t.string "cadre_juridique" t.bigint "canonical_procedure_id" t.boolean "cerfa_flag", default: false + t.jsonb "chorus", default: {}, null: false t.boolean "cloned_from_library", default: false t.datetime "closed_at", precision: 6 t.datetime "created_at", precision: 6, null: false From 87f435d2d36062c4ec9adcc571e6a1d4c0c2b14a Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 29 Aug 2023 09:22:58 +0200 Subject: [PATCH 05/13] amelioration(procedure.chorus): ajoute le form component pour le 'crud' du ChorusConfiguration --- .../procedure/chorus_form_component.rb | 7 ++ .../chorus_form_component.html.haml | 15 ++++ .../administrateurs/chorus_controller.rb | 20 ++++++ app/models/chorus_configuration.rb | 24 +++++++ .../concerns/procedure_chorus_concern.rb | 4 ++ .../administrateurs/chorus/edit.html.haml | 2 + config/routes.rb | 2 +- .../administrateurs/chorus_controller_spec.rb | 69 +++++++++++++++++++ 8 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 app/components/procedure/chorus_form_component.rb create mode 100644 app/components/procedure/chorus_form_component/chorus_form_component.html.haml create mode 100644 app/models/chorus_configuration.rb diff --git a/app/components/procedure/chorus_form_component.rb b/app/components/procedure/chorus_form_component.rb new file mode 100644 index 000000000..219b4e066 --- /dev/null +++ b/app/components/procedure/chorus_form_component.rb @@ -0,0 +1,7 @@ +class Procedure::ChorusFormComponent < ApplicationComponent + attr_reader :procedure + + def initialize(procedure:) + @procedure = procedure + end +end diff --git a/app/components/procedure/chorus_form_component/chorus_form_component.html.haml b/app/components/procedure/chorus_form_component/chorus_form_component.html.haml new file mode 100644 index 000000000..6f48026ba --- /dev/null +++ b/app/components/procedure/chorus_form_component/chorus_form_component.html.haml @@ -0,0 +1,15 @@ += form_for([procedure, procedure.chorus_configuration],url: admin_procedure_chorus_path(procedure), method: :put) do |f| + + .fr-select-group + = f.label :centre_de_coup, class: 'fr-label' + = f.select :centre_de_coup, options_for_select(ChorusConfiguration.centre_de_coup_options, procedure.chorus_configuration.centre_de_coup), {}, class: 'fr-select' + + .fr-select-group + = f.label :domaine_fonctionnel, class: 'fr-label' + = f.select :domaine_fonctionnel, options_for_select(ChorusConfiguration.domaine_fonctionnel_options, procedure.chorus_configuration.domaine_fonctionnel), {}, class: 'fr-select' + + .fr-select-group + = f.label :referentiel_de_programmation, class: 'fr-label' + = f.select :referentiel_de_programmation, options_for_select(ChorusConfiguration.referentiel_de_programmation_options, procedure.chorus_configuration.referentiel_de_programmation), {}, class: 'fr-select' + + = f.submit "Enregister", class: 'fr-btn' diff --git a/app/controllers/administrateurs/chorus_controller.rb b/app/controllers/administrateurs/chorus_controller.rb index ebf5b682f..e6b850392 100644 --- a/app/controllers/administrateurs/chorus_controller.rb +++ b/app/controllers/administrateurs/chorus_controller.rb @@ -4,5 +4,25 @@ module Administrateurs def edit end + + def update + @configuration = @procedure.chorus_configuration + @configuration.assign_attributes(configurations_params) + if @configuration.valid? + @procedure.update!(chorus: @configuration.attributes) + + flash.notice = "La configuration Chorus a été mise à jour et prend immédiatement effet pour les nouveaux dossiers." + redirect_to admin_procedure_path(@procedure) + else + flash.now.alert = "Des erreurs empêchent la validation du connecteur chorus. Corrigez les erreurs" + render :edit + end + end + + private + + def configurations_params + params.require(:chorus_configuration).permit(:centre_de_coup, :domaine_fonctionnel, :referentiel_de_programmation) + end end end diff --git a/app/models/chorus_configuration.rb b/app/models/chorus_configuration.rb new file mode 100644 index 000000000..cec9eb9a1 --- /dev/null +++ b/app/models/chorus_configuration.rb @@ -0,0 +1,24 @@ +class ChorusConfiguration + include ActiveModel::Model + include ActiveModel::Attributes + + attribute :centre_de_coup, default: nil + attribute :domaine_fonctionnel, default: nil + attribute :referentiel_de_programmation, default: nil + + validates :centre_de_coup, inclusion: { in: Proc.new { ChorusConfiguration.centre_de_coup_options } } + validates :domaine_fonctionnel, inclusion: { in: Proc.new { ChorusConfiguration.domaine_fonctionnel_options } } + validates :referentiel_de_programmation, inclusion: { in: Proc.new { ChorusConfiguration.referentiel_de_programmation_options } } + + def self.centre_de_coup_options + [1, 2, 3].map(&:to_s) + end + + def self.domaine_fonctionnel_options + [4, 5, 6].map(&:to_s) + end + + def self.referentiel_de_programmation_options + [7, 8, 9].map(&:to_s) + end +end diff --git a/app/models/concerns/procedure_chorus_concern.rb b/app/models/concerns/procedure_chorus_concern.rb index 5de495d1e..fe71152f5 100644 --- a/app/models/concerns/procedure_chorus_concern.rb +++ b/app/models/concerns/procedure_chorus_concern.rb @@ -2,6 +2,10 @@ module ProcedureChorusConcern extend ActiveSupport::Concern included do + def chorus_configuration + @chorus_configuration ||= ChorusConfiguration.new(chorus) + end + def chorusable? feature_enabled?(:chorus) end diff --git a/app/views/administrateurs/chorus/edit.html.haml b/app/views/administrateurs/chorus/edit.html.haml index 6d6e2327a..8f6037e99 100644 --- a/app/views/administrateurs/chorus/edit.html.haml +++ b/app/views/administrateurs/chorus/edit.html.haml @@ -7,3 +7,5 @@ .container %h1.mb-2 Cadre budgétaire + + = render Procedure::ChorusFormComponent.new(procedure: @procedure) diff --git a/config/routes.rb b/config/routes.rb index 9c2e0f460..21158ba68 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -601,7 +601,7 @@ Rails.application.routes.draw do resource :attestation_template, only: [:show, :edit, :update, :create] do get 'preview', on: :member end - resource :chorus, only: [:edit] + resource :chorus, only: [:edit, :update] resource :dossier_submitted_message, only: [:edit, :update, :create] # ADDED TO ACCESS IT FROM THE IFRAME get 'attestation_template/preview' => 'attestation_templates#preview' diff --git a/spec/controllers/administrateurs/chorus_controller_spec.rb b/spec/controllers/administrateurs/chorus_controller_spec.rb index 0e0005626..89b6d7278 100644 --- a/spec/controllers/administrateurs/chorus_controller_spec.rb +++ b/spec/controllers/administrateurs/chorus_controller_spec.rb @@ -17,6 +17,75 @@ describe Administrateurs::ChorusController, type: :controller do context 'signed as admin' do before { sign_in(admin.user) } it { is_expected.to have_http_status(200) } + + context 'rendered' do + render_views + + it { is_expected.to have_http_status(200) } + end + end + end + + describe 'update' do + let(:user) { create(:user) } + let(:admin) { create(:administrateur, user: create(:user)) } + let(:procedure) { create(:procedure, administrateurs: [admin]) } + let(:chorus_configuration_params) { {} } + subject do + put :update, + params: { + procedure_id: procedure.id, + chorus_configuration: chorus_configuration_params + } + end + + context 'not signed in' do + it { is_expected.to redirect_to(new_user_session_path) } + end + + context 'signed in but not admin of procedure' do + before { sign_in(user) } + it { is_expected.to redirect_to(new_user_session_path) } + end + + context 'signed as admin' do + before { sign_in(admin.user) } + + context "valid payload" do + let(:chorus_configuration_params) do + { + centre_de_coup: ChorusConfiguration.centre_de_coup_options.first, + domaine_fonctionnel: ChorusConfiguration.domaine_fonctionnel_options.first, + referentiel_de_programmation: ChorusConfiguration.referentiel_de_programmation_options.first + } + end + + it { is_expected.to redirect_to(admin_procedure_path(procedure)) } + it 'updates params' do + subject + expect(flash[:notice]).to eq("La configuration Chorus a été mise à jour et prend immédiatement effet pour les nouveaux dossiers.") + procedure.reload + expect(procedure.chorus_configuration.centre_de_coup).to eq(ChorusConfiguration.centre_de_coup_options.first) + expect(procedure.chorus_configuration.domaine_fonctionnel).to eq(ChorusConfiguration.domaine_fonctionnel_options.first) + expect(procedure.chorus_configuration.referentiel_de_programmation).to eq(ChorusConfiguration.referentiel_de_programmation_options.first) + end + end + + context "invalid payload" do + let(:chorus_configuration_params) do + { + centre_de_coup: 0 + } + end + + it { is_expected.to have_http_status(200) } + it 'updates params' do + subject + expect(flash[:notice]).to eq("Des erreurs empêchent la validation du connecteur chorus. Corrigez les erreurs") + procedure.reload + expect(procedure.chorus_configuration.centre_de_coup).to eq(nil) + end + end end end end From df78e1446935b5f20b1663edb143d87d8495cc64 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 1 Sep 2023 11:00:49 +0200 Subject: [PATCH 06/13] amelioration(procedure.chorus): ajoute le service pour requeter les api chorus fournies par la bretagne --- app/services/api_bretagne_service.rb | 92 ++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 app/services/api_bretagne_service.rb diff --git a/app/services/api_bretagne_service.rb b/app/services/api_bretagne_service.rb new file mode 100644 index 000000000..ab40e0760 --- /dev/null +++ b/app/services/api_bretagne_service.rb @@ -0,0 +1,92 @@ +class APIBretagneService + include Dry::Monads[:result] + HOST = 'https://api.databretagne.fr' + ENDPOINTS = { + # see: https://api.databretagne.fr/budget/doc#operations-Auth_Controller-post_login + "login" => "/budget/api/v1/auth/login", + # see: https://api.databretagne.fr/budget/doc#operations-Centre_couts-get_ref_controller_list + "centre-couts" => '/budget/api/v1/centre-couts', + # see: https://api.databretagne.fr/budget/doc#operations-Domaine_Fonctionnel-get_ref_controller_list + "domaine-fonct" => '/budget/api/v1/domaine-fonct', + # see: https://api.databretagne.fr/budget/doc#operations-Referentiel_Programmation-get_ref_controller_list + "ref-programmation" => '/budget/api/v1/ref-programmation' + } + + def search_domaine_fonct(code_or_label: "") + url = build_url(ENDPOINTS.fetch('domaine-fonct')) + fetch_all_page(url:, code_or_label:) + end + + private + + def fetch_page(url:, params:, retry_count: 1) + result = call(url:, params:) + case result + in Failure(code:, reason:) if code.in?(401..403) + if retry_count > 0 + login + fetch_page(url:, params:, retry_count: 0) + else + fail "APIBretagneService, #{reason} #{code}" + end + in Failure(code:) if code == 204 + [] + in Success(body:) + body + end + end + + def fetch_all_page(url:, code_or_label:) + first_page = fetch_page(url:, params: { page_number: 1, query: code_or_label }) + return [] if first_page.empty? + + total_pages = (first_page[:pageInfo][:totalRows].to_f / first_page[:pageInfo][:pageSize].to_f).ceil + all = first_page[:items] + (2..total_pages).map do |page_number| + page = fetch_page(url:, params: { page_number: }) + all.concat(page[:items]) + end + all + end + + def call(url:, params:) + API::Client.new.(url:, params:, authorization_token:, method:) + end + + def method + :get + end + + def authorization_token + result = login + case result + in Success(token:) + @token = token + in Failure(reason:, code:) + fail "APIBretagneService, #{reason} #{code}" + end + end + + def login + result = API::Client.new.call(url: build_url(ENDPOINTS.fetch("login")), + json: { + email: ENV['API_DATABRETAGE_USERNAME'], + password: ENV['API_DATABRETAGE_PASSWORD'] + }, + method: :post) + case result + in Success(body:) + Success(token: body.split("Bearer ")[1]) + in Failure(code:, reason:) if code.in?(403) + Failure(API::Client::Error[:invalid_credential, code, false, reason]) + else + Failure(API::Client::Error[:api_down]) + end + end + + def build_url(endpoint) + uri = URI(HOST) + uri.path = endpoint + uri + end +end From ecb3909e99576295a83507554f97d8af7c3d883b Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 6 Sep 2023 17:16:02 +0200 Subject: [PATCH 07/13] chorus(autocomplete): autocomplete sur le centre de cout, domaine fonctionnel, referentiel de programmation --- .../chorus_form_component.html.haml | 20 +- .../administrateurs/chorus_controller.rb | 7 +- .../data_sources/chorus_controller.rb | 49 ++ app/models/chorus_configuration.rb | 54 +- app/services/api_bretagne_service.rb | 38 +- config/initializers/attribute_types.rb | 11 + config/routes.rb | 3 + .../administrateurs/chorus_controller_spec.rb | 29 +- .../data_sources/chorus_controller_spec.rb | 54 ++ .../files/api_databretagne/centre-couts.json | 709 ++++++++++++++++++ .../files/api_databretagne/domaine-fonct.json | 609 +++++++++++++++ .../api_databretagne/ref-programmation.json | 609 +++++++++++++++ spec/models/chorus_configuration_spec.rb | 36 + 13 files changed, 2168 insertions(+), 60 deletions(-) create mode 100644 app/controllers/data_sources/chorus_controller.rb create mode 100644 config/initializers/attribute_types.rb create mode 100644 spec/controllers/data_sources/chorus_controller_spec.rb create mode 100644 spec/fixtures/files/api_databretagne/centre-couts.json create mode 100644 spec/fixtures/files/api_databretagne/domaine-fonct.json create mode 100644 spec/fixtures/files/api_databretagne/ref-programmation.json create mode 100644 spec/models/chorus_configuration_spec.rb diff --git a/app/components/procedure/chorus_form_component/chorus_form_component.html.haml b/app/components/procedure/chorus_form_component/chorus_form_component.html.haml index 6f48026ba..b6c875d9a 100644 --- a/app/components/procedure/chorus_form_component/chorus_form_component.html.haml +++ b/app/components/procedure/chorus_form_component/chorus_form_component.html.haml @@ -1,15 +1,11 @@ +- map_attribute_to_autocomplete_endpoint = { centre_de_coup: data_sources_search_centre_couts_path, domaine_fonctionnel: data_sources_search_domaine_fonct_path, referentiel_de_programmation: data_sources_search_ref_programmation_path } + = form_for([procedure, procedure.chorus_configuration],url: admin_procedure_chorus_path(procedure), method: :put) do |f| - - .fr-select-group - = f.label :centre_de_coup, class: 'fr-label' - = f.select :centre_de_coup, options_for_select(ChorusConfiguration.centre_de_coup_options, procedure.chorus_configuration.centre_de_coup), {}, class: 'fr-select' - - .fr-select-group - = f.label :domaine_fonctionnel, class: 'fr-label' - = f.select :domaine_fonctionnel, options_for_select(ChorusConfiguration.domaine_fonctionnel_options, procedure.chorus_configuration.domaine_fonctionnel), {}, class: 'fr-select' - - .fr-select-group - = f.label :referentiel_de_programmation, class: 'fr-label' - = f.select :referentiel_de_programmation, options_for_select(ChorusConfiguration.referentiel_de_programmation_options, procedure.chorus_configuration.referentiel_de_programmation), {}, class: 'fr-select' + - map_attribute_to_autocomplete_endpoint.map do |chorus_configuration_attribute, datasource_endpoint| + - label_class_name = "#{chorus_configuration_attribute}-label" + .fr-select-group + = f.label chorus_configuration_attribute, class: 'fr-label', id: label_class_name + = render Dsfr::ComboboxComponent.new form: f, name: :chorus_configuration_attribute, url: datasource_endpoint, value: procedure.chorus_configuration.format_displayed_value(chorus_configuration_attribute), id: chorus_configuration_attribute, class: 'fr-select', describedby: label_class_name do + = f.hidden_field chorus_configuration_attribute, data: { value_slot: 'data' }, value: procedure.chorus_configuration.format_hidden_value(chorus_configuration_attribute) = f.submit "Enregister", class: 'fr-btn' diff --git a/app/controllers/administrateurs/chorus_controller.rb b/app/controllers/administrateurs/chorus_controller.rb index e6b850392..1acd31a4d 100644 --- a/app/controllers/administrateurs/chorus_controller.rb +++ b/app/controllers/administrateurs/chorus_controller.rb @@ -21,8 +21,13 @@ module Administrateurs private + def search_params + params.permit(:q) + end + def configurations_params - params.require(:chorus_configuration).permit(:centre_de_coup, :domaine_fonctionnel, :referentiel_de_programmation) + params.require(:chorus_configuration) + .permit(:centre_de_coup, :domaine_fonctionnel, :referentiel_de_programmation) end end end diff --git a/app/controllers/data_sources/chorus_controller.rb b/app/controllers/data_sources/chorus_controller.rb new file mode 100644 index 000000000..7ee61e3b9 --- /dev/null +++ b/app/controllers/data_sources/chorus_controller.rb @@ -0,0 +1,49 @@ +class DataSources::ChorusController < ApplicationController + before_action :authenticate_administrateur! + + def search_domaine_fonct + @result = APIBretagneService.new.search_domaine_fonct(code_or_label: params[:q]) + result_json = @result.map do |item| + { + label: ChorusConfiguration.format_domaine_fonctionnel_label(item), + value: "#{item[:label]} - #{item[:code_programme]}", + data: item + } + end + render json: result_json + end + + def search_centre_couts + @result = APIBretagneService.new.search_centre_couts(code_or_label: params[:q]) + result_json = @result.map do |item| + { + label: ChorusConfiguration.format_domaine_fonctionnel_label(item), + value: "#{item[:label]} - #{item[:code_programme]}", + data: item + } + end + render json: result_json + end + + def search_ref_programmation + @result = APIBretagneService.new.search_ref_programmation(code_or_label: params[:q]) + result_json = @result.map do |item| + { + label: ChorusConfiguration.format_domaine_fonctionnel_label(item), + value: "#{item[:label]} - #{item[:code_programme]}", + data: item + } + end + render json: result_json + end + + # def search + # if params[:q].present? && params[:q].length > 3 + # response = Typhoeus.get("#{API_ADRESSE_URL}/search", params: { q: params[:q], limit: 10 }) + # result = JSON.parse(response.body, symbolize_names: true) + # render json: result[:features].map { { label: _1[:properties][:label], value: _1[:properties][:label] } } + # else + # render json: [] + # end + # end +end diff --git a/app/models/chorus_configuration.rb b/app/models/chorus_configuration.rb index cec9eb9a1..de2f1c792 100644 --- a/app/models/chorus_configuration.rb +++ b/app/models/chorus_configuration.rb @@ -2,23 +2,51 @@ class ChorusConfiguration include ActiveModel::Model include ActiveModel::Attributes - attribute :centre_de_coup, default: nil - attribute :domaine_fonctionnel, default: nil - attribute :referentiel_de_programmation, default: nil + attribute :centre_de_coup, :json, default: '{}' + attribute :domaine_fonctionnel, :json, default: '{}' + attribute :referentiel_de_programmation, :json, default: '{}' - validates :centre_de_coup, inclusion: { in: Proc.new { ChorusConfiguration.centre_de_coup_options } } - validates :domaine_fonctionnel, inclusion: { in: Proc.new { ChorusConfiguration.domaine_fonctionnel_options } } - validates :referentiel_de_programmation, inclusion: { in: Proc.new { ChorusConfiguration.referentiel_de_programmation_options } } - - def self.centre_de_coup_options - [1, 2, 3].map(&:to_s) + def format_displayed_value(attribute_name) + case attribute_name + when :centre_de_coup + ChorusConfiguration.format_centre_de_coup_label(centre_de_coup) + when :domaine_fonctionnel + ChorusConfiguration.format_domaine_fonctionnel_label(domaine_fonctionnel) + when :referentiel_de_programmation + ChorusConfiguration.format_ref_programmation_label(referentiel_de_programmation) + else + raise 'unknown attribute_name' + end end - def self.domaine_fonctionnel_options - [4, 5, 6].map(&:to_s) + def format_hidden_value(attribute_name) + case attribute_name + when :centre_de_coup + centre_de_coup.to_json + when :domaine_fonctionnel + domaine_fonctionnel.to_json + when :referentiel_de_programmation + referentiel_de_programmation.to_json + else + raise 'unknown attribute_name' + end end - def self.referentiel_de_programmation_options - [7, 8, 9].map(&:to_s) + def self.format_centre_de_coup_label(api_result) + return "" if api_result.blank? + api_result = api_result.symbolize_keys + "#{api_result[:description]} - #{api_result[:code]}" + end + + def self.format_domaine_fonctionnel_label(api_result) + return "" if api_result.blank? + api_result = api_result.symbolize_keys + "#{api_result[:label]} - #{api_result[:code]}" + end + + def self.format_ref_programmation_label(api_result) + return "" if api_result.blank? + api_result = api_result.symbolize_keys + "#{api_result[:label]} - #{api_result[:code]}" end end diff --git a/app/services/api_bretagne_service.rb b/app/services/api_bretagne_service.rb index ab40e0760..39edb2015 100644 --- a/app/services/api_bretagne_service.rb +++ b/app/services/api_bretagne_service.rb @@ -14,7 +14,17 @@ class APIBretagneService def search_domaine_fonct(code_or_label: "") url = build_url(ENDPOINTS.fetch('domaine-fonct')) - fetch_all_page(url:, code_or_label:) + fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] + end + + def search_centre_couts(code_or_label: "") + url = build_url(ENDPOINTS.fetch('centre-couts')) + fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] + end + + def search_ref_programmation(code_or_label: "") + url = build_url(ENDPOINTS.fetch('ref-programmation')) + fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] end private @@ -36,18 +46,22 @@ class APIBretagneService end end - def fetch_all_page(url:, code_or_label:) - first_page = fetch_page(url:, params: { page_number: 1, query: code_or_label }) - return [] if first_page.empty? + # QUESTION TECH, on a des API av 18k reponse potentielles, + # sur de l'autocomplete je suppose qu'on cherche pas a paginer le resultat. + # vous avez une autre idée en tête ? - total_pages = (first_page[:pageInfo][:totalRows].to_f / first_page[:pageInfo][:pageSize].to_f).ceil - all = first_page[:items] - (2..total_pages).map do |page_number| - page = fetch_page(url:, params: { page_number: }) - all.concat(page[:items]) - end - all - end + # def fetch_all_page(url:, code_or_label:) + # first_page = fetch_page(url:, params: { page_number: 1, query: code_or_label }) + # return [] if first_page.empty? + + # total_pages = (first_page[:pageInfo][:totalRows].to_f / first_page[:pageInfo][:pageSize].to_f).ceil + # all = first_page[:items] + # (2..total_pages).map do |page_number| + # page = fetch_page(url:, params: { page_number: }) + # all.concat(page[:items]) + # end + # all + # end def call(url:, params:) API::Client.new.(url:, params:, authorization_token:, method:) diff --git a/config/initializers/attribute_types.rb b/config/initializers/attribute_types.rb new file mode 100644 index 000000000..a487d607d --- /dev/null +++ b/config/initializers/attribute_types.rb @@ -0,0 +1,11 @@ +class JsonType < ActiveModel::Type::Value + def cast(value) + return nil if value.blank? + return value if value.is_a?(Hash) + JSON.parse(value) + rescue JSON::ParserError + {} + end +end + +ActiveModel::Type.register(:json, JsonType) diff --git a/config/routes.rb b/config/routes.rb index 21158ba68..2582ac224 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -223,6 +223,9 @@ Rails.application.routes.draw do namespace :data_sources do get :adresse, to: 'adresse#search', as: :data_source_adresse + get :search_domaine_fonct, to: 'chorus#search_domaine_fonct', as: :search_domaine_fonct + get :search_centre_couts, to: 'chorus#search_centre_couts', as: :search_centre_couts + get :search_ref_programmation, to: 'chorus#search_ref_programmation', as: :search_ref_programmation end # diff --git a/spec/controllers/administrateurs/chorus_controller_spec.rb b/spec/controllers/administrateurs/chorus_controller_spec.rb index 89b6d7278..30f12b1cd 100644 --- a/spec/controllers/administrateurs/chorus_controller_spec.rb +++ b/spec/controllers/administrateurs/chorus_controller_spec.rb @@ -52,11 +52,12 @@ describe Administrateurs::ChorusController, type: :controller do before { sign_in(admin.user) } context "valid payload" do + let(:centre_de_coup) { '{"code":"D00C8DX004","label":"Aumôniers+protestant","ville":null,"code_postal":null,"description":"Aumoniers+protestants"}' } + let(:domaine_fonctionnel) { '{"code":"0105-05-01","label":"Formation+des+élites+et+cadres+de+sécurité+et+de+défense","description":null,"code_programme":"105"}' } + let(:referentiel_de_programmation) { '{"code":"010101010101","label":"DOTATIONS+CARPA+AJ+ET+AUTRES+INTERVENTIONS","description":null,"code_programme":"101"}' } let(:chorus_configuration_params) do { - centre_de_coup: ChorusConfiguration.centre_de_coup_options.first, - domaine_fonctionnel: ChorusConfiguration.domaine_fonctionnel_options.first, - referentiel_de_programmation: ChorusConfiguration.referentiel_de_programmation_options.first + centre_de_coup:, domaine_fonctionnel:, referentiel_de_programmation:, } end @@ -65,25 +66,9 @@ describe Administrateurs::ChorusController, type: :controller do subject expect(flash[:notice]).to eq("La configuration Chorus a été mise à jour et prend immédiatement effet pour les nouveaux dossiers.") procedure.reload - expect(procedure.chorus_configuration.centre_de_coup).to eq(ChorusConfiguration.centre_de_coup_options.first) - expect(procedure.chorus_configuration.domaine_fonctionnel).to eq(ChorusConfiguration.domaine_fonctionnel_options.first) - expect(procedure.chorus_configuration.referentiel_de_programmation).to eq(ChorusConfiguration.referentiel_de_programmation_options.first) - end - end - - context "invalid payload" do - let(:chorus_configuration_params) do - { - centre_de_coup: 0 - } - end - - it { is_expected.to have_http_status(200) } - it 'updates params' do - subject - expect(flash[:notice]).to eq("Des erreurs empêchent la validation du connecteur chorus. Corrigez les erreurs") - procedure.reload - expect(procedure.chorus_configuration.centre_de_coup).to eq(nil) + expect(procedure.chorus_configuration.centre_de_coup).to eq(JSON.parse(centre_de_coup)) + expect(procedure.chorus_configuration.domaine_fonctionnel).to eq(JSON.parse(domaine_fonctionnel)) + expect(procedure.chorus_configuration.referentiel_de_programmation).to eq(JSON.parse(referentiel_de_programmation)) end end end diff --git a/spec/controllers/data_sources/chorus_controller_spec.rb b/spec/controllers/data_sources/chorus_controller_spec.rb new file mode 100644 index 000000000..241f133e4 --- /dev/null +++ b/spec/controllers/data_sources/chorus_controller_spec.rb @@ -0,0 +1,54 @@ +describe DataSources::ChorusController do + let(:administrateur) { create(:administrateur) } + + render_views + + before do + sign_in(administrateur.user) + end + + describe 'search_domaine_fonct' do + let(:mock_api_response) do + JSON.parse(Rails.root.join("spec/fixtures/files/api_databretagne/domaine-fonct.json").read)['items'] + end + + before do + allow_any_instance_of(APIBretagneService).to receive(:search_domaine_fonct).and_return(mock_api_response) + end + + it 'works' do + get :search_domaine_fonct, params: { q: "Dépenses" } + expect(response.parsed_body.size).to eq(mock_api_response.size) + end + end + + describe 'search_centre_couts' do + let(:mock_api_response) do + JSON.parse(Rails.root.join("spec/fixtures/files/api_databretagne/centre-couts.json").read)['items'] + end + + before do + allow_any_instance_of(APIBretagneService).to receive(:search_centre_couts).and_return(mock_api_response) + end + + it 'works' do + get :search_centre_couts, params: { q: "Dépenses" } + expect(response.parsed_body.size).to eq(mock_api_response.size) + end + end + + describe 'search_ref_programmation' do + let(:mock_api_response) do + JSON.parse(Rails.root.join("spec/fixtures/files/api_databretagne/ref-programmation.json").read)['items'] + end + + before do + allow_any_instance_of(APIBretagneService).to receive(:search_ref_programmation).and_return(mock_api_response) + end + + it 'works' do + get :search_ref_programmation, params: { q: "Dépenses" } + expect(response.parsed_body.size).to eq(mock_api_response.size) + end + end +end diff --git a/spec/fixtures/files/api_databretagne/centre-couts.json b/spec/fixtures/files/api_databretagne/centre-couts.json new file mode 100644 index 000000000..4e16d69af --- /dev/null +++ b/spec/fixtures/files/api_databretagne/centre-couts.json @@ -0,0 +1,709 @@ +{ + "items": [ + { + "ville": null, + "code": "#", + "description": null, + "label": null, + "code_postal": null + }, + { + "ville": "PARIS", + "code": "16GY991", + "description": "(R) Hors Armee de Terre", + "label": "(R)HORS ADT", + "code_postal": "75000" + }, + { + "ville": "Nice", + "code": "2", + "description": "Unites Csfa BA 943 Nice", + "label": "Unites Csfa BA 943", + "code_postal": "6000" + }, + { + "ville": null, + "code": "3070ATE012", + "description": "Centre de coût mutualisé de l’ATE 12", + "label": "PRF0ATE012", + "code_postal": null + }, + { + "ville": "PARIS", + "code": "AAIAC00075", + "description": "AC", + "label": "AC", + "code_postal": "75001" + }, + { + "ville": "PARIS", + "code": "AAIACNU075", + "description": "ACNUSA", + "label": "ACNUSA", + "code_postal": "75006" + }, + { + "ville": "PARIS", + "code": "AAIASN1075", + "description": "Autorite de Surete Nucleaire", + "label": "ASN", + "code_postal": "75012" + }, + { + "ville": "Paris 07", + "code": "AAICADA075", + "description": "CADA", + "label": "CADA", + "code_postal": "75700" + }, + { + "ville": "Paris 07", + "code": "AAICCNE075", + "description": "CCNE", + "label": "CCNE", + "code_postal": "75700" + }, + { + "ville": "Paris 07", + "code": "AAICCSD075", + "description": "CCSDN", + "label": "CCSDN", + "code_postal": "75700" + }, + { + "ville": "Paris 19", + "code": "AAICGLP075", + "description": "CGLPL", + "label": "CGLPL", + "code_postal": "75019" + }, + { + "ville": "Paris 07", + "code": "AAICNCD075", + "description": "CNCDH", + "label": "CNCDH", + "code_postal": "75700" + }, + { + "ville": "Paris 07", + "code": "AAICNCI075", + "description": "CNCIS", + "label": "CNCIS", + "code_postal": "75700" + }, + { + "ville": "PARIS", + "code": "AAICNDP075", + "description": "Commission Nationale du Débat Public", + "label": "CNDP", + "code_postal": "75007" + }, + { + "ville": "Paris 07", + "code": "AAICNDS075", + "description": "CNDS", + "label": "CNDS", + "code_postal": "75700" + }, + { + "ville": "Paris cedex 15", + "code": "AAIRCEP075", + "description": "ARCEP", + "label": "ARCEP", + "code_postal": "75730" + }, + { + "ville": "Paris", + "code": "AAIRE00075", + "description": "CRE commun", + "label": "CRE", + "code_postal": "75379" + }, + { + "ville": null, + "code": "AAIRE01075", + "description": "CRE-PRESIDENT", + "label": "CRE-PRESIDENT", + "code_postal": null + }, + { + "ville": "Paris", + "code": "AAIRE02075", + "description": "CRE Collège/DG", + "label": "CRE01 Collège/DG", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE03075", + "description": "CRE DRH", + "label": "CRE02 DRH", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE04075", + "description": "CRE-Direction des Réseaux", + "label": "CRE-DR", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE05075", + "description": "CRE-Secrétaire Général", + "label": "CRE-SG", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE06075", + "description": "CRE-Dir Dév Marchés et Transition Energ", + "label": "CRE-DDMTE", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE07075", + "description": "CRE DSMG", + "label": "CRE DSMG", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE08075", + "description": "CRE-Direction des Affaires Juridiques", + "label": "CRE-DAJ", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE09075", + "description": "CRE-Dir affaires europ, inter et coop", + "label": "CRE-DAEIC", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE10075", + "description": "CRE - DCRI Comm et Relations Instit", + "label": "CRE - DCRI", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE11075", + "description": "CRE PAYE", + "label": "CRE10 PAYE", + "code_postal": "75379" + }, + { + "ville": null, + "code": "AAIRE12075", + "description": "CRE-COMMISSAIRE1", + "label": "CRE-COMMISSAIRE1", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE13075", + "description": "CRE-COMMISSAIRE2", + "label": "CRE-COMMISSAIRE2", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE14075", + "description": "CRE-COMMISSAIRE3", + "label": "CRE-COMMISSAIRE3", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE15075", + "description": "CRE-COMMISSAIRE4", + "label": "CRE-COMMISSAIRE4", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE16075", + "description": "CRE-COMMISSAIRE5", + "label": "CRE-COMMISSAIRE5", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE17075", + "description": "CRE-Service Prospective et Innovation", + "label": "CRE-SPI", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE18075", + "description": "CRE-CS2", + "label": "CRE-CS2", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE19075", + "description": "CRE-RI", + "label": "CRE-RI", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE20075", + "description": "CRE-AUTRES", + "label": "CRE-AUTRES", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE21075", + "description": "CRE-CORDIS", + "label": "CRE-CORDIS", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE22075", + "description": "CRE-SIGR", + "label": "CRE-SIGR", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE23075", + "description": "CRE-Direction des Affaires Financières", + "label": "CRE-DAF", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE24075", + "description": "CRE-SGS", + "label": "CRE-SGS", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE25075", + "description": "CRE-ACTION SOCIALE", + "label": "CRE-ACTION SOCIALE", + "code_postal": null + }, + { + "ville": null, + "code": "AAIRE26075", + "description": "CRE-FONCTIONNEMENT COURANT", + "label": "CRE-FONCTIONNEMENT", + "code_postal": null + }, + { + "ville": "Paris", + "code": "AAIRE27075", + "description": "CRE - STP Service Transfo et Projets", + "label": "CRE - STP", + "code_postal": "75379" + }, + { + "ville": "Paris", + "code": "AAIRE28075", + "description": "CRE-DAEF", + "label": "CRE-DAEF", + "code_postal": "75379" + }, + { + "ville": null, + "code": "AAIRE75ASP", + "description": "CRE Contentieux MESSER", + "label": "CRE Cont MESSER", + "code_postal": null + }, + { + "ville": null, + "code": "ACCELER001", + "description": "Accélérateur TE2", + "label": "Accélérat TE2", + "code_postal": null + }, + { + "ville": "PARIS", + "code": "ACI0DTA075", + "description": "Direction du Transport Aerien", + "label": "DTA", + "code_postal": "75720" + }, + { + "ville": "ST PIERRE ET MIQUELON", + "code": "ACIAERO975", + "description": "SAC SPM Aérogare", + "label": "SAC SPM Aérogare", + "code_postal": "97500" + }, + { + "ville": "NOUMEA", + "code": "ACIDC98098", + "description": "Directionn de l'Aviation Civile NC", + "label": "DAC NC", + "code_postal": "98845" + }, + { + "ville": "FORT-DE-FRANCE CEDEX", + "code": "ACIDCA2972", + "description": "DSAC ANTILLES-GUYANE", + "label": "DSAC ANTILLES-GUYANE", + "code_postal": "97262" + }, + { + "ville": "SAINT-DENIS MESSAG CEDEX 9", + "code": "ACIDCA4974", + "description": "DSAC OCEAN INDIEN REUNION MAYOTTE", + "label": "DSAC OI REUNION MAYO", + "code_postal": "97408" + }, + { + "ville": "SAINT-PIERRE-ET-MIQUELON", + "code": "ACIDCA5975", + "description": "SAC SAINT-PIERRE-ET-MIQUELON", + "label": "SAC SPMI", + "code_postal": "97500" + }, + { + "ville": "UVEA-WALLIS", + "code": "ACIDCB6986", + "description": "SAC WALLIS FUTUNA", + "label": "SAC WALLIS FUTUNA", + "code_postal": "98600" + }, + { + "ville": "FAA-A TAHITI", + "code": "ACIDCB7987", + "description": "SAC POLYNESIE", + "label": "SAC POLYNESIE", + "code_postal": "98702" + }, + { + "ville": "PARIS", + "code": "ACIDSAC075", + "description": "Dir de la Sécurité de l Aviation Civile", + "label": "DSAC", + "code_postal": "75720" + }, + { + "ville": "PARIS", + "code": "ACIDSNA075", + "description": "DSNA", + "label": "DSNA", + "code_postal": "75015" + }, + { + "ville": "PARIS", + "code": "ACIOCAC075", + "description": "Direction Generale de l'Aviation Civile", + "label": "DGAC", + "code_postal": "75720" + }, + { + "ville": "ST PIERRE ET MIQUELON", + "code": "ACISLIA975", + "description": "SAC SPM SSLIA", + "label": "SAC SPM SSLIA", + "code_postal": "97500" + }, + { + "ville": "PARIS", + "code": "ACISNIA075", + "description": "Service National d'Ingénierie Aéro", + "label": "SNIA", + "code_postal": "75700" + }, + { + "ville": "VBONNEUIL SUR MARNE CEDEX", + "code": "ACISTAC094", + "description": "Service Technique de l'Aviation civile", + "label": "STAC", + "code_postal": "94381" + }, + { + "ville": "PARIS", + "code": "ADCACTF075", + "description": "Serv. gest. bien ADC", + "label": "CC S. gest. bien ADC", + "code_postal": "75105" + }, + { + "ville": "PARIS", + "code": "ADCAIAC075", + "description": "BAIAC", + "label": "CC BAIAC", + "code_postal": "75105" + }, + { + "ville": null, + "code": "ADCAISC075", + "description": "Centre de cout BAIATSC", + "label": "BAIATSC", + "code_postal": null + }, + { + "ville": null, + "code": "ADCAJCX075", + "description": "CC DLPAJ suivi du contentieux", + "label": "CC DLPAJ CTX", + "code_postal": null + }, + { + "ville": "PARIS", + "code": "ADCBAFE075", + "description": "BAFED", + "label": "CC BAFED", + "code_postal": "75105" + }, + { + "ville": null, + "code": "ADCBCAM075", + "description": "BCAM", + "label": "BCAM", + "code_postal": null + }, + { + "ville": "PARIS", + "code": "ADCBEEP075", + "description": "BEEP", + "label": "CC BEEP", + "code_postal": "75008" + }, + { + "ville": "PARIS", + "code": "ADCBFAC075", + "description": "BBFAC", + "label": "CC BBFAC", + "code_postal": "75105" + }, + { + "ville": "PARIS", + "code": "ADCBFPP075", + "description": "BFPP", + "label": "CC BFPP", + "code_postal": "75105" + }, + { + "ville": "PARIS", + "code": "ADCBGCP075", + "description": "BGCPAC", + "label": "CC BGCPAC", + "code_postal": "75008" + }, + { + "ville": null, + "code": "ADCBMAT075", + "description": "BMAT", + "label": "BMAT", + "code_postal": null + }, + { + "ville": "PARIS", + "code": "ADCBPAI075", + "description": "BPAI", + "label": "CC BPAI", + "code_postal": "75105" + }, + { + "ville": null, + "code": "ADCBQPA075", + "description": "BQPPAT", + "label": "BQPPAT", + "code_postal": null + }, + { + "ville": "PARIS", + "code": "ADCBRH1075", + "description": "BRH (T2)", + "label": "CC DRH (T2)", + "code_postal": "75105" + }, + { + "ville": "PARIS", + "code": "ADCCBMI075", + "description": "BBFAC CABMIN", + "label": "CC BBFAC CABMIN", + "code_postal": "75105" + }, + { + "ville": "PARIS", + "code": "ADCCBOM075", + "description": "BBFAC CABOM", + "label": "CC BBFAC CABOM", + "code_postal": "75105" + }, + { + "ville": null, + "code": "ADCCCSP075", + "description": "BBFAC JAS-CCSP", + "label": "BBFAC JAS-CCSP", + "code_postal": null + }, + { + "ville": "PARIS cedex 17", + "code": "ADCCNCC075", + "description": "CNCCFP", + "label": "CC CNCCFP", + "code_postal": "75176" + }, + { + "ville": null, + "code": "ADCCOMM075", + "description": "Dép. T2 agents GN DICOM", + "label": "T2 agents GN DICOM", + "code_postal": null + }, + { + "ville": null, + "code": "ADCCPFI075", + "description": "Dép. T2 agents CPFi DEPAFI", + "label": "T2 agents GN CPFi", + "code_postal": null + }, + { + "ville": "AGEN CEDEX", + "code": "ADCCSAI075", + "description": "Centre de coût SAILMI", + "label": "CC SAILMI", + "code_postal": "47004" + }, + { + "ville": null, + "code": "ADCCSAT075", + "description": "DEPENSES FONCTIONNEMENT PREFETS CSATE", + "label": "DEP FC PREFETS CSATE", + "code_postal": null + }, + { + "ville": "PARIS", + "code": "ADCCSER075", + "description": "CC FNPC", + "label": "CC FNPC", + "code_postal": "75015" + }, + { + "ville": "PARIS", + "code": "ADCDDE1075", + "description": "CC DDE ADC DEGEOM", + "label": "CC DDE ADC DEGEOM", + "code_postal": "75007" + }, + { + "ville": "AGEN CEDEX", + "code": "ADCDDI0075", + "description": "MI 0333 DDI", + "label": "MI 0333 ADCDDI0075", + "code_postal": "47004" + }, + { + "ville": "PARIS", + "code": "ADCDGCL075", + "description": "DGCL", + "label": "DGCL", + "code_postal": "75000" + }, + { + "ville": "PARIS", + "code": "ADCDGOM075", + "description": "DEGEOM (T2)", + "label": "CC DEGEOM (T2)", + "code_postal": "75105" + }, + { + "ville": "PARIS", + "code": "ADCDICO075", + "description": "DICOM", + "label": "CC DICOM", + "code_postal": "75008" + }, + { + "ville": "PARIS", + "code": "ADCDLPA075", + "description": "DLPAJ", + "label": "CC DLPAJ", + "code_postal": "75008" + }, + { + "ville": "PARIS", + "code": "ADCDMA1075", + "description": "DMAT", + "label": "DMAT", + "code_postal": "75008" + }, + { + "ville": "AGEN CEDEX", + "code": "ADCDNUM075", + "description": "Centre de cout DNUM", + "label": "CC DNUM", + "code_postal": "47004" + }, + { + "ville": null, + "code": "ADCDPSU075", + "description": "Projet site unique", + "label": "Projet site unique", + "code_postal": null + }, + { + "ville": "PARIS", + "code": "ADCDPTS075", + "description": "CC FAETON", + "label": "CC FAETON", + "code_postal": "75015" + }, + { + "ville": "PARIS", + "code": "ADCDSIC075", + "description": "CC DSIC", + "label": "CC DSIC", + "code_postal": "75015" + }, + { + "ville": "PARIS", + "code": "ADCELEG075", + "description": "Vacations Elections Personnel GN", + "label": "CC Vac Elec GN", + "code_postal": "75008" + }, + { + "ville": "PARIS", + "code": "ADCELEP075", + "description": "Vacations Elections Personnel PN", + "label": "CC Vac Elec PN", + "code_postal": "75008" + }, + { + "ville": "PARIS", + "code": "ADCELEX075", + "description": "Vacations Elections Personnel Autre", + "label": "CC Vac Elec Autre", + "code_postal": "75008" + }, + { + "ville": null, + "code": "ADCEM01075", + "description": "BBFAC CAB SG", + "label": "BBFAC CAB SG", + "code_postal": null + }, + { + "ville": null, + "code": "ADCEM02075", + "description": "BBFAC SHFD SG", + "label": "BBFAC SHFD SG", + "code_postal": null + } + ], + "pageInfo": { + "totalRows": 18988, + "page": 1, + "pageSize": 100 + } +} \ No newline at end of file diff --git a/spec/fixtures/files/api_databretagne/domaine-fonct.json b/spec/fixtures/files/api_databretagne/domaine-fonct.json new file mode 100644 index 000000000..993aa85d0 --- /dev/null +++ b/spec/fixtures/files/api_databretagne/domaine-fonct.json @@ -0,0 +1,609 @@ +{ + "items": [ + { + "code_programme": "101", + "code": "0101", + "description": null, + "label": "Accès au droit et à la justice" + }, + { + "code_programme": "101", + "code": "0101-01", + "description": null, + "label": "Aide juridictionnelle" + }, + { + "code_programme": "101", + "code": "0101-01-03", + "description": null, + "label": null + }, + { + "code_programme": "101", + "code": "0101-02", + "description": null, + "label": "Développement de l'accès au droit et du réseau judiciaire de proximité" + }, + { + "code_programme": "101", + "code": "0101-03", + "description": null, + "label": "Aide aux victimes" + }, + { + "code_programme": "101", + "code": "0101-04", + "description": null, + "label": "Médiation familiale et espaces de rencontre" + }, + { + "code_programme": "101", + "code": "0101-05", + "description": null, + "label": "Indemnisation des avoués" + }, + { + "code_programme": "102", + "code": "0102", + "description": null, + "label": "Accès et retour à l'emploi" + }, + { + "code_programme": "102", + "code": "0102-01", + "description": null, + "label": "Amélioration de l'efficacité du service public de l'emploi" + }, + { + "code_programme": "102", + "code": "0102-01-01", + "description": null, + "label": "Indemnisation des demandeurs d'emploi" + }, + { + "code_programme": "102", + "code": "0102-01-02", + "description": null, + "label": "Coordination du service public de l'emploi" + }, + { + "code_programme": "102", + "code": "0102-02", + "description": null, + "label": "Amélioration des dispositifs en faveur de l'emploi des personnes les plus éloignées du marché du travail" + }, + { + "code_programme": "102", + "code": "0102-02-01", + "description": null, + "label": "Insertion dans l'emploi au moyen de contrats aidés" + }, + { + "code_programme": "102", + "code": "0102-02-02", + "description": null, + "label": "Accompagnement des publics les plus en difficultés" + }, + { + "code_programme": "102", + "code": "0102-03", + "description": null, + "label": "Plan d'investissement des compétences" + }, + { + "code_programme": "102", + "code": "0102-04", + "description": null, + "label": "AideExcepContPro" + }, + { + "code_programme": "103", + "code": "0103", + "description": null, + "label": "Accompagnement des mutations économiques et développement de l'emploi" + }, + { + "code_programme": "103", + "code": "0103-01", + "description": null, + "label": "Anticipation et accompagnement des conséquences des mutations économiques sur l'emploi" + }, + { + "code_programme": "103", + "code": "0103-01-01", + "description": null, + "label": "Développement de l'emploi en TPE-PME" + }, + { + "code_programme": "103", + "code": "0103-01-02", + "description": null, + "label": "Implication des branches et des entreprises dans la prévention du licenciement et le reclassement des salariés" + }, + { + "code_programme": "103", + "code": "0103-02", + "description": null, + "label": "Amélioration de l'insertion dans l'emploi par l'adaptation des qualifications et la reconnaissance des compétences" + }, + { + "code_programme": "103", + "code": "0103-02-03", + "description": null, + "label": "Reconnaissance des compétences acquises par les personnes" + }, + { + "code_programme": "103", + "code": "0103-02-04", + "description": null, + "label": "Amélioration de l'accès à la qualification par le développement de l'alternance et de la certification" + }, + { + "code_programme": "103", + "code": "0103-03", + "description": null, + "label": "Développement de l'emploi" + }, + { + "code_programme": "103", + "code": "0103-03-01", + "description": null, + "label": "Baisse du coût du travail pour faciliter le développement de territoires et de secteurs à forts potentiels d'emploi" + }, + { + "code_programme": "103", + "code": "0103-03-02", + "description": null, + "label": "Promotion de l'activité" + }, + { + "code_programme": "103", + "code": "0103-03-03", + "description": null, + "label": "Aide à l'embauche" + }, + { + "code_programme": "103", + "code": "0103-04", + "description": null, + "label": "Plan d'investissement des compétences" + }, + { + "code_programme": "103", + "code": "0103-05", + "description": null, + "label": "Aide except apprentis" + }, + { + "code_programme": "104", + "code": "0104", + "description": null, + "label": "Intégration et accès à la nationalité française" + }, + { + "code_programme": "104", + "code": "0104-11", + "description": null, + "label": "Accueil des étrangers primo arrivants" + }, + { + "code_programme": "104", + "code": "0104-11-02", + "description": null, + "label": "Accueil des étrangers primo-arrivants - Dépenses sur crédits nationaux" + }, + { + "code_programme": "104", + "code": "0104-11-04", + "description": null, + "label": "Accueil des étrangers primo-arrivants - Dépenses à la charge du fonds européen pour l'intégration (FEI)" + }, + { + "code_programme": "104", + "code": "0104-11-05", + "description": null, + "label": "Accueil des étrangers primo-arrivants - Dépenses à la charge du Fonds européen asile, migration et intégration" + }, + { + "code_programme": "104", + "code": "0104-12", + "description": null, + "label": "Actions d'accompagnement des étrangers en situation régulière" + }, + { + "code_programme": "104", + "code": "0104-12-02", + "description": null, + "label": "Actions d'accompagnement des primo-arrivants - Dépenses sur crédits nationaux" + }, + { + "code_programme": "104", + "code": "0104-12-08", + "description": null, + "label": "Actions d'accompagnement des primo-arrivants. Dépenses à la charge du Fonds européen pour l'intégration (FEI)" + }, + { + "code_programme": "104", + "code": "0104-12-14", + "description": null, + "label": "Actions d'accompagnement des étrangers. Dépenses à la charge du Fonds européen asile, migration et intégration (FAMI)" + }, + { + "code_programme": "104", + "code": "0104-12-15", + "description": null, + "label": "Actions Réfugiés" + }, + { + "code_programme": "104", + "code": "0104-14", + "description": null, + "label": "Accès à la nationalité française" + }, + { + "code_programme": "104", + "code": "0104-15", + "description": null, + "label": "Accompagnement des réfugiés" + }, + { + "code_programme": "104", + "code": "0104-15-01", + "description": null, + "label": "Centres provisoires d'hébergement des réfugiés" + }, + { + "code_programme": "104", + "code": "0104-15-04", + "description": null, + "label": "Aide et accompagnement des réfugiés. Dépenses à la charge du Fonds Européen pour les réfugiés (FER)" + }, + { + "code_programme": "104", + "code": "0104-15-12", + "description": null, + "label": "Aide et accompagnement des réfugiés - Dépenses sur crédits nationaux" + }, + { + "code_programme": "104", + "code": "0104-15-13", + "description": null, + "label": "Aide et accompagnement des réfugiés - dépenses à la charge du Fonds européen asile, migration et intégration (FAMI)" + }, + { + "code_programme": "104", + "code": "0104-16", + "description": null, + "label": "Accompagnement du plan de traitement des foyers de travailleurs migrants" + }, + { + "code_programme": "104", + "code": "0104-16-01", + "description": null, + "label": "Foyers de travailleurs migrants - Dépenses sur crédits nationaux" + }, + { + "code_programme": "105", + "code": "0105", + "description": null, + "label": "Action de la France en Europe et dans le monde" + }, + { + "code_programme": "105", + "code": "0105-01", + "description": null, + "label": "Coordination de l'action diplomatique" + }, + { + "code_programme": "105", + "code": "0105-01-01", + "description": null, + "label": "Etat major : cabinets" + }, + { + "code_programme": "105", + "code": "0105-01-08", + "description": null, + "label": "Etat major : logistique de niveau chef d'Etat ou de gouvernement" + }, + { + "code_programme": "105", + "code": "0105-01-09", + "description": null, + "label": "Etat-major : affaires politiques" + }, + { + "code_programme": "105", + "code": "0105-01-15", + "description": null, + "label": "Personnel concourant à l'action coordination de l'action diplomatique" + }, + { + "code_programme": "105", + "code": "0105-01-16", + "description": null, + "label": "Sécurité des communautés françaises" + }, + { + "code_programme": "105", + "code": "0105-02", + "description": null, + "label": "Action européenne" + }, + { + "code_programme": "105", + "code": "0105-02-01", + "description": null, + "label": "Moyens de fonctionnement : services d'administration centrale" + }, + { + "code_programme": "105", + "code": "0105-02-07", + "description": null, + "label": "Dépenses d'intervention et de communication du ministre chargé des affaires européennes" + }, + { + "code_programme": "105", + "code": "0105-02-08", + "description": null, + "label": "Contributions aux organisations européennes" + }, + { + "code_programme": "105", + "code": "0105-02-11", + "description": null, + "label": "Personnel concourant à l'action action européenne" + }, + { + "code_programme": "105", + "code": "0105-04", + "description": null, + "label": "Contributions internationales" + }, + { + "code_programme": "105", + "code": "0105-04-01", + "description": null, + "label": "Opérations de maintien de la paix : participations obligatoires aux opérations de maintien de la paix des Nations Unies (en devises)" + }, + { + "code_programme": "105", + "code": "0105-04-02", + "description": null, + "label": "Contributions en euros aux organisations internationales" + }, + { + "code_programme": "105", + "code": "0105-04-05", + "description": null, + "label": "Contributions en devises aux organisations internationales" + }, + { + "code_programme": "105", + "code": "0105-05", + "description": null, + "label": "Coopération de sécurité et de défense" + }, + { + "code_programme": "105", + "code": "0105-05-01", + "description": null, + "label": "Formation des élites et cadres de sécurité et de défense" + }, + { + "code_programme": "105", + "code": "0105-05-02", + "description": null, + "label": "Ingénierie de sécurité et de défense" + }, + { + "code_programme": "105", + "code": "0105-05-03", + "description": null, + "label": "Fonctions de direction et soutien en France et étranger" + }, + { + "code_programme": "105", + "code": "0105-05-04", + "description": null, + "label": "Personnel concourant à l'action coopération de sécurité et de défense" + }, + { + "code_programme": "105", + "code": "0105-06", + "description": null, + "label": "Soutien" + }, + { + "code_programme": "105", + "code": "0105-06-06", + "description": null, + "label": "Ressources humaines" + }, + { + "code_programme": "105", + "code": "0105-06-08", + "description": null, + "label": "Systèmes d'information et de télécommunication" + }, + { + "code_programme": "105", + "code": "0105-06-10", + "description": null, + "label": "Logistique diplomatique" + }, + { + "code_programme": "105", + "code": "0105-06-11", + "description": null, + "label": "Politique immobilière" + }, + { + "code_programme": "105", + "code": "0105-06-12", + "description": null, + "label": "Sécurité diplomatique" + }, + { + "code_programme": "105", + "code": "0105-06-14", + "description": null, + "label": "Personnel concourant à l'action soutien" + }, + { + "code_programme": "105", + "code": "0105-07", + "description": null, + "label": "Réseau diplomatique" + }, + { + "code_programme": "105", + "code": "0105-07-01", + "description": null, + "label": "Réseau diplomatique : postes à l'étranger - dépenses de structures du ministère des Affaires étrangères" + }, + { + "code_programme": "105", + "code": "0105-07-02", + "description": null, + "label": "Personnel du Ministère des Affaires étrangères et européennes concourant à l'action réseau diplomatique" + }, + { + "code_programme": "105", + "code": "0105-07-03", + "description": null, + "label": "Réseau diplomatique : postes à l'étranger - dépenses de structures hors ministère des Affaires étrangères" + }, + { + "code_programme": "105", + "code": "0105-07-04", + "description": null, + "label": "Personnel hors Ministère des Affaires étrangères et européennes, concourant à l'action réseau diplomatique" + }, + { + "code_programme": "105", + "code": "0105-99", + "description": null, + "label": "Dépenses de personnel à reventiler entre les actions du programme" + }, + { + "code_programme": "107", + "code": "0107", + "description": null, + "label": "Administration pénitentiaire" + }, + { + "code_programme": "107", + "code": "0107-01", + "description": null, + "label": "Garde et contrôle des personnes placées sous main de justice" + }, + { + "code_programme": "107", + "code": "0107-02", + "description": null, + "label": "Accueil et accompagnement des personnes placées sous main de justice" + }, + { + "code_programme": "107", + "code": "0107-04", + "description": null, + "label": "Soutien et formation" + }, + { + "code_programme": "107", + "code": "0107-99", + "description": null, + "label": "Dépenses de personnel du programme à reventiler" + }, + { + "code_programme": "109", + "code": "0109", + "description": null, + "label": "Aide à l'accès au logement" + }, + { + "code_programme": "109", + "code": "0109-01", + "description": null, + "label": "Aides personnelles" + }, + { + "code_programme": "109", + "code": "0109-02", + "description": null, + "label": "Information relative au logement et accompagnement des publics en difficulté" + }, + { + "code_programme": "109", + "code": "0109-02-01", + "description": null, + "label": "Information sur le logement (ANIL et ADIL)" + }, + { + "code_programme": "109", + "code": "0109-02-02", + "description": null, + "label": "Autres interventions de l'Etat en faveur du logement des publics en difficulté" + }, + { + "code_programme": "109", + "code": "0109-03", + "description": null, + "label": "Sécurisation des risques locatifs" + }, + { + "code_programme": "109", + "code": "0109-03-01", + "description": null, + "label": "Garantie des risques locatifs" + }, + { + "code_programme": "110", + "code": "0110", + "description": null, + "label": "Aide économique et financière au développement" + }, + { + "code_programme": "110", + "code": "0110-01", + "description": null, + "label": "Aide économique et financière multilatérale" + }, + { + "code_programme": "110", + "code": "0110-01-10", + "description": null, + "label": "Association internationale de développement" + }, + { + "code_programme": "110", + "code": "0110-01-13", + "description": null, + "label": "Groupe de la banque mondiale" + }, + { + "code_programme": "110", + "code": "0110-01-16", + "description": null, + "label": "Coopération technique" + }, + { + "code_programme": "110", + "code": "0110-01-18", + "description": null, + "label": "Fonds africain de développement" + }, + { + "code_programme": "110", + "code": "0110-01-24", + "description": null, + "label": "Fonds asiatique de développement" + } + ], + "pageInfo": { + "totalRows": 3066, + "page": 1, + "pageSize": 100 + } +} \ No newline at end of file diff --git a/spec/fixtures/files/api_databretagne/ref-programmation.json b/spec/fixtures/files/api_databretagne/ref-programmation.json new file mode 100644 index 000000000..30f02b27e --- /dev/null +++ b/spec/fixtures/files/api_databretagne/ref-programmation.json @@ -0,0 +1,609 @@ +{ + "items": [ + { + "code_programme": "101", + "code": "010101010101", + "description": null, + "label": "DOTATIONS CARPA AJ ET AUTRES INTERVENTIONS" + }, + { + "code_programme": "101", + "code": "010101010106", + "description": null, + "label": "RETRIBUER AVOCATS CE CCASS MISSIONS AJ" + }, + { + "code_programme": "101", + "code": "010101010113", + "description": null, + "label": "RETRIBUER HUISSIERS" + }, + { + "code_programme": "101", + "code": "010101010114", + "description": null, + "label": "AUTRES HONOR FRAIS ACTES PROCED ENQUETES TRADUCT" + }, + { + "code_programme": "101", + "code": "010101010115", + "description": null, + "label": "RETRIBUER EXPERTS MISSIONS AJ" + }, + { + "code_programme": "101", + "code": "010101010116", + "description": null, + "label": "RETRIBUER MEDIATEURS" + }, + { + "code_programme": "101", + "code": "010101010117", + "description": null, + "label": "DOTATION CONTRACTUALISATION" + }, + { + "code_programme": "101", + "code": "010101010118", + "description": null, + "label": "CONDUITE CHANGEMENT AIDE JURIDICTIONNELLE" + }, + { + "code_programme": "101", + "code": "010101040101", + "description": null, + "label": "VERSER UNE SUBVENTION EN FAVEUR DES CDAD" + }, + { + "code_programme": "101", + "code": "010101040103", + "description": null, + "label": "MAISONS DE JUSTICE ET DU DROIT" + }, + { + "code_programme": "101", + "code": "010101040104", + "description": null, + "label": "VERSER UNE SUBV ASSOC FED AD" + }, + { + "code_programme": "101", + "code": "010101050105", + "description": null, + "label": "MEDIATION FAMILIALE" + }, + { + "code_programme": "101", + "code": "010101050106", + "description": null, + "label": "ESPACES RENCONTRE" + }, + { + "code_programme": "101", + "code": "010101050107", + "description": null, + "label": "MEDIATION - HORS MEDIATION FAMILIALE" + }, + { + "code_programme": "101", + "code": "010101060102", + "description": null, + "label": "DOTATION AIDE AUX VICTIMES HORS BAV" + }, + { + "code_programme": "101", + "code": "010101060105", + "description": null, + "label": "ASSISTANCE TELEPH SITE INTERNET AV TGD" + }, + { + "code_programme": "101", + "code": "010101060106", + "description": null, + "label": "CREATION SOUTIEN EQ BUREAUX AIDE VICTIMES" + }, + { + "code_programme": "101", + "code": "010101070101", + "description": null, + "label": "CONTRIBUTION AU FIDA" + }, + { + "code_programme": "102", + "code": "010200000101", + "description": null, + "label": "Allocation de solidarité spécifique (ASS)" + }, + { + "code_programme": "102", + "code": "010200000102", + "description": null, + "label": "Contribution exceptionnelle de solidarité - FDS" + }, + { + "code_programme": "102", + "code": "010200000202", + "description": null, + "label": "Allocations complémentaires" + }, + { + "code_programme": "102", + "code": "010200000203", + "description": null, + "label": "Aide au pouvoir d'achat des demandeurs d'emploi" + }, + { + "code_programme": "102", + "code": "010200000301", + "description": null, + "label": "Allocations temporaires d'attente (ATA)" + }, + { + "code_programme": "102", + "code": "010200000304", + "description": null, + "label": "Intermittents" + }, + { + "code_programme": "102", + "code": "010200000702", + "description": null, + "label": "Maisons de l'emploi" + }, + { + "code_programme": "102", + "code": "010200000703", + "description": null, + "label": "Maison de l'emploi - investissement" + }, + { + "code_programme": "102", + "code": "010200000704", + "description": null, + "label": "Clauses sociales" + }, + { + "code_programme": "102", + "code": "010200000801", + "description": null, + "label": "Pôle emploi" + }, + { + "code_programme": "102", + "code": "010200001005", + "description": null, + "label": "Contrats initiative Emploi (CIE)-Marchand" + }, + { + "code_programme": "102", + "code": "010200001006", + "description": null, + "label": "Parcours Emploi Compétences (PEC)-CAE-Non Marchand" + }, + { + "code_programme": "102", + "code": "010200001007", + "description": null, + "label": "Emplois d'avenir" + }, + { + "code_programme": "102", + "code": "010200001101", + "description": null, + "label": "Chantier de développement local (Mayotte)" + }, + { + "code_programme": "102", + "code": "010200001102", + "description": null, + "label": "Congé solidarité (secteur marchand - outre mer)" + }, + { + "code_programme": "102", + "code": "010200001103", + "description": null, + "label": "Contrats d'accès à l'emploi DOM" + }, + { + "code_programme": "102", + "code": "010200001106", + "description": null, + "label": "Contrats emploi jeune et consolidation" + }, + { + "code_programme": "102", + "code": "010200001107", + "description": null, + "label": "Contrats emploi solidarité" + }, + { + "code_programme": "102", + "code": "010200001109", + "description": null, + "label": "Stage d'insertion et de formation professionnelle" + }, + { + "code_programme": "102", + "code": "010200001201", + "description": null, + "label": "Contrats d'accompagnement dans l'emploi (CAE) DOM" + }, + { + "code_programme": "102", + "code": "010200001210", + "description": null, + "label": "CAE, CAV" + }, + { + "code_programme": "102", + "code": "010200001301", + "description": null, + "label": "Agence Services et Paiement (ASP)-Frais de gestion" + }, + { + "code_programme": "102", + "code": "010200001302", + "description": null, + "label": "ASP-Dotation d'investissement" + }, + { + "code_programme": "102", + "code": "010200001401", + "description": null, + "label": "Contrat d'autonomie pour les jeunes des quartiers" + }, + { + "code_programme": "102", + "code": "010200001501", + "description": null, + "label": "Entreprises adaptées-Aides au poste classiques" + }, + { + "code_programme": "102", + "code": "010200001503", + "description": null, + "label": "Programmes Régionaux d'insertion des TH (PRITH)" + }, + { + "code_programme": "102", + "code": "010200001504", + "description": null, + "label": "PROG Ins. travailleurs hand. CPER" + }, + { + "code_programme": "102", + "code": "010200001505", + "description": null, + "label": "Subventions aux entreprises adaptées" + }, + { + "code_programme": "102", + "code": "010200001601", + "description": null, + "label": "IAE-Associations intermédiaires(AI)-AAP classiques" + }, + { + "code_programme": "102", + "code": "010200001602", + "description": null, + "label": "IAE-Chantiers d'insertion (ACI)-AAP classiques" + }, + { + "code_programme": "102", + "code": "010200001603", + "description": null, + "label": "IAE- Entreprises d'insertion (EI)-AAP classiques" + }, + { + "code_programme": "102", + "code": "010200001604", + "description": null, + "label": "Exos-Structures agréées Réinsertion Pro" + }, + { + "code_programme": "102", + "code": "010200001605", + "description": null, + "label": "IAE-Fonds départemental d'insertion (FDI)" + }, + { + "code_programme": "102", + "code": "010200001607", + "description": null, + "label": "Réserves parlementaires" + }, + { + "code_programme": "102", + "code": "010200001608", + "description": null, + "label": "ETCLD-Contribution développement l'emploi (CDE)" + }, + { + "code_programme": "102", + "code": "010200001609", + "description": null, + "label": "Exonération association intermédiaire" + }, + { + "code_programme": "102", + "code": "010200001610", + "description": null, + "label": "IAE-Exos-Ateliers et Chantiers d'insertion (ACI)" + }, + { + "code_programme": "102", + "code": "010200001611", + "description": null, + "label": "Contrats à impact social (CIS)" + }, + { + "code_programme": "102", + "code": "010200001612", + "description": null, + "label": "FIE-Initiatives territoriales (IT)" + }, + { + "code_programme": "102", + "code": "010200001613", + "description": null, + "label": "IAE-Entrep Travail Temp Inser(ETTI)-AAP classiques" + }, + { + "code_programme": "102", + "code": "010200001614", + "description": null, + "label": "IAE-Entrep d'inser travail indépendant (EITI)" + }, + { + "code_programme": "102", + "code": "010200001615", + "description": null, + "label": "IAE-SEVE Emploi" + }, + { + "code_programme": "102", + "code": "010200001616", + "description": null, + "label": "Contrat Pro IAE" + }, + { + "code_programme": "102", + "code": "010200001617", + "description": null, + "label": "IAE-Chantiers d'insertion (ACI)-Contrats Pass" + }, + { + "code_programme": "102", + "code": "010200001618", + "description": null, + "label": "CDI Séniors IAE" + }, + { + "code_programme": "102", + "code": "010200001619", + "description": null, + "label": "IAE-Aide création d'entreprise- Accompagnement" + }, + { + "code_programme": "102", + "code": "010200001701", + "description": null, + "label": "Accompagnement" + }, + { + "code_programme": "102", + "code": "010200001702", + "description": null, + "label": "Actions de parrainage" + }, + { + "code_programme": "102", + "code": "010200001704", + "description": null, + "label": "EPIDE-Fonctionnement" + }, + { + "code_programme": "102", + "code": "010200001705", + "description": null, + "label": "Fonds Insertion professionnelle des jeunes (FIPJ)" + }, + { + "code_programme": "102", + "code": "010200001706", + "description": null, + "label": "Missions locales" + }, + { + "code_programme": "102", + "code": "010200001707", + "description": null, + "label": "Missions locales Accompagnement EAV" + }, + { + "code_programme": "102", + "code": "010200001708", + "description": null, + "label": "Structuration Réseau ML (UNML et ARML)" + }, + { + "code_programme": "102", + "code": "010200001710", + "description": null, + "label": "Ecoles de la deuxième chance (E2C)" + }, + { + "code_programme": "102", + "code": "010200001714", + "description": null, + "label": "EPIDE- Dotation d'investissement" + }, + { + "code_programme": "102", + "code": "010200001716", + "description": null, + "label": "Allocation APEC" + }, + { + "code_programme": "102", + "code": "010200001717", + "description": null, + "label": "Allocation AIJ" + }, + { + "code_programme": "102", + "code": "010200001718", + "description": null, + "label": "Allocation Jeunes étudiants diplômés" + }, + { + "code_programme": "102", + "code": "010200001719", + "description": null, + "label": "Mentorat" + }, + { + "code_programme": "102", + "code": "010200001720", + "description": null, + "label": "CEJ-Allocation Jeunes Ponctuelle PE (AJPE)" + }, + { + "code_programme": "102", + "code": "010200001721", + "description": null, + "label": "CEJ-Nouveaux acteurs accomp des jeunes en rupture" + }, + { + "code_programme": "102", + "code": "010200001722", + "description": null, + "label": "CEJ-Allocation Mensuelle Pôle Emploi" + }, + { + "code_programme": "102", + "code": "010200001723", + "description": null, + "label": "CEJ-Remobilisation jeunes en rupture" + }, + { + "code_programme": "102", + "code": "010200001724", + "description": null, + "label": "CEJ-Offre de service digitale" + }, + { + "code_programme": "102", + "code": "010200001725", + "description": null, + "label": "CEJ-Pôle emploi" + }, + { + "code_programme": "102", + "code": "010200001726", + "description": null, + "label": "CEJ-Allocation Mensuelle Missions locales" + }, + { + "code_programme": "102", + "code": "010200001727", + "description": null, + "label": "CEJ-Accompagnement APEC" + }, + { + "code_programme": "102", + "code": "010200001728", + "description": null, + "label": "Marseille Création d'entreprises Jeunes" + }, + { + "code_programme": "102", + "code": "010200001729", + "description": null, + "label": "Allocation ponctuelle PACEA Missions locales" + }, + { + "code_programme": "102", + "code": "010200001801", + "description": null, + "label": "Mise en situation Emploi Publics fragiles ou spéc." + }, + { + "code_programme": "102", + "code": "010200001901", + "description": null, + "label": "Allocation équivalent retraite (AER)" + }, + { + "code_programme": "102", + "code": "010200001902", + "description": null, + "label": "R2F" + }, + { + "code_programme": "102", + "code": "010200002001", + "description": null, + "label": "Garanties jeunes Accompagnement" + }, + { + "code_programme": "102", + "code": "010200002002", + "description": null, + "label": "Garanties Jeunes Allocations" + }, + { + "code_programme": "102", + "code": "010200002003", + "description": null, + "label": "Allocation PACEA" + }, + { + "code_programme": "102", + "code": "010200002004", + "description": null, + "label": "PIC- Programmes nationaux - Accompagnement" + }, + { + "code_programme": "102", + "code": "010200002005", + "description": null, + "label": "PIC-R2F" + }, + { + "code_programme": "102", + "code": "010200002101", + "description": null, + "label": "Aide exceptionnelle contrats professionnalisation" + }, + { + "code_programme": "102", + "code": "010200002201", + "description": null, + "label": "Expérimentations SPIE" + }, + { + "code_programme": "102", + "code": "010200002301", + "description": null, + "label": "Accompagnements mobilité résidentielle Pôle Emploi" + }, + { + "code_programme": "102", + "code": "010200002302", + "description": null, + "label": "Actions en faveur mobilité des demandeurs d'emploi" + }, + { + "code_programme": "102", + "code": "010200002401", + "description": null, + "label": "ASS-Création Reprise d'Entreprise (ACCRE-ASS)" + } + ], + "pageInfo": { + "totalRows": 13381, + "page": 1, + "pageSize": 100 + } +} diff --git a/spec/models/chorus_configuration_spec.rb b/spec/models/chorus_configuration_spec.rb new file mode 100644 index 000000000..08d636234 --- /dev/null +++ b/spec/models/chorus_configuration_spec.rb @@ -0,0 +1,36 @@ +describe ChorusConfiguration do + subject { create(:procedure) } + it { is_expected.to be_valid } + + context 'empty' do + subject { create(:procedure, chorus: {}) } + it { is_expected.to be_valid } + end + + context 'partially filled chorus_configuration' do + subject { create(:procedure, chorus: { 'centre_de_cout' => '1' }) } + it { is_expected.to be_valid } + end + + context 'fully filled chorus_configuration' do + subject { create(:procedure, chorus: { 'centre_de_coup' => {}, 'domaine_fonctionnel' => {}, 'referentiel_de_programmation' => {} }) } + it { is_expected.to be_valid } + end + + describe 'ChorusConfiguration' do + it 'works without args' do + expect { ChorusConfiguration.new }.not_to raise_error + end + + it 'works with args' do + expect { ChorusConfiguration.new({}) }.not_to raise_error + end + + it 'works with existing args' do + expect do + cc = ChorusConfiguration.new() + cc.assign_attributes(centre_de_coup: {}, domaine_fonctionnel: {}, referentiel_de_programmation: {}) + end.not_to raise_error + end + end +end From 9c2e8d266c234cee5172ee990400c0bd2df78d63 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 13 Oct 2023 12:43:59 +0200 Subject: [PATCH 08/13] fixup! amelioration(procedure.chorus): ajoute la tuile chorus quand cette fonction est active sur la procedure --- .../procedure/card/chorus_component/chorus_component.html.haml | 2 +- .../chorus_form_component/chorus_form_component.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/procedure/card/chorus_component/chorus_component.html.haml b/app/components/procedure/card/chorus_component/chorus_component.html.haml index 7f85ec850..209591865 100644 --- a/app/components/procedure/card/chorus_component/chorus_component.html.haml +++ b/app/components/procedure/card/chorus_component/chorus_component.html.haml @@ -1,5 +1,5 @@ .fr-col-6.fr-col-md-4.fr-col-lg-3.chorus-component - = link_to edit_admin_procedure_chorus_path(@procedure), class: 'fr-tile fr-enlarge-link', title: error_messages do + = link_to edit_admin_procedure_chorus_path(@procedure), class: 'fr-tile fr-enlarge-link', title: 'Configurer le cadre budgetaire Chorus' do .fr-tile__body.flex.column.align-center.justify-between - if error_messages.present? %div diff --git a/app/components/procedure/chorus_form_component/chorus_form_component.html.haml b/app/components/procedure/chorus_form_component/chorus_form_component.html.haml index b6c875d9a..e3d8e2e96 100644 --- a/app/components/procedure/chorus_form_component/chorus_form_component.html.haml +++ b/app/components/procedure/chorus_form_component/chorus_form_component.html.haml @@ -5,7 +5,7 @@ - label_class_name = "#{chorus_configuration_attribute}-label" .fr-select-group = f.label chorus_configuration_attribute, class: 'fr-label', id: label_class_name - = render Dsfr::ComboboxComponent.new form: f, name: :chorus_configuration_attribute, url: datasource_endpoint, value: procedure.chorus_configuration.format_displayed_value(chorus_configuration_attribute), id: chorus_configuration_attribute, class: 'fr-select', describedby: label_class_name do + = render Dsfr::ComboboxComponent.new form: f, name: :chorus_configuration_attribute, url: datasource_endpoint, selected: procedure.chorus_configuration.format_displayed_value(chorus_configuration_attribute), id: chorus_configuration_attribute, class: 'fr-select', describedby: label_class_name do = f.hidden_field chorus_configuration_attribute, data: { value_slot: 'data' }, value: procedure.chorus_configuration.format_hidden_value(chorus_configuration_attribute) = f.submit "Enregister", class: 'fr-btn' From 857c1f0c2100172b3011b7d5d16b959804ccc284 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 13 Oct 2023 13:16:22 +0200 Subject: [PATCH 09/13] amelioration(ChorusComponent): ameliore le rendu de la tuile si celle ci n'est que partiellement rempli --- .../procedure/card/chorus_component.rb | 4 +-- .../chorus_component.html.haml | 6 +--- app/models/chorus_configuration.rb | 8 ++++++ .../administrateurs/chorus/edit.html.haml | 2 +- .../administrateurs/chorus_controller_spec.rb | 2 +- spec/factories/procedure.rb | 16 +++++++++++ spec/models/chorus_configuration_spec.rb | 28 +++++++++++++++---- 7 files changed, 51 insertions(+), 15 deletions(-) diff --git a/app/components/procedure/card/chorus_component.rb b/app/components/procedure/card/chorus_component.rb index e4830ce70..88a36ab3b 100644 --- a/app/components/procedure/card/chorus_component.rb +++ b/app/components/procedure/card/chorus_component.rb @@ -7,7 +7,7 @@ class Procedure::Card::ChorusComponent < ApplicationComponent @procedure.chorusable? end - def error_messages - [] + def complete? + @procedure.chorus_configuration.complete? end end diff --git a/app/components/procedure/card/chorus_component/chorus_component.html.haml b/app/components/procedure/card/chorus_component/chorus_component.html.haml index 209591865..8fd65667b 100644 --- a/app/components/procedure/card/chorus_component/chorus_component.html.haml +++ b/app/components/procedure/card/chorus_component/chorus_component.html.haml @@ -1,11 +1,7 @@ .fr-col-6.fr-col-md-4.fr-col-lg-3.chorus-component = link_to edit_admin_procedure_chorus_path(@procedure), class: 'fr-tile fr-enlarge-link', title: 'Configurer le cadre budgetaire Chorus' do .fr-tile__body.flex.column.align-center.justify-between - - if error_messages.present? - %div - %span.icon.refuse - %p.fr-tile-status-error À modifier - - elsif @count == 0 + - if !@procedure.chorus_configuration.complete? %div %span.icon.clock %p.fr-tile-status-todo À compléter diff --git a/app/models/chorus_configuration.rb b/app/models/chorus_configuration.rb index de2f1c792..de2602385 100644 --- a/app/models/chorus_configuration.rb +++ b/app/models/chorus_configuration.rb @@ -49,4 +49,12 @@ class ChorusConfiguration api_result = api_result.symbolize_keys "#{api_result[:label]} - #{api_result[:code]}" end + + def complete? + [ + centre_de_coup, + domaine_fonctionnel, + referentiel_de_programmation + ].all?(&:present?) + end end diff --git a/app/views/administrateurs/chorus/edit.html.haml b/app/views/administrateurs/chorus/edit.html.haml index 8f6037e99..bf7c2b1b5 100644 --- a/app/views/administrateurs/chorus/edit.html.haml +++ b/app/views/administrateurs/chorus/edit.html.haml @@ -5,7 +5,7 @@ .container - %h1.mb-2 + %h1.fr-h1 Cadre budgétaire = render Procedure::ChorusFormComponent.new(procedure: @procedure) diff --git a/spec/controllers/administrateurs/chorus_controller_spec.rb b/spec/controllers/administrateurs/chorus_controller_spec.rb index 30f12b1cd..c239a8d26 100644 --- a/spec/controllers/administrateurs/chorus_controller_spec.rb +++ b/spec/controllers/administrateurs/chorus_controller_spec.rb @@ -57,7 +57,7 @@ describe Administrateurs::ChorusController, type: :controller do let(:referentiel_de_programmation) { '{"code":"010101010101","label":"DOTATIONS+CARPA+AJ+ET+AUTRES+INTERVENTIONS","description":null,"code_programme":"101"}' } let(:chorus_configuration_params) do { - centre_de_coup:, domaine_fonctionnel:, referentiel_de_programmation:, + centre_de_coup:, domaine_fonctionnel:, referentiel_de_programmation: } end diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb index cc2eb519f..a545fab33 100644 --- a/spec/factories/procedure.rb +++ b/spec/factories/procedure.rb @@ -422,6 +422,22 @@ FactoryBot.define do trait :svr do sva_svr { SVASVRConfiguration.new(decision: :svr).attributes } end + + trait :empty_chorus do + chorus { ChorusConfiguration.new } + end + + trait :partial_chorus do + chorus { ChorusConfiguration.new(centre_de_coup: { a: 1 }) } + end + + trait :filled_chorus do + chorus do + ChorusConfiguration.new(centre_de_coup: { a: 1 }, + domaine_fonctionnel: { b: 2 }, + referentiel_de_programmation: { c: 3 }) + end + end end end diff --git a/spec/models/chorus_configuration_spec.rb b/spec/models/chorus_configuration_spec.rb index 08d636234..5298b4e42 100644 --- a/spec/models/chorus_configuration_spec.rb +++ b/spec/models/chorus_configuration_spec.rb @@ -1,19 +1,16 @@ describe ChorusConfiguration do - subject { create(:procedure) } - it { is_expected.to be_valid } - context 'empty' do - subject { create(:procedure, chorus: {}) } + subject { create(:procedure, :empty_chorus) } it { is_expected.to be_valid } end context 'partially filled chorus_configuration' do - subject { create(:procedure, chorus: { 'centre_de_cout' => '1' }) } + subject { create(:procedure, :partial_chorus) } it { is_expected.to be_valid } end context 'fully filled chorus_configuration' do - subject { create(:procedure, chorus: { 'centre_de_coup' => {}, 'domaine_fonctionnel' => {}, 'referentiel_de_programmation' => {} }) } + subject { create(:procedure, :filled_chorus) } it { is_expected.to be_valid } end @@ -33,4 +30,23 @@ describe ChorusConfiguration do end.not_to raise_error end end + + describe '#complete?' do + subject { procedure.chorus_configuration.complete? } + + context 'without data' do + let(:procedure) { create(:procedure, :empty_chorus) } + it { is_expected.to be_falsey } + end + + context 'with partial data' do + let(:procedure) { create(:procedure, :partial_chorus) } + it { is_expected.to be_falsey } + end + + context 'with all data' do + let(:procedure) { create(:procedure, :filled_chorus) } + it { is_expected.to be_truthy } + end + end end From 3495147045f1c7a1f4249d2cf40ac7ede65c1b02 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 19 Oct 2023 08:47:48 +0200 Subject: [PATCH 10/13] =?UTF-8?q?tech(APIBretagneService.clean):=20renvoie?= =?UTF-8?q?=20un=20tableau=20vide=20tant=20que=20la=20recherche=20ne=20fai?= =?UTF-8?q?t=20pas=20plus=20de=202=20caract=C3=A8res?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/api_bretagne_service.rb | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/app/services/api_bretagne_service.rb b/app/services/api_bretagne_service.rb index 39edb2015..e5a956f7f 100644 --- a/app/services/api_bretagne_service.rb +++ b/app/services/api_bretagne_service.rb @@ -14,16 +14,19 @@ class APIBretagneService def search_domaine_fonct(code_or_label: "") url = build_url(ENDPOINTS.fetch('domaine-fonct')) + return [] if code_or_label.size < 3 fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] end def search_centre_couts(code_or_label: "") url = build_url(ENDPOINTS.fetch('centre-couts')) + return [] if code_or_label.size < 3 fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] end def search_ref_programmation(code_or_label: "") url = build_url(ENDPOINTS.fetch('ref-programmation')) + return [] if code_or_label.size < 3 fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] end @@ -46,22 +49,6 @@ class APIBretagneService end end - # QUESTION TECH, on a des API av 18k reponse potentielles, - # sur de l'autocomplete je suppose qu'on cherche pas a paginer le resultat. - # vous avez une autre idée en tête ? - - # def fetch_all_page(url:, code_or_label:) - # first_page = fetch_page(url:, params: { page_number: 1, query: code_or_label }) - # return [] if first_page.empty? - - # total_pages = (first_page[:pageInfo][:totalRows].to_f / first_page[:pageInfo][:pageSize].to_f).ceil - # all = first_page[:items] - # (2..total_pages).map do |page_number| - # page = fetch_page(url:, params: { page_number: }) - # all.concat(page[:items]) - # end - # all - # end def call(url:, params:) API::Client.new.(url:, params:, authorization_token:, method:) From ffdd7ee95d0ae750ebd414606b8829cf8d57ee6f Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 19 Oct 2023 08:50:51 +0200 Subject: [PATCH 11/13] tech(APIBretagneService.clean): l'API de data.bretagne renvoie un vide quand il n'y a pas de resultat, change l'implem pour ce cas --- app/services/api_bretagne_service.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/api_bretagne_service.rb b/app/services/api_bretagne_service.rb index e5a956f7f..1bdc0e56e 100644 --- a/app/services/api_bretagne_service.rb +++ b/app/services/api_bretagne_service.rb @@ -34,6 +34,7 @@ class APIBretagneService def fetch_page(url:, params:, retry_count: 1) result = call(url:, params:) + case result in Failure(code:, reason:) if code.in?(401..403) if retry_count > 0 @@ -42,10 +43,10 @@ class APIBretagneService else fail "APIBretagneService, #{reason} #{code}" end - in Failure(code:) if code == 204 - [] in Success(body:) body + else # no response gives back a 204, so we don't try to JSON.parse(nil) to avoid error + { items: [] } end end From a4ef3cdf2337065d1dce2bd3078f8d1f0cb593ae Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 19 Oct 2023 08:55:01 +0200 Subject: [PATCH 12/13] tech(ActiveModel.types.json): renomme en simple_json pour eviter la confusion avec ActiveRecord.types.json --- app/models/chorus_configuration.rb | 6 +++--- app/services/api_bretagne_service.rb | 1 - config/initializers/attribute_types.rb | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/models/chorus_configuration.rb b/app/models/chorus_configuration.rb index de2602385..3447e7bc1 100644 --- a/app/models/chorus_configuration.rb +++ b/app/models/chorus_configuration.rb @@ -2,9 +2,9 @@ class ChorusConfiguration include ActiveModel::Model include ActiveModel::Attributes - attribute :centre_de_coup, :json, default: '{}' - attribute :domaine_fonctionnel, :json, default: '{}' - attribute :referentiel_de_programmation, :json, default: '{}' + attribute :centre_de_coup, :simple_json, default: '{}' + attribute :domaine_fonctionnel, :simple_json, default: '{}' + attribute :referentiel_de_programmation, :simple_json, default: '{}' def format_displayed_value(attribute_name) case attribute_name diff --git a/app/services/api_bretagne_service.rb b/app/services/api_bretagne_service.rb index 1bdc0e56e..5f317ee59 100644 --- a/app/services/api_bretagne_service.rb +++ b/app/services/api_bretagne_service.rb @@ -50,7 +50,6 @@ class APIBretagneService end end - def call(url:, params:) API::Client.new.(url:, params:, authorization_token:, method:) end diff --git a/config/initializers/attribute_types.rb b/config/initializers/attribute_types.rb index a487d607d..153838f86 100644 --- a/config/initializers/attribute_types.rb +++ b/config/initializers/attribute_types.rb @@ -1,4 +1,4 @@ -class JsonType < ActiveModel::Type::Value +class SimpleJsonType < ActiveModel::Type::Value def cast(value) return nil if value.blank? return value if value.is_a?(Hash) @@ -8,4 +8,4 @@ class JsonType < ActiveModel::Type::Value end end -ActiveModel::Type.register(:json, JsonType) +ActiveModel::Type.register(:simple_json, SimpleJsonType) From 0922e0987ab0c8022ff26b9cc67a01c653a241b4 Mon Sep 17 00:00:00 2001 From: mfo Date: Thu, 19 Oct 2023 15:19:23 +0200 Subject: [PATCH 13/13] =?UTF-8?q?review(maj):=20strip=20la=20valeur=20rech?= =?UTF-8?q?erch=C3=A9=20sur=20les=20api=20chorus=20avant=20de=20le=20soume?= =?UTF-8?q?ttre=20=C3=A0=20l'API,=20et=20quelques=20maj=20de=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Colin Darie --- .../procedure/chorus_form_component.rb | 8 +++ .../chorus_form_component.html.haml | 2 - .../data_sources/chorus_controller.rb | 50 +++++++------------ app/services/api_bretagne_service.rb | 26 +++++----- 4 files changed, 38 insertions(+), 48 deletions(-) diff --git a/app/components/procedure/chorus_form_component.rb b/app/components/procedure/chorus_form_component.rb index 219b4e066..7e2dc99a7 100644 --- a/app/components/procedure/chorus_form_component.rb +++ b/app/components/procedure/chorus_form_component.rb @@ -4,4 +4,12 @@ class Procedure::ChorusFormComponent < ApplicationComponent def initialize(procedure:) @procedure = procedure end + + def map_attribute_to_autocomplete_endpoint + { + centre_de_coup: data_sources_search_centre_couts_path, + domaine_fonctionnel: data_sources_search_domaine_fonct_path, + referentiel_de_programmation: data_sources_search_ref_programmation_path + } + end end diff --git a/app/components/procedure/chorus_form_component/chorus_form_component.html.haml b/app/components/procedure/chorus_form_component/chorus_form_component.html.haml index e3d8e2e96..a78da64dd 100644 --- a/app/components/procedure/chorus_form_component/chorus_form_component.html.haml +++ b/app/components/procedure/chorus_form_component/chorus_form_component.html.haml @@ -1,5 +1,3 @@ -- map_attribute_to_autocomplete_endpoint = { centre_de_coup: data_sources_search_centre_couts_path, domaine_fonctionnel: data_sources_search_domaine_fonct_path, referentiel_de_programmation: data_sources_search_ref_programmation_path } - = form_for([procedure, procedure.chorus_configuration],url: admin_procedure_chorus_path(procedure), method: :put) do |f| - map_attribute_to_autocomplete_endpoint.map do |chorus_configuration_attribute, datasource_endpoint| - label_class_name = "#{chorus_configuration_attribute}-label" diff --git a/app/controllers/data_sources/chorus_controller.rb b/app/controllers/data_sources/chorus_controller.rb index 7ee61e3b9..c734e869b 100644 --- a/app/controllers/data_sources/chorus_controller.rb +++ b/app/controllers/data_sources/chorus_controller.rb @@ -2,48 +2,32 @@ class DataSources::ChorusController < ApplicationController before_action :authenticate_administrateur! def search_domaine_fonct - @result = APIBretagneService.new.search_domaine_fonct(code_or_label: params[:q]) - result_json = @result.map do |item| - { - label: ChorusConfiguration.format_domaine_fonctionnel_label(item), - value: "#{item[:label]} - #{item[:code_programme]}", - data: item - } - end - render json: result_json + result_json = APIBretagneService.new.search_domaine_fonct(code_or_label: params[:q]) + render json: format_result(result_json:, + label_formatter: ChorusConfiguration.method(:format_domaine_fonctionnel_label)) end def search_centre_couts - @result = APIBretagneService.new.search_centre_couts(code_or_label: params[:q]) - result_json = @result.map do |item| - { - label: ChorusConfiguration.format_domaine_fonctionnel_label(item), - value: "#{item[:label]} - #{item[:code_programme]}", - data: item - } - end - render json: result_json + result_json = APIBretagneService.new.search_centre_couts(code_or_label: params[:q]) + render json: format_result(result_json:, + label_formatter: ChorusConfiguration.method(:format_centre_de_coup_label)) end def search_ref_programmation - @result = APIBretagneService.new.search_ref_programmation(code_or_label: params[:q]) - result_json = @result.map do |item| + result_json = APIBretagneService.new.search_ref_programmation(code_or_label: params[:q]) + render json: format_result(result_json:, + label_formatter: ChorusConfiguration.method(:format_ref_programmation_label)) + end + + private + + def format_result(result_json:, label_formatter:) + result_json.map do |item| { - label: ChorusConfiguration.format_domaine_fonctionnel_label(item), + label: label_formatter.call(item), value: "#{item[:label]} - #{item[:code_programme]}", data: item } end - render json: result_json - end - - # def search - # if params[:q].present? && params[:q].length > 3 - # response = Typhoeus.get("#{API_ADRESSE_URL}/search", params: { q: params[:q], limit: 10 }) - # result = JSON.parse(response.body, symbolize_names: true) - # render json: result[:features].map { { label: _1[:properties][:label], value: _1[:properties][:label] } } - # else - # render json: [] - # end - # end + end end diff --git a/app/services/api_bretagne_service.rb b/app/services/api_bretagne_service.rb index 5f317ee59..b4d5ee5b7 100644 --- a/app/services/api_bretagne_service.rb +++ b/app/services/api_bretagne_service.rb @@ -13,33 +13,33 @@ class APIBretagneService } def search_domaine_fonct(code_or_label: "") - url = build_url(ENDPOINTS.fetch('domaine-fonct')) - return [] if code_or_label.size < 3 - fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] + request(endpoint: ENDPOINTS.fetch('domaine-fonct'), code_or_label:) end def search_centre_couts(code_or_label: "") - url = build_url(ENDPOINTS.fetch('centre-couts')) - return [] if code_or_label.size < 3 - fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] + request(endpoint: ENDPOINTS.fetch('centre-couts'), code_or_label:) end def search_ref_programmation(code_or_label: "") - url = build_url(ENDPOINTS.fetch('ref-programmation')) - return [] if code_or_label.size < 3 - fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] + request(endpoint: ENDPOINTS.fetch('ref-programmation'), code_or_label:) end private - def fetch_page(url:, params:, retry_count: 1) + def request(endpoint:, code_or_label:) + return [] if (code_or_label || "").strip.size < 3 + url = build_url(endpoint) + fetch_page(url:, params: { query: code_or_label, page_number: 1 })[:items] || [] + end + + def fetch_page(url:, params:, remaining_retry_count: 1) result = call(url:, params:) case result in Failure(code:, reason:) if code.in?(401..403) - if retry_count > 0 + if remaining_retry_count > 0 login - fetch_page(url:, params:, retry_count: 0) + fetch_page(url:, params:, remaining_retry_count: 0) else fail "APIBretagneService, #{reason} #{code}" end @@ -72,7 +72,7 @@ class APIBretagneService result = API::Client.new.call(url: build_url(ENDPOINTS.fetch("login")), json: { email: ENV['API_DATABRETAGE_USERNAME'], - password: ENV['API_DATABRETAGE_PASSWORD'] + password: ENV['API_DATABRETAGE_PASSWORD'] }, method: :post) case result