Skip to content

Commit

Permalink
Merge pull request #9420 from mfo/US/chorus-tile
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mfo authored Oct 24, 2023
2 parents 5d3d4cb + 0922e09 commit ebea269
Show file tree
Hide file tree
Showing 24 changed files with 2,468 additions and 0 deletions.
13 changes: 13 additions & 0 deletions app/components/procedure/card/chorus_component.rb
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions app/components/procedure/chorus_form_component.rb
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 changes: 33 additions & 0 deletions app/controllers/administrateurs/chorus_controller.rb
Original file line number Diff line number Diff line change
@@ -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 changes: 33 additions & 0 deletions app/controllers/data_sources/chorus_controller.rb
Original file line number Diff line number Diff line change
@@ -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 changes: 60 additions & 0 deletions app/models/chorus_configuration.rb
Original file line number Diff line number Diff line change
@@ -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 changes: 13 additions & 0 deletions app/models/concerns/procedure_chorus_concern.rb
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions app/models/procedure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Procedure < ApplicationRecord
include InitiationProcedureConcern
include ProcedureGroupeInstructeurAPIHackConcern
include ProcedureSVASVRConcern
include ProcedureChorusConcern

include Discard::Model
self.discard_column = :hidden_at
Expand Down
93 changes: 93 additions & 0 deletions app/services/api_bretagne_service.rb
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions app/views/administrateurs/chorus/edit.html.haml
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions app/views/administrateurs/procedures/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
11 changes: 11 additions & 0 deletions config/initializers/attribute_types.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class SimpleJsonType < 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(:simple_json, SimpleJsonType)
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@

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

#
Expand Down Expand Up @@ -602,6 +605,7 @@
resource :attestation_template, only: [:show, :edit, :update, :create] do
get 'preview', on: :member
end
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'
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20230828131618_add_chorus_column_to_procedure.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddChorusColumnToProcedure < ActiveRecord::Migration[7.0]
def change
add_column :procedures, :chorus, :jsonb, default: {}, null: false
end
end
1 change: 1 addition & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@
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
Expand Down
24 changes: 24 additions & 0 deletions spec/components/procedures/card/chorus_component_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit ebea269

Please sign in to comment.