Merge pull request #9420 from mfo/US/chorus-tile
amelioration(tuile.chorus): ETQ admin, je peux saisir le cadre budgetaire d'une demarche de subvention pour faciliter le rapprochement d'un export DS a un export Chorus
This commit is contained in:
commit
ebea269f79
24 changed files with 2468 additions and 0 deletions
13
app/components/procedure/card/chorus_component.rb
Normal file
13
app/components/procedure/card/chorus_component.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
class Procedure::Card::ChorusComponent < ApplicationComponent
|
||||
def initialize(procedure:)
|
||||
@procedure = procedure
|
||||
end
|
||||
|
||||
def render?
|
||||
@procedure.chorusable?
|
||||
end
|
||||
|
||||
def complete?
|
||||
@procedure.chorus_configuration.complete?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
.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 !@procedure.chorus_configuration.complete?
|
||||
%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
|
15
app/components/procedure/chorus_form_component.rb
Normal file
15
app/components/procedure/chorus_form_component.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class Procedure::ChorusFormComponent < ApplicationComponent
|
||||
attr_reader :procedure
|
||||
|
||||
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
|
|
@ -0,0 +1,9 @@
|
|||
= 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"
|
||||
.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, 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'
|
33
app/controllers/administrateurs/chorus_controller.rb
Normal file
33
app/controllers/administrateurs/chorus_controller.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
module Administrateurs
|
||||
class ChorusController < AdministrateurController
|
||||
before_action :retrieve_procedure
|
||||
|
||||
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 search_params
|
||||
params.permit(:q)
|
||||
end
|
||||
|
||||
def configurations_params
|
||||
params.require(:chorus_configuration)
|
||||
.permit(:centre_de_coup, :domaine_fonctionnel, :referentiel_de_programmation)
|
||||
end
|
||||
end
|
||||
end
|
33
app/controllers/data_sources/chorus_controller.rb
Normal file
33
app/controllers/data_sources/chorus_controller.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class DataSources::ChorusController < ApplicationController
|
||||
before_action :authenticate_administrateur!
|
||||
|
||||
def search_domaine_fonct
|
||||
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_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_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: label_formatter.call(item),
|
||||
value: "#{item[:label]} - #{item[:code_programme]}",
|
||||
data: item
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
60
app/models/chorus_configuration.rb
Normal file
60
app/models/chorus_configuration.rb
Normal file
|
@ -0,0 +1,60 @@
|
|||
class ChorusConfiguration
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
|
||||
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
|
||||
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 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.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
|
||||
|
||||
def complete?
|
||||
[
|
||||
centre_de_coup,
|
||||
domaine_fonctionnel,
|
||||
referentiel_de_programmation
|
||||
].all?(&:present?)
|
||||
end
|
||||
end
|
13
app/models/concerns/procedure_chorus_concern.rb
Normal file
13
app/models/concerns/procedure_chorus_concern.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
module ProcedureChorusConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def chorus_configuration
|
||||
@chorus_configuration ||= ChorusConfiguration.new(chorus)
|
||||
end
|
||||
|
||||
def chorusable?
|
||||
feature_enabled?(:chorus)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@ class Procedure < ApplicationRecord
|
|||
include InitiationProcedureConcern
|
||||
include ProcedureGroupeInstructeurAPIHackConcern
|
||||
include ProcedureSVASVRConcern
|
||||
include ProcedureChorusConcern
|
||||
|
||||
include Discard::Model
|
||||
self.discard_column = :hidden_at
|
||||
|
|
93
app/services/api_bretagne_service.rb
Normal file
93
app/services/api_bretagne_service.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
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: "")
|
||||
request(endpoint: ENDPOINTS.fetch('domaine-fonct'), code_or_label:)
|
||||
end
|
||||
|
||||
def search_centre_couts(code_or_label: "")
|
||||
request(endpoint: ENDPOINTS.fetch('centre-couts'), code_or_label:)
|
||||
end
|
||||
|
||||
def search_ref_programmation(code_or_label: "")
|
||||
request(endpoint: ENDPOINTS.fetch('ref-programmation'), code_or_label:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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 remaining_retry_count > 0
|
||||
login
|
||||
fetch_page(url:, params:, remaining_retry_count: 0)
|
||||
else
|
||||
fail "APIBretagneService, #{reason} #{code}"
|
||||
end
|
||||
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
|
||||
|
||||
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
|
11
app/views/administrateurs/chorus/edit.html.haml
Normal file
11
app/views/administrateurs/chorus/edit.html.haml
Normal file
|
@ -0,0 +1,11 @@
|
|||
= render partial: 'administrateurs/breadcrumbs',
|
||||
locals: { steps: [['Démarches', admin_procedures_path],
|
||||
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
|
||||
['Connecteur Chorus']] }
|
||||
|
||||
|
||||
.container
|
||||
%h1.fr-h1
|
||||
Cadre budgétaire
|
||||
|
||||
= render Procedure::ChorusFormComponent.new(procedure: @procedure)
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue