Merge pull request #6488 from betagouv/add_cnaf_champ

Ajoute le champ CNAF
This commit is contained in:
LeSim 2021-10-12 14:37:35 +02:00 committed by GitHub
commit b8946137c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 651 additions and 40 deletions

View file

@ -0,0 +1,30 @@
@import "constants";
@import "colors";
table.cnaf {
margin: 2 * $default-padding 0 $default-padding $default-padding;
width: 100%;
caption {
font-weight: bold;
margin-left: - $default-padding;
margin-bottom: $default-spacer;
text-align: left;
}
th,
td {
font-weight: normal;
padding: $default-spacer;
}
th.text-right {
text-align: right;
}
&.horizontal {
th {
border-bottom: 1px solid $grey;
}
}
}

View file

@ -356,6 +356,17 @@
}
}
.cnaf-inputs {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
max-width: 700px;
input {
width: inherit;
}
}
input.aa-input,
input.aa-hint {
border-radius: 4px;

View file

@ -337,8 +337,8 @@ module Users
def champs_params
params.permit(dossier: {
champs_attributes: [
:id, :value, :external_id, :primary_value, :secondary_value, :piece_justificative_file, value: [],
champs_attributes: [:id, :_destroy, :value, :external_id, :primary_value, :secondary_value, :piece_justificative_file, value: []]
:id, :value, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, value: [],
champs_attributes: [:id, :_destroy, :value, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, value: []]
]
})
end
@ -382,7 +382,8 @@ module Users
if @dossier.champs.any?(&:changed_for_autosave?)
@dossier.last_champ_updated_at = Time.zone.now
end
if !@dossier.save
if !@dossier.save(**validation_options)
errors += @dossier.errors.full_messages
elsif change_groupe_instructeur?
@dossier.assign_to_groupe_instructeur(groupe_instructeur_from_params)
@ -453,5 +454,16 @@ module Users
def save_draft?
dossier.brouillon? && !params[:submit_draft]
end
def validation_options
if save_draft?
{ context: :brouillon }
else
# rubocop:disable Lint/BooleanSymbol
# Force ActiveRecord to re-validate associated records.
{ context: :false }
# rubocop:enable Lint/BooleanSymbol
end
end
end
end

View file

@ -1869,6 +1869,11 @@ enum TypeDeChamp {
"""
civilite
"""
Données de la Caisse nationale des allocations familiales
"""
cnaf
"""
Communes
"""

View file

@ -1,4 +1,4 @@
class APIParticulier::CNAFAdapter
class APIParticulier::CnafAdapter
class InvalidSchemaError < ::StandardError
def initialize(errors)
super(errors.map(&:to_json).join("\n"))

View file

@ -14,7 +14,7 @@ module APIParticulier
msg = <<~TEXT
url: #{url}
HTTP error code: #{http_error_code}
#{response.body}
#{response.body.force_encoding('UTF-8')}
curl message: #{curl_message}
total time: #{total_time}
connect time: #{connect_time}

View file

@ -8,6 +8,7 @@ module APIParticulier
def available_sources
@procedure.api_particulier_scopes
.map { |provider_and_scope| raw_scopes[provider_and_scope] }
.compact
.map { |provider, scope| extract_sources(provider, scope) }
.reduce({}) { |acc, el| acc.deep_merge(el) }
end

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -0,0 +1,52 @@
# == Schema Information
#
# Table name: champs
#
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# private :boolean default(FALSE), not null
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# type_de_champ_id :integer
#
class Champs::CnafChamp < Champs::TextChamp
# see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/cnaf-input-validation.middleware.ts
validates :numero_allocataire, format: { with: /\A\d{1,7}\z/ }, if: -> { code_postal.present? && validation_context != :brouillon }
validates :code_postal, format: { with: /\A\w{5}\z/ }, if: -> { numero_allocataire.present? && validation_context != :brouillon }
store_accessor :value_json, :numero_allocataire, :code_postal
def blank?
external_id.nil?
end
def fetch_external_data?
true
end
def fetch_external_data
if valid?
APIParticulier::CnafAdapter.new(
procedure.api_particulier_token,
numero_allocataire,
code_postal,
procedure.api_particulier_sources
).to_params
end
end
def external_id
if numero_allocataire.present? && code_postal.present?
{ code_postal: code_postal, numero_allocataire: numero_allocataire }.to_json
end
end
end

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -9,6 +9,7 @@
# row :integer
# type :string
# value :string
# value_json :jsonb
# created_at :datetime
# updated_at :datetime
# dossier_id :integer

View file

@ -716,6 +716,10 @@ class Procedure < ApplicationRecord
published_revision.touch(:published_at)
end
def cnaf_enabled?
api_particulier_sources['cnaf'].present?
end
private
def before_publish

View file

@ -48,7 +48,8 @@ class TypeDeChamp < ApplicationRecord
repetition: 'repetition',
titre_identite: 'titre_identite',
iban: 'iban',
annuaire_education: 'annuaire_education'
annuaire_education: 'annuaire_education',
cnaf: 'cnaf'
}
belongs_to :revision, class_name: 'ProcedureRevision', optional: true
@ -291,17 +292,26 @@ class TypeDeChamp < ApplicationRecord
def self.type_de_champ_types_for(procedure, user)
has_legacy_number = (procedure.types_de_champ + procedure.types_de_champ_private).any?(&:legacy_number?)
show_number = -> (tdc) { tdc != TypeDeChamp.type_champs.fetch(:number) || has_legacy_number }
enabled_featured_champ = -> (tdc) do
filter_featured_tdc = -> (tdc) do
feature_name = FEATURE_FLAGS[tdc]
feature_name.blank? || Flipper.enabled?(feature_name, user)
end
filter_tdc = -> (tdc) do
case tdc
when TypeDeChamp.type_champs.fetch(:number)
has_legacy_number
when TypeDeChamp.type_champs.fetch(:cnaf)
procedure.cnaf_enabled?
else
true
end
end
type_champs
.keys
.filter(&show_number)
.filter(&enabled_featured_champ)
.filter(&filter_tdc)
.filter(&filter_featured_tdc)
.map { |tdc| [I18n.t("activerecord.attributes.type_de_champ.type_champs.#{tdc}"), tdc] }
.sort_by(&:first)
end

View file

@ -0,0 +1,2 @@
class TypesDeChamp::CnafTypeDeChamp < TypesDeChamp::TextTypeDeChamp
end

View file

@ -5,7 +5,7 @@
.container
.flex
= link_to admin_procedure_api_particulier_jeton_path, class: 'card-admin' do
= link_to admin_procedure_api_particulier_jeton_path, class: 'card-admin', id: 'add-jeton' do
- if @procedure.api_particulier_token.blank?
%div
%span.icon.clock

View file

@ -193,7 +193,7 @@
%p.button Modifier
- if feature_enabled?(:api_particulier)
= link_to admin_procedure_api_particulier_path(@procedure), class: 'card-admin' do
= link_to admin_procedure_api_particulier_path(@procedure), class: 'card-admin', id: 'api-particulier' do
- if @procedure.api_particulier_token.present?
%div
%span.icon.accept

View file

@ -16,7 +16,7 @@
- scopes.each do |scope_key, sources|
%h3.explication-libelle= t("api_particulier.providers.#{provider_key}.scopes.#{scope_key}.libelle")
%ul.procedure-admin-api-particulier-sources
%ul.procedure-admin-api-particulier-sources{ id: scope_key }
- sources.each do |source_key, enabled_hash|
- enabled = (@procedure.api_particulier_sources.dig(provider_key, scope_key)&.include?(source_key)).present?
%li

View file

@ -0,0 +1,10 @@
%table.cnaf
%caption #{t("api_particulier.providers.cnaf.scopes.adresse.libelle")} :
- for key in ['identite', 'complementIdentite', 'numeroRue', 'complementIdentiteGeo', 'lieuDit', 'codePostalVille', 'pays'] do
- if adresse[key].present?
%tr
%th= t("api_particulier.providers.cnaf.scopes.adresse.#{key}")
%td= adresse[key]

View file

@ -0,0 +1,19 @@
%table.cnaf.horizontal
%caption #{t("api_particulier.providers.cnaf.scopes.#{scope}.libelle")} :
%thead
%tr
- for key in ['nomPrenom', 'sexe', 'dateDeNaissance'] do
- if personnes.first[key].present?
%th{ class: "#{"text-right" if key == 'dateDeNaissance'}" }= t("api_particulier.providers.cnaf.scopes.personne.#{key}")
%tbody
- personnes.each do |personne|
%tr
- for key in ['nomPrenom', 'sexe', 'dateDeNaissance'] do
- if personne[key].present?
- case key
- when 'dateDeNaissance'
%td.text-right= try_format_datetime(Date.strptime(personne[key], "%d%m%Y"))
- when 'sexe'
%td= t("api_particulier.providers.cnaf.scopes.personne.#{personne[key]}")
- else
%td= personne[key]

View file

@ -0,0 +1,14 @@
%table.cnaf.horizontal
%caption #{t("api_particulier.providers.cnaf.scopes.quotient_familial.libelle")} :
%thead
%tr
- for key in ['quotientFamilial', 'mois', 'annee'] do
- if quotient_familial[key].present?
%th.text-right= t("api_particulier.providers.cnaf.scopes.quotient_familial.#{key}")
%tbody
%tr
- for key in ['quotientFamilial', 'mois', 'annee'] do
- if quotient_familial[key].present?
%td.text-right= quotient_familial[key]
- else
%td

View file

@ -0,0 +1,24 @@
- if champ.blank?
%p= t('.not_filled')
- elsif champ.data.blank?
%p= t('.fetching_data',
numero_allocataire: champ.numero_allocataire,
code_postal: champ.code_postal)
- else
- if profile == 'usager'
%p= t('.data_fetched',
sources: champ.procedure.api_particulier_sources['cnaf'].keys.map(&:to_s).join(', '),
numero_allocataire: champ.numero_allocataire,
code_postal: champ.code_postal)
- if profile == 'instructeur'
%p= t('.data_fetched_title')
- ['adresse', 'quotient_familial', 'enfants', 'allocataires'].each do |scope|
- if champ.data[scope].present?
- if scope == 'quotient_familial'
= render partial: 'shared/champs/cnaf/quotient_familial', locals: { quotient_familial: champ.data[scope] }
- if scope.in? ['enfants', 'allocataires']
= render partial: 'shared/champs/cnaf/personnes', locals: { scope: scope, personnes: champ.data[scope] }
- elsif scope == 'adresse'
= render partial: 'shared/champs/cnaf/adresse', locals: { adresse: champ.data[scope] }

View file

@ -36,6 +36,8 @@
= render partial: "shared/champs/textarea/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:annuaire_education)
= render partial: "shared/champs/annuaire_education/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:cnaf)
= render partial: "shared/champs/cnaf/show", locals: { champ: c, profile: profile }
- when TypeDeChamp.type_champs.fetch(:address)
= render partial: "shared/champs/address/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:communes)

View file

@ -0,0 +1,16 @@
.cnaf-inputs
%div
= form.label :numero_allocataire, t('.numero_allocataire_label')
%p.notice= t('.numero_allocataire_notice')
= form.text_field :numero_allocataire,
required: champ.mandatory?,
size: 7,
aria: { describedby: describedby_id(champ) }
%div
= form.label :code_postal, t('.code_postal_label')
%p.notice= t('.code_postal_notice')
= form.text_field :code_postal,
size: 5,
required: champ.mandatory?,
aria: { describedby: describedby_id(champ) }

View file

@ -14,7 +14,6 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'JSON'
inflect.acronym 'RNA'
inflect.acronym 'URL'
inflect.acronym 'CNAF'
inflect.irregular 'type_de_champ', 'types_de_champ'
inflect.irregular 'type_de_champ_private', 'types_de_champ_private'
inflect.irregular 'procedure_revision_type_de_champ', 'procedure_revision_types_de_champ'

View file

@ -0,0 +1,32 @@
en:
api_particulier:
providers:
cnaf:
libelle: Caisse nationale dallocations familiales (CAF)
scopes:
personne: &personne
nomPrenom: first and last name
dateDeNaissance: birth date
sexe: sex
M: male
F: female
allocataires:
libelle: beneficiaries
<<: *personne
enfants:
libelle: children
<<: *personne
adresse:
libelle: address
identite: identity
complementIdentite: complément didentité
complementIdentiteGeo: complément didentité géographique
numeroRue: number and street
lieuDit: lieu-dit
codePostalVille: postcode and city
pays: country
quotient_familial:
libelle: quotient familial
quotientFamilial: quotient familial
mois: month
annee: year

View file

@ -2,12 +2,14 @@ fr:
api_particulier:
providers:
cnaf:
libelle: Caisse dallocations familiales (CAF)
libelle: Caisse nationale dallocations familiales (CAF)
scopes:
personne: &personne
nomPrenom: noms et prénoms
dateDeNaissance: date de naissance
sexe: genre
sexe: sexe
M: masculin
F: féminin
allocataires:
libelle: allocataires
<<: *personne

View file

@ -274,6 +274,12 @@ en:
taken: is already used for procedure. You cannot use it because it belongs to another administrator.
# taken_can_be_claimed: est identique à celui dune autre de vos procedures publiées. Si vous publiez cette procedure, lancienne sera dépubliée et ne sera plus accessible au public. Les utilisateurs qui ont commencé un brouillon vont pouvoir le déposer.
invalid: is not valid. It must countain between 3 and 50 characters among a-z, 0-9, '_' and '-'.
"champs/cnaf_champ":
attributes:
numero_allocataire:
invalid: "must be a maximum of 7 digits"
code_postal:
invalid: "must be 5 characters long"
errors:
messages:
dossier_not_found: "The file does not exist or you do not have access to it."

View file

@ -280,6 +280,12 @@ fr:
taken: est déjà utilisé par une démarche. Vous ne pouvez pas lutiliser car il appartient à un autre administrateur.
taken_can_be_claimed: est identique à celui dune autre de vos démarches publiées. Si vous publiez cette démarche, lancienne sera dépubliée et ne sera plus accessible au public. Les utilisateurs qui ont commencé un brouillon vont pouvoir le déposer.
invalid: nest pas valide. Il doit comporter au moins 3 caractères, au plus 50 caractères et seuls les caractères a-z, 0-9, '_' et '-' sont autorisés.
"champs/cnaf_champ":
attributes:
numero_allocataire:
invalid: "doit être composé au maximum de 7 chiffres"
code_postal:
invalid: "doit posséder 5 caractères"
errors:
messages:
saml_not_authorized: "Vous nêtes pas autorisé à accéder à ce service."

View file

@ -1,6 +0,0 @@
fr:
activerecord:
attributes:
champ:
value: La valeur du champ
piece_justificative_file: La pièce justificative

View file

@ -36,3 +36,5 @@ fr:
titre_identite: 'Titre identité'
iban: 'Iban'
annuaire_education: 'Annuaire de léducation'
cnaf: 'Données de la Caisse nationale des allocations familiales'

View file

@ -0,0 +1,16 @@
en:
shared:
dossiers:
editable_champs:
cnaf:
numero_allocataire_label: CAF benefit number
numero_allocataire_notice: It is usually composed of 7 digits.
code_postal_label: postal code
code_postal_notice: It is usually composed of 5 digits.
champs:
cnaf:
show:
not_filled: not filled
fetching_data: "Fetching data for recipient No. %{numero_allocataire} with postal code %{code_postal}."
data_fetched: "Data concerning %{sources} linked to the account Nº %{numero_allocataire} with the postal code %{code_postal} has been received from the CAF."
data_fetched_title: "Data received from la Caisse nationale dallocations familiales"

View file

@ -0,0 +1,16 @@
fr:
shared:
dossiers:
editable_champs:
cnaf:
numero_allocataire_label: Le numéro dallocataire CAF
numero_allocataire_notice: Il est généralement composé de 7 chiffres.
code_postal_label: Le code postal
code_postal_notice: Il est généralement composé de 5 chiffres.
champs:
cnaf:
show:
not_filled: non renseigné
fetching_data: "La récupération automatique des données pour lallocataire Nº %{numero_allocataire} avec le code postal %{code_postal} est en cours."
data_fetched: "Des données concernant %{sources} liées au compte Nº %{numero_allocataire} avec le code postal %{code_postal} ont été reçues depuis la CAF."
data_fetched_title: "Données obtenues de la Caisse nationale dallocations familiales"

View file

@ -0,0 +1,5 @@
class AddValueJSONColumnToChamp < ActiveRecord::Migration[6.1]
def change
add_column :champs, :value_json, :jsonb
end
end

View file

@ -189,6 +189,7 @@ ActiveRecord::Schema.define(version: 2021_10_06_164955) do
t.jsonb "data"
t.string "external_id"
t.string "fetch_external_data_exceptions", array: true
t.jsonb "value_json"
t.index ["dossier_id"], name: "index_champs_on_dossier_id"
t.index ["parent_id"], name: "index_champs_on_parent_id"
t.index ["private"], name: "index_champs_on_private"

View file

@ -185,6 +185,10 @@ FactoryBot.define do
type_de_champ { association :type_de_champ_annuaire_education, procedure: dossier.procedure }
end
factory :champ_cnaf, class: 'Champs::CnafChamp' do
type_de_champ { association :type_de_champ_cnaf, procedure: dossier.procedure }
end
factory :champ_siret, class: 'Champs::SiretChamp' do
association :type_de_champ, factory: [:type_de_champ_siret]
association :etablissement, factory: [:etablissement]

View file

@ -205,6 +205,12 @@ FactoryBot.define do
end
end
trait :with_cnaf do
after(:build) do |procedure, _evaluator|
build(:type_de_champ_cnaf, procedure: procedure)
end
end
trait :with_explication do
after(:build) do |procedure, _evaluator|
build(:type_de_champ_explication, procedure: procedure)

View file

@ -151,6 +151,9 @@ FactoryBot.define do
factory :type_de_champ_annuaire_education do
type_champ { TypeDeChamp.type_champs.fetch(:annuaire_education) }
end
factory :type_de_champ_cnaf do
type_champ { TypeDeChamp.type_champs.fetch(:cnaf) }
end
factory :type_de_champ_carte do
type_champ { TypeDeChamp.type_champs.fetch(:carte) }
end

View file

@ -0,0 +1,167 @@
feature 'fetch API Particulier Data', js: true do
let(:administrateur) { create(:administrateur) }
let(:expected_token) { 'd7e9c9f4c3ca00caadde31f50fd4521a' }
let(:expected_sources) do
{
"cnaf" =>
{
"adresse" => ["identite", "complementIdentite", "complementIdentiteGeo", "numeroRue", "lieuDit", "codePostalVille", "pays"],
"allocataires" => ["nomPrenom", "dateDeNaissance", "sexe"],
"enfants" => ["nomPrenom", "dateDeNaissance", "sexe"],
"quotient_familial" => ["quotientFamilial", "annee", "mois"]
}
}
end
before do
stub_const("API_PARTICULIER_URL", "https://particulier.api.gouv.fr/api")
Flipper.enable(:api_particulier)
end
context "when an administrateur is logged" do
let(:procedure) do
create(:procedure, :with_service, :with_instructeur,
aasm_state: :brouillon,
administrateurs: [administrateur],
libelle: "libellé de la procédure",
path: "libelle-de-la-procedure")
end
before { login_as administrateur.user, scope: :user }
scenario 'it can enable api particulier' do
visit admin_procedure_path(procedure)
expect(page).to have_content("Configurer le jeton API particulier")
find('#api-particulier').click
expect(page).to have_current_path(admin_procedure_api_particulier_path(procedure))
find('#add-jeton').click
expect(page).to have_current_path(admin_procedure_api_particulier_jeton_path(procedure))
fill_in 'procedure_api_particulier_token', with: expected_token
VCR.use_cassette("api_particulier/success/introspect") { click_on 'Enregistrer' }
expect(page).to have_text('Le jeton a bien été mis à jour')
expect(page).to have_current_path(admin_procedure_api_particulier_sources_path(procedure))
['allocataires', 'enfants'].each do |scope|
within("##{scope}") do
check('noms et prénoms')
check('date de naissance')
check('sexe')
end
end
within("#adresse") do
check('identité')
check('complément didentité')
check('complément didentité géographique')
check('numéro et rue')
check('lieu-dit')
check('code postal et ville')
check('pays')
end
within("#quotient_familial") do
check('quotient familial')
check('année')
check('mois')
end
click_on "Enregistrer"
within("#enfants") do
expect(find('input[value=nomPrenom]')).to be_checked
end
expect(procedure.reload.api_particulier_sources).to eq(expected_sources)
visit champs_admin_procedure_path(procedure)
add_champ
select('Données de la Caisse nationale des allocations familiales', from: 'champ-0-type_champ')
fill_in 'champ-0-libelle', with: 'libellé de champ'
blur
expect(page).to have_content('Formulaire enregistré')
visit admin_procedure_path(procedure)
find('#publish-procedure-link').click
expect(find_field('procedure_path').value).to eq procedure.path
fill_in 'lien_site_web', with: 'http://some.website'
click_on 'Publier'
expect(page).to have_text('Démarche publiée')
end
end
context 'when an user is logged' do
let(:user) { create(:user) }
let(:api_particulier_token) { '29eb50b65f64e8e00c0847a8bbcbd150e1f847' }
let(:numero_allocataire) { '5843972' }
let(:code_postal) { '92110' }
let(:instructeur) { create(:instructeur) }
let(:procedure) do
create(:procedure, :for_individual, :with_service, :with_cnaf, :published,
libelle: "libellé de la procédure",
path: "libelle-de-la-procedure",
instructeurs: [instructeur],
api_particulier_sources: expected_sources,
api_particulier_token: api_particulier_token)
end
before { login_as user, scope: :user }
scenario 'it can fill an cnaf champ' do
visit commencer_path(path: procedure.path)
click_on 'Commencer la démarche'
choose 'Monsieur'
fill_in 'individual_nom', with: 'Nom'
fill_in 'individual_prenom', with: 'Prenom'
click_button('Continuer')
fill_in 'Le numéro dallocataire CAF', with: numero_allocataire
fill_in 'Le code postal', with: 'wrong_code'
blur
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
dossier = Dossier.last
expect(dossier.champs.first.code_postal).to eq('wrong_code')
click_on 'Déposer le dossier'
expect(page).to have_content(/code postal doit posséder 5 caractères/)
fill_in 'Le code postal', with: code_postal
VCR.use_cassette("api_particulier/success/composition_familiale") do
perform_enqueued_jobs { click_on 'Déposer le dossier' }
end
visit demande_dossier_path(dossier)
expect(page).to have_content(/Des données.*ont été reçues depuis la CAF/)
log_out
login_as instructeur.user, scope: :user
visit instructeur_dossier_path(procedure, dossier)
expect(page).to have_content('code postal et ville 92110 Clichy')
expect(page).to have_content('identité Mr SNOW Eric')
expect(page).to have_content('complément didentité ne connait rien')
expect(page).to have_content('numéro et rue 109 rue La Boétie')
expect(page).to have_content('pays FRANCE')
expect(page).to have_content('complément didentité géographique au nord de paris')
expect(page).to have_content('lieu-dit glagla')
expect(page).to have_content('ERIC SNOW masculin 07/01/1991')
expect(page).to have_content('SANSA SNOW féminin 15/01/1992')
expect(page).to have_content('PAUL SNOW masculin 04/01/2018')
expect(page).to have_content('1856 6 2021')
end
end
end

View file

@ -63,7 +63,7 @@ feature 'The routing', js: true do
# publish
publish_procedure(procedure)
log_out(old_layout: true)
log_out
# 2 users fill a dossier in each group
user_send_dossier(scientifique_user, 'scientifique')
@ -222,15 +222,4 @@ feature 'The routing', js: true do
expect(page).to have_text('Mot de passe enregistré')
end
def log_out(old_layout: false)
if old_layout
page.all('.dropdown-button').first.click
click_on 'Se déconnecter'
else
click_button(title: 'Mon compte')
click_on 'Se déconnecter'
end
expect(page).to have_current_path(root_path)
end
end

View file

@ -2,7 +2,7 @@
http_interactions:
- request:
method: get
uri: https://particulier-test.api.gouv.fr/api/v2/composition-familiale?codePostal=92110&numeroAllocataire=5843972
uri: https://particulier.api.gouv.fr/api/v2/composition-familiale?codePostal=92110&numeroAllocataire=5843972
body:
encoding: US-ASCII
string: ''

View file

@ -2,7 +2,7 @@
http_interactions:
- request:
method: get
uri: https://particulier-test.api.gouv.fr/api/v2/composition-familiale?codePostal=92110&numeroAllocataire=5843972
uri: https://particulier.api.gouv.fr/api/v2/composition-familiale?codePostal=92110&numeroAllocataire=5843972
body:
encoding: US-ASCII
string: ''

View file

@ -1,7 +1,7 @@
describe APIParticulier::CNAFAdapter do
describe APIParticulier::CnafAdapter do
let(:adapter) { described_class.new(api_particulier_token, numero_allocataire, code_postal, requested_sources) }
before { stub_const("API_PARTICULIER_URL", "https://particulier-test.api.gouv.fr/api") }
before { stub_const("API_PARTICULIER_URL", "https://particulier.api.gouv.fr/api") }
describe '#to_params' do
let(:api_particulier_token) { '29eb50b65f64e8e00c0847a8bbcbd150e1f847' }
@ -63,7 +63,7 @@ describe APIParticulier::CNAFAdapter do
context 'when no sources is requested' do
let(:requested_sources) { {} }
it { expect { subject }.to raise_error(APIParticulier::CNAFAdapter::InvalidSchemaError) }
it { expect { subject }.to raise_error(APIParticulier::CnafAdapter::InvalidSchemaError) }
end
end
end

View file

@ -31,6 +31,12 @@ describe APIParticulier::Services::SourcesService do
it { is_expected.to match(cnaf_allocataires_and_enfants) }
end
context 'when a procedure has an unknown scope' do
let(:api_particulier_scopes) { ['unknown_scope'] }
it { is_expected.to match({}) }
end
end
describe '#sanitize' do

View file

@ -0,0 +1,103 @@
describe Champs::CnafChamp, type: :model do
let(:champ) { described_class.new }
describe 'numero_allocataire and code_postal' do
before do
champ.numero_allocataire = '1234567'
champ.code_postal = '12345'
end
it 'saves numero_allocataire and code_postal' do
expect(champ.numero_allocataire).to eq('1234567')
expect(champ.code_postal).to eq('12345')
end
end
describe 'external_id' do
context 'when only one data is given' do
before do
champ.numero_allocataire = '1234567'
champ.save
end
it { expect(champ.external_id).to be_nil }
end
context 'when all data required for an external fetch are given' do
before do
champ.numero_allocataire = '1234567'
champ.code_postal = '12345'
champ.save
end
it { expect(JSON.parse(champ.external_id)).to eq({ "code_postal" => "12345", "numero_allocataire" => "1234567" }) }
end
end
describe '#validate' do
let(:numero_allocataire) { '1234567' }
let(:code_postal) { '12345' }
let(:champ) { described_class.new(dossier: create(:dossier), type_de_champ: create(:type_de_champ_cnaf)) }
let(:validation_context) { :create }
subject { champ.valid?(validation_context) }
before do
champ.numero_allocataire = numero_allocataire
champ.code_postal = code_postal
end
context 'when numero_allocataire and code_postal are valids' do
it { is_expected.to be true }
end
context 'when numero_allocataire and code_postal are nil' do
let(:numero_allocataire) { nil }
let(:code_postal) { nil }
it { is_expected.to be true }
end
context 'when only code_postal is nil' do
let(:code_postal) { nil }
it do
is_expected.to be false
expect(champ.errors.full_messages).to eq(["Code postal doit posséder 5 caractères"])
end
end
context 'when only numero_allocataire is nil' do
let(:numero_allocataire) { nil }
it do
is_expected.to be false
expect(champ.errors.full_messages).to eq(["Numero allocataire doit être composé au maximum de 7 chiffres"])
end
end
context 'when numero_allocataire is invalid' do
let(:numero_allocataire) { '123456a' }
it do
is_expected.to be false
expect(champ.errors.full_messages).to eq(["Numero allocataire doit être composé au maximum de 7 chiffres"])
end
context 'and the validation_context is :brouillon' do
let(:validation_context) { :brouillon }
it { is_expected.to be true }
end
end
context 'when code_postal is invalid' do
let(:code_postal) { '123456' }
it do
is_expected.to be false
expect(champ.errors.full_messages).to eq(["Code postal doit posséder 5 caractères"])
end
end
end
end

View file

@ -77,6 +77,7 @@ describe ProcedureExportService do
"titre_identite",
"iban",
"annuaire_education",
"cnaf",
"text"
]
end
@ -164,6 +165,7 @@ describe ProcedureExportService do
"titre_identite",
"iban",
"annuaire_education",
"cnaf",
"text"
]
end
@ -247,6 +249,7 @@ describe ProcedureExportService do
"titre_identite",
"iban",
"annuaire_education",
"cnaf",
"text"
]
end

View file

@ -142,6 +142,13 @@ module FeatureHelpers
have_css("##{form_id_for(libelle)}[value=\"#{with}\"]")
end
def log_out
click_button(title: 'Mon compte')
click_on 'Se déconnecter'
expect(page).to have_current_path(root_path)
end
# Keep the brower window open after a test success of failure, to
# allow inspecting the page or the console.
#