Merge pull request #6725 from betagouv/main

This commit is contained in:
Pierre de La Morinerie 2021-12-07 12:55:43 +01:00 committed by GitHub
commit 4b35e4a328
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 1114 additions and 46 deletions

View file

@ -0,0 +1,30 @@
@import "constants";
@import "colors";
table.dgfip {
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

@ -357,7 +357,8 @@
} }
} }
.cnaf-inputs { .cnaf-inputs,
.dgfip-inputs {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;

View file

@ -348,8 +348,8 @@ module Users
def champs_params def champs_params
params.permit(dossier: { params.permit(dossier: {
champs_attributes: [ champs_attributes: [
:id, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, :departement, :code_departement, value: [], :id, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :numero_fiscal, :reference_avis, :piece_justificative_file, :departement, :code_departement, value: [],
champs_attributes: [:id, :_destroy, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, :departement, :code_departement, value: []] champs_attributes: [:id, :_destroy, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :numero_fiscal, :reference_avis, :piece_justificative_file, :departement, :code_departement, value: []]
] ]
}) })
end end

View file

@ -1933,6 +1933,11 @@ enum TypeDeChamp {
""" """
departements departements
"""
Données de la Direction générale des Finances publiques
"""
dgfip
""" """
Lien vers un autre dossier Lien vers un autre dossier
""" """

View file

@ -3,6 +3,7 @@ class APIParticulier::API
INTROSPECT_RESOURCE_NAME = "introspect" INTROSPECT_RESOURCE_NAME = "introspect"
COMPOSITION_FAMILIALE_RESOURCE_NAME = "v2/composition-familiale" COMPOSITION_FAMILIALE_RESOURCE_NAME = "v2/composition-familiale"
AVIS_IMPOSITION_RESOURCE_NAME = "v2/avis-imposition"
TIMEOUT = 20 TIMEOUT = 20
@ -20,6 +21,14 @@ class APIParticulier::API
codePostal: code_postal) codePostal: code_postal)
end end
def avis_imposition(numero_fiscal, reference_avis)
# NOTE: Il est possible que l'utilisateur ajoute une quatorzième lettre à la fin de sa référence d'avis.
# Il s'agit d'une clé de vérification qu'il est nécessaire de'enlever avant de contacter API Particulier.
get(AVIS_IMPOSITION_RESOURCE_NAME,
numeroFiscal: numero_fiscal.to_i.to_s.rjust(13, "0"),
referenceAvis: reference_avis.to_i.to_s.rjust(13, "0"))
end
private private
def get(resource_name, params = {}) def get(resource_name, params = {})

View file

@ -0,0 +1,49 @@
class APIParticulier::DgfipAdapter
class InvalidSchemaError < ::StandardError
def initialize(errors)
super(errors.map(&:to_json).join("\n"))
end
end
def initialize(api_particulier_token, numero_fiscal, reference_avis, requested_sources)
@api = APIParticulier::API.new(api_particulier_token)
@numero_fiscal = numero_fiscal
@reference_avis = reference_avis
@requested_sources = requested_sources
end
def to_params
@api.avis_imposition(@numero_fiscal, @reference_avis)
.tap { |d| ensure_valid_schema!(d) }
.then { |d| extract_requested_sources(d) }
end
private
def ensure_valid_schema!(data)
if !schemer.valid?(data)
errors = schemer.validate(data).to_a
raise InvalidSchemaError.new(errors)
end
end
def schemer
@schemer ||= JSONSchemer.schema(Rails.root.join('app/schemas/avis-imposition.json'))
end
def extract_requested_sources(data)
@requested_sources['dgfip']&.map do |(scope, sources)|
case scope
when 'foyer_fiscal'
{ scope => data['foyerFiscal'].slice(*sources).merge(data.slice(*sources)) }
when 'declarant1', 'declarant2'
sources.map { |source| { scope => data[scope].slice(*source) } }
when 'agregats_fiscaux', 'echeance_avis', 'complements'
sources.map { |source| { scope => data.slice(*source) } }
else
{ scope => data.slice(*sources) }
end
end
&.flatten&.reduce(&:deep_merge) || {}
end
end

View file

@ -10,7 +10,7 @@ module APIParticulier
.map { |provider_and_scope| raw_scopes[provider_and_scope] } .map { |provider_and_scope| raw_scopes[provider_and_scope] }
.compact .compact
.map { |provider, scope| extract_sources(provider, scope) } .map { |provider, scope| extract_sources(provider, scope) }
.reduce({}) { |acc, el| acc.deep_merge(el) } .reduce({}) { |acc, el| acc.deep_merge(el) { |_, this_val, other_val| this_val + other_val } }
end end
# Remove sources not available for the procedure # Remove sources not available for the procedure
@ -38,7 +38,14 @@ module APIParticulier
end end
def extract_sources(provider, scope) def extract_sources(provider, scope)
{ provider => { scope => providers[provider][scope] } } provider_scope_value = providers[provider][scope]
case provider_scope_value
when Hash
{ provider => provider_scope_value }
else
{ provider => { scope => Array(provider_scope_value) } }
end
end end
def raw_scopes def raw_scopes
@ -46,7 +53,31 @@ module APIParticulier
'cnaf_allocataires' => ['cnaf', 'allocataires'], 'cnaf_allocataires' => ['cnaf', 'allocataires'],
'cnaf_enfants' => ['cnaf', 'enfants'], 'cnaf_enfants' => ['cnaf', 'enfants'],
'cnaf_adresse' => ['cnaf', 'adresse'], 'cnaf_adresse' => ['cnaf', 'adresse'],
'cnaf_quotient_familial' => ['cnaf', 'quotient_familial'] 'cnaf_quotient_familial' => ['cnaf', 'quotient_familial'],
'dgfip_declarant1_nom' => ['dgfip', 'declarant1_nom'],
'dgfip_declarant1_nom_naissance' => ['dgfip', 'declarant1_nom_naissance'],
'dgfip_declarant1_prenoms' => ['dgfip', 'declarant1_prenoms'],
'dgfip_declarant1_date_naissance' => ['dgfip', 'declarant1_date_naissance'],
'dgfip_declarant2_nom' => ['dgfip', 'declarant2_nom'],
'dgfip_declarant2_nom_naissance' => ['dgfip', 'declarant2_nom_naissance'],
'dgfip_declarant2_prenoms' => ['dgfip', 'declarant2_prenoms'],
'dgfip_declarant2_date_naissance' => ['dgfip', 'declarant2_date_naissance'],
'dgfip_date_recouvrement' => ['dgfip', 'date_recouvrement'],
'dgfip_date_etablissement' => ['dgfip', 'date_etablissement'],
'dgfip_adresse_fiscale_taxation' => ['dgfip', 'adresse_fiscale_taxation'],
'dgfip_adresse_fiscale_annee' => ['dgfip', 'adresse_fiscale_annee'],
'dgfip_nombre_parts' => ['dgfip', 'nombre_parts'],
'dgfip_nombre_personnes_a_charge' => ['dgfip', 'nombre_personnes_a_charge'],
'dgfip_situation_familiale' => ['dgfip', 'situation_familiale'],
'dgfip_revenu_brut_global' => ['dgfip', 'revenu_brut_global'],
'dgfip_revenu_imposable' => ['dgfip', 'revenu_imposable'],
'dgfip_impot_revenu_net_avant_corrections' => ['dgfip', 'impot_revenu_net_avant_corrections'],
'dgfip_montant_impot' => ['dgfip', 'montant_impot'],
'dgfip_revenu_fiscal_reference' => ['dgfip', 'revenu_fiscal_reference'],
'dgfip_annee_impot' => ['dgfip', 'annee_impot'],
'dgfip_annee_revenus' => ['dgfip', 'annee_revenus'],
'dgfip_erreur_correctif' => ['dgfip', 'erreur_correctif'],
'dgfip_situation_partielle' => ['dgfip', 'situation_partielle']
} }
end end
@ -57,6 +88,32 @@ module APIParticulier
'enfants' => ['nomPrenom', 'dateDeNaissance', 'sexe'], 'enfants' => ['nomPrenom', 'dateDeNaissance', 'sexe'],
'adresse' => ['identite', 'complementIdentite', 'complementIdentiteGeo', 'numeroRue', 'lieuDit', 'codePostalVille', 'pays'], 'adresse' => ['identite', 'complementIdentite', 'complementIdentiteGeo', 'numeroRue', 'lieuDit', 'codePostalVille', 'pays'],
'quotient_familial' => ['quotientFamilial', 'annee', 'mois'] 'quotient_familial' => ['quotientFamilial', 'annee', 'mois']
},
'dgfip' => {
'declarant1_nom' => { 'declarant1' => ['nom'] },
'declarant1_nom_naissance' => { 'declarant1' => ['nomNaissance'] },
'declarant1_prenoms' => { 'declarant1' => ['prenoms'] },
'declarant1_date_naissance' => { 'declarant1' => ['dateNaissance'] },
'declarant2_nom' => { 'declarant2' => ['nom'] },
'declarant2_nom_naissance' => { 'declarant2' => ['nomNaissance'] },
'declarant2_prenoms' => { 'declarant2' => ['prenoms'] },
'declarant2_date_naissance' => { 'declarant2' => ['dateNaissance'] },
'date_recouvrement' => { 'echeance_avis' => ['dateRecouvrement'] },
'date_etablissement' => { 'echeance_avis' => ['dateEtablissement'] },
'adresse_fiscale_taxation' => { 'foyer_fiscal' => ['adresse'] },
'adresse_fiscale_annee' => { 'foyer_fiscal' => ['annee'] },
'nombre_parts' => { 'foyer_fiscal' => ['nombreParts'] },
'nombre_personnes_a_charge' => { 'foyer_fiscal' => ['nombrePersonnesCharge'] },
'situation_familiale' => { 'foyer_fiscal' => ['situationFamille'] },
'revenu_brut_global' => { 'agregats_fiscaux' => ['revenuBrutGlobal'] },
'revenu_imposable' => { 'agregats_fiscaux' => ['revenuImposable'] },
'impot_revenu_net_avant_corrections' => { 'agregats_fiscaux' => ['impotRevenuNetAvantCorrections'] },
'montant_impot' => { 'agregats_fiscaux' => ['montantImpot'] },
'revenu_fiscal_reference' => { 'agregats_fiscaux' => ['revenuFiscalReference'] },
'annee_impot' => { 'agregats_fiscaux' => ['anneeImpots'] },
'annee_revenus' => { 'agregats_fiscaux' => ['anneeRevenus'] },
'erreur_correctif' => { 'complements' => ['erreurCorrectif'] },
'situation_partielle' => { 'complements' => ['situationPartielle'] }
} }
} }
end end

View file

@ -0,0 +1,53 @@
# == 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
# rebased_at :datetime
# 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::DgfipChamp < Champs::TextChamp
# see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/dgfip-input-validation.middleware.ts
validates :numero_fiscal, format: { with: /\A\w{13,14}\z/ }, if: -> { reference_avis.present? && validation_context != :brouillon }
validates :reference_avis, format: { with: /\A\w{13,14}\z/ }, if: -> { numero_fiscal.present? && validation_context != :brouillon }
store_accessor :value_json, :numero_fiscal, :reference_avis
def blank?
external_id.nil?
end
def fetch_external_data?
true
end
def fetch_external_data
if valid?
APIParticulier::DgfipAdapter.new(
procedure.api_particulier_token,
numero_fiscal,
reference_avis,
procedure.api_particulier_sources
).to_params
end
end
def external_id
if numero_fiscal.present? && reference_avis.present?
{ reference_avis: reference_avis, numero_fiscal: numero_fiscal }.to_json
end
end
end

View file

@ -560,7 +560,7 @@ class Dossier < ApplicationRecord
approximative_expiration_date_reference, approximative_expiration_date_reference,
conservation_extension, conservation_extension,
procedure.duree_conservation_dossiers_dans_ds.months procedure.duree_conservation_dossiers_dans_ds.months
].sum - REMAINING_WEEKS_BEFORE_EXPIRATION.weeks ].compact.sum - REMAINING_WEEKS_BEFORE_EXPIRATION.weeks
end end
def close_to_expiration? def close_to_expiration?

View file

@ -726,6 +726,10 @@ class Procedure < ApplicationRecord
api_particulier_sources['cnaf'].present? api_particulier_sources['cnaf'].present?
end end
def dgfip_enabled?
api_particulier_sources['dgfip'].present?
end
private private
def validate_for_publication? def validate_for_publication?

View file

@ -50,7 +50,8 @@ class TypeDeChamp < ApplicationRecord
titre_identite: 'titre_identite', titre_identite: 'titre_identite',
iban: 'iban', iban: 'iban',
annuaire_education: 'annuaire_education', annuaire_education: 'annuaire_education',
cnaf: 'cnaf' cnaf: 'cnaf',
dgfip: 'dgfip'
} }
belongs_to :revision, class_name: 'ProcedureRevision', optional: true belongs_to :revision, class_name: 'ProcedureRevision', optional: true
@ -324,6 +325,8 @@ class TypeDeChamp < ApplicationRecord
has_legacy_number has_legacy_number
when TypeDeChamp.type_champs.fetch(:cnaf) when TypeDeChamp.type_champs.fetch(:cnaf)
procedure.cnaf_enabled? procedure.cnaf_enabled?
when TypeDeChamp.type_champs.fetch(:dgfip)
procedure.dgfip_enabled?
else else
true true
end end

View file

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

View file

@ -0,0 +1,81 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://demarches-simplifiees.fr/avis-imposition.schema.json",
"title": "avis imposition",
"type": "object",
"properties": {
"declarant1": {
"$ref": "#/$defs/declarant"
},
"declarant2": {
"$ref": "#/$defs/declarant"
},
"dateRecouvrement": {
"type": "string",
"pattern": "^[0-9]{2}/[0-9]{2}/[0-9]{4}$"
},
"dateEtablissement": {
"type": "string",
"pattern": "^[0-9]{2}/[0-9]{2}/[0-9]{4}$"
},
"nombreParts": {
"type": "number"
},
"situationFamille": {
"type": "string"
},
"revenuBrutGlobal": {
"type": "number",
"nullable": true
},
"revenuImposable": {
"type": "number",
"nullable": true
},
"impotRevenuNetAvantCorrections": {
"type": "number",
"nullable": true
},
"montantImpot": {
"type": "number",
"nullable": true
},
"revenuFiscalReference": {
"type": "number",
"nullable": true
},
"nombrePersonnesCharge": {
"type": "integer"
},
"anneeImpots": {
"type": "string",
"pattern": "^[0-9]{4}$"
},
"anneeRevenus": {
"type": "string",
"pattern": "^[0-9]{4}$"
},
"erreurCorrectif": {
"type": "string"
},
"situationPartielle": {
"type": "string"
}
},
"$defs": {
"declarant": {
"type": "object",
"properties": {
"nom": {
"type": "string"
},
"nomNaissance": {
"type": "string"
},
"prenoms": {
"type": "string"
}
}
}
}
}

View file

@ -0,0 +1,6 @@
%table.dgfip
%caption #{t("api_particulier.providers.dgfip.scopes.#{scope}.libelle")} :
- agregats_fiscaux.slice('revenuBrutGlobal', 'revenuImposable', 'impotRevenuNetAvantCorrections', 'montantImpot', 'revenuFiscalReference', 'anneeImpots', 'anneeRevenus', 'erreurCorrectif', 'situationPartielle').keys.each do |key|
%tr
%th= t("api_particulier.providers.dgfip.scopes.#{scope}.#{key}")
%td= agregats_fiscaux[key]

View file

@ -0,0 +1,6 @@
%table.dgfip
%caption #{t("api_particulier.providers.dgfip.scopes.#{scope}.libelle")} :
- complements.slice('erreurCorrectif', 'situationPartielle').keys.each do |key|
%tr
%th= t("api_particulier.providers.dgfip.scopes.#{scope}.#{key}")
%td= complements[key]

View file

@ -0,0 +1,6 @@
%table.dgfip
%caption #{t("api_particulier.providers.dgfip.scopes.#{scope}.libelle")} :
- declarant.slice('nom', 'nomNaissance', 'prenoms', 'dateNaissance').keys.each do |key|
%tr
%th= t("api_particulier.providers.dgfip.scopes.#{scope}.#{key}")
%td= declarant[key]

View file

@ -0,0 +1,6 @@
%table.dgfip
%caption #{t("api_particulier.providers.dgfip.scopes.#{scope}.libelle")} :
- echeance_avis.slice('dateRecouvrement', 'dateEtablissement').keys.each do |key|
%tr
%th= t("api_particulier.providers.dgfip.scopes.#{scope}.#{key}")
%td= echeance_avis[key]

View file

@ -0,0 +1,6 @@
%table.dgfip
%caption #{t("api_particulier.providers.dgfip.scopes.#{scope}.libelle")} :
- foyer_fiscal.slice('annee', 'adresse', 'nombreParts', 'situationFamille', 'nombrePersonnesCharge').keys.each do |key|
%tr
%th= t("api_particulier.providers.dgfip.scopes.#{scope}.#{key}")
%td= foyer_fiscal[key]

View file

@ -0,0 +1,30 @@
- if champ.blank?
%p= t('.not_filled')
- elsif champ.data.blank?
%p= t('.fetching_data',
numero_fiscal: champ.numero_fiscal,
reference_avis: champ.reference_avis)
- else
- if profile == 'usager'
- sources = champ.procedure.api_particulier_sources['dgfip'].keys
- i18n_sources = sources.map { |s| I18n.t("#{s}.libelle", scope: 'api_particulier.providers.dgfip.scopes') }
%p= t('.data_fetched',
sources: i18n_sources.to_sentence,
numero_fiscal: champ.numero_fiscal,
reference_avis: champ.reference_avis)
- if profile == 'instructeur'
%p= t('.data_fetched_title')
- champ.data.slice('declarant1', 'declarant2', 'echeance_avis', 'foyer_fiscal', 'agregats_fiscaux', 'complements').keys.each do |scope|
- case scope
- when 'declarant1', 'declarant2'
= render partial: 'shared/champs/dgfip/declarant', locals: { scope: scope, declarant: champ.data[scope] }
- when 'echeance_avis'
= render partial: 'shared/champs/dgfip/echeance_avis', locals: { scope: scope, echeance_avis: champ.data[scope] }
- when 'foyer_fiscal'
= render partial: 'shared/champs/dgfip/foyer_fiscal', locals: { scope: scope, foyer_fiscal: champ.data[scope] }
- when 'agregats_fiscaux'
= render partial: 'shared/champs/dgfip/agregats_fiscaux', locals: { scope: scope, agregats_fiscaux: champ.data[scope] }
- when 'complements'
= render partial: 'shared/champs/dgfip/complements', locals: { scope: scope, complements: champ.data[scope] }

View file

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

View file

@ -0,0 +1,16 @@
.dgfip-inputs
%div
= form.label :numero_fiscal, t('.numero_fiscal_label')
%p.notice= t('.numero_fiscal_notice')
= form.text_field :numero_fiscal,
required: champ.mandatory?,
size: 14,
aria: { describedby: describedby_id(champ) }
%div
= form.label :reference_avis, t('.reference_avis_label')
%p.notice= t('.reference_avis_notice')
= form.text_field :reference_avis,
size: 14,
required: champ.mandatory?,
aria: { describedby: describedby_id(champ) }

View file

@ -30,3 +30,41 @@ en:
quotientFamilial: quotient familial quotientFamilial: quotient familial
mois: month mois: month
annee: year annee: year
dgfip:
libelle: Direction Générale des Finances Publiques (DGFiP)
scopes:
declarant: &declarant
nom: name
nomNaissance: birth name
prenoms: firstname
dateNaissance: birth date
declarant1:
libelle: declarant 1
<<: *declarant
declarant2:
libelle: declarant 2
<<: *declarant
echeance_avis:
libelle: expiry of the notice
dateEtablissement: date of establishment
dateRecouvrement: recovery date
foyer_fiscal: &foyer_fiscal
libelle: taxable household
adresse: tax address for the year
annee: year
nombreParts: number of units
nombrePersonnesCharge: number of dependants
situationFamille: family status
agregats_fiscaux:
libelle: tax notice
revenuBrutGlobal: overall gross income
revenuImposable: taxable income
impotRevenuNetAvantCorrections: net income tax before correction
montantImpot: tax amount
revenuFiscalReference: reference tax income
anneeImpots: tax year
anneeRevenus: income year
complements:
libelle: supplements
erreurCorrectif: error correction
situationPartielle: partial status

View file

@ -30,3 +30,41 @@ fr:
quotientFamilial: quotient familial quotientFamilial: quotient familial
mois: mois mois: mois
annee: année annee: année
dgfip:
libelle: Direction Générale des Finances Publiques (DGFiP)
scopes:
declarant: &declarant
nom: nom
nomNaissance: nom de naissance
prenoms: prénoms
dateNaissance: date de naissance
declarant1:
libelle: déclarant 1
<<: *declarant
declarant2:
libelle: déclarant 2
<<: *declarant
echeance_avis:
libelle: échéance de lavis
dateEtablissement: date détablissement
dateRecouvrement: date de recouvrement
foyer_fiscal: &foyer_fiscal
libelle: foyer fiscal
adresse: adresse fiscale de lannée passée
annee: année
nombreParts: nombre de parts
nombrePersonnesCharge: nombre de personnes à charge
situationFamille: situation familiale
agregats_fiscaux:
libelle: agrégats fiscaux
revenuBrutGlobal: revenu brut global
revenuImposable: revenu imposable
impotRevenuNetAvantCorrections: impôt sur le revenu net avant correction
montantImpot: montant de limpôt
revenuFiscalReference: revenu fiscal de référence
anneeImpots: année dimposition
anneeRevenus: année des revenus
complements:
libelle: compléments
erreurCorrectif: erreur correctif
situationPartielle: situation partielle

View file

@ -291,6 +291,12 @@ en:
invalid: "must be a maximum of 7 digits" invalid: "must be a maximum of 7 digits"
code_postal: code_postal:
invalid: "must be 5 characters long" invalid: "must be 5 characters long"
"champs/dgfip_champ":
attributes:
numero_fiscal:
invalid: "must be 13 or 14 characters long"
reference_avis:
invalid: "must be 13 or 14 characters long"
errors: errors:
messages: messages:
dossier_not_found: "The file does not exist or you do not have access to it." dossier_not_found: "The file does not exist or you do not have access to it."

View file

@ -299,6 +299,12 @@ fr:
invalid: "doit être composé au maximum de 7 chiffres" invalid: "doit être composé au maximum de 7 chiffres"
code_postal: code_postal:
invalid: "doit posséder 5 caractères" invalid: "doit posséder 5 caractères"
"champs/dgfip_champ":
attributes:
numero_fiscal:
invalid: "doit posséder 13 ou 14 caractères"
reference_avis:
invalid: "doit posséder 13 ou 14 caractères"
errors: errors:
messages: messages:
saml_not_authorized: "Vous nêtes pas autorisé à accéder à ce service." saml_not_authorized: "Vous nêtes pas autorisé à accéder à ce service."
@ -414,12 +420,12 @@ fr:
jeton_particulier: jeton_particulier:
show: show:
configure_token: "Configurer le jeton API Particulier" configure_token: "Configurer le jeton API Particulier"
api_particulier_description_html: "%{app_name} utilise <a href=\"https://api.gouv.fr/les-api/api-particulier\">API Particulier</a> qui permet de récupérer les données familiales (CAF).<br />Renseignez ici le <a href=\"https://api.gouv.fr/les-api/api-particulier/demande-acces\">jeton API Particulier</a> propre à votre démarche." api_particulier_description_html: "%{app_name} utilise <a href=\"https://api.gouv.fr/les-api/api-particulier\">API Particulier</a> qui permet de récupérer les données fiscales (DGFiP) et familiales (CAF).<br />Renseignez ici le <a href=\"https://api.gouv.fr/les-api/api-particulier/demande-acces\">jeton API Particulier</a> propre à votre démarche."
token_description: "Il doit contenir au minimum 15 caractères." token_description: "Il doit contenir au minimum 15 caractères."
update: update:
token_ok: "Le jeton a bien été mis à jour" token_ok: "Le jeton a bien été mis à jour"
no_scopes_token: "Mise à jour impossible : le jeton n'a pas acces aux données.<br /><br />Vérifier le auprès de <a href='https://datapass.api.gouv.fr/'>https://datapass.api.gouv.fr/</a>" no_scopes_token: "Mise à jour impossible : le jeton n'a pas acces aux données.<br /><br />Vérifiez-le auprès de <a href='https://datapass.api.gouv.fr/'>https://datapass.api.gouv.fr/</a>"
not_found_token: "Mise à jour impossible : le jeton n'a pas été trouvé ou n'est pas actif<br /><br />Vérifier le auprès de <a href='https://datapass.api.gouv.fr/'>https://datapass.api.gouv.fr/</a>" not_found_token: "Mise à jour impossible : le jeton n'a pas été trouvé ou n'est pas actif<br /><br />Vérifiez-le auprès de <a href='https://datapass.api.gouv.fr/'>https://datapass.api.gouv.fr/</a>"
network_error: "Mise à jour impossible : une erreur réseau est survenue" network_error: "Mise à jour impossible : une erreur réseau est survenue"
api_particulier: api_particulier:
already_configured: "Déjà rempli" already_configured: "Déjà rempli"
@ -428,7 +434,7 @@ fr:
show: show:
title: "Définir les sources de données" title: "Définir les sources de données"
data_sources: "Sources de données" data_sources: "Sources de données"
explication_html: "<p>API Particulier facilite laccès des administrations aux données familiales (CAF) pour simplifier les démarches administratives mises en œuvre par les collectivités et les administrations.<br> Cela permet aux administrations daccéder à des informations certifiées à la source et ainsi : </p> <ul> <li>de saffranchir des pièces justificatives lors des démarches en ligne,</li> <li>de réduire le nombre derreurs de saisie,</li> <li>décarter le risque de fraude documentaire.</li> </ul> <p> <strong>Important&nbsp;:</strong> les disposition de l'article <a href='https://www.legifrance.gouv.fr/affichCodeArticle.do?cidTexte=LEGITEXT000031366350&amp;idArticle=LEGIARTI000031367412&amp;dateTexte=&amp;categorieLien=cid'>L144-8</a> nautorisent que léchange des informations strictement nécessaires pour traiter une démarche.<br /><br />En conséquence, ne sélectionnez ici que les données auxquelles vous aurez accès dun point de vue légal.</p>" explication_html: "<p>API Particulier facilite laccès des administrations aux données fiscales (DGFiP) et familiales (CAF) pour simplifier les démarches administratives mises en œuvre par les collectivités et les administrations.<br> Cela permet aux administrations daccéder à des informations certifiées à la source et ainsi : </p> <ul> <li>de saffranchir des pièces justificatives lors des démarches en ligne,</li> <li>de réduire le nombre derreurs de saisie,</li> <li>décarter le risque de fraude documentaire.</li> </ul> <p> <strong>Important&nbsp;:</strong> les disposition de l'article <a href='https://www.legifrance.gouv.fr/affichCodeArticle.do?cidTexte=LEGITEXT000031366350&amp;idArticle=LEGIARTI000031367412&amp;dateTexte=&amp;categorieLien=cid'>L144-8</a> nautorisent que léchange des informations strictement nécessaires pour traiter une démarche.<br /><br />En conséquence, ne sélectionnez ici que les données auxquelles vous aurez accès dun point de vue légal.</p>"
update: update:
sources_ok: 'Mise à jour effectuée' sources_ok: 'Mise à jour effectuée'
procedures: procedures:

View file

@ -37,4 +37,4 @@ fr:
iban: 'Iban' iban: 'Iban'
annuaire_education: 'Annuaire de léducation' annuaire_education: 'Annuaire de léducation'
cnaf: 'Données de la Caisse nationale des allocations familiales' cnaf: 'Données de la Caisse nationale des allocations familiales'
dgfip: 'Données de la Direction générale des Finances publiques'

View file

@ -7,6 +7,11 @@ en:
numero_allocataire_notice: It is usually composed of 7 digits. numero_allocataire_notice: It is usually composed of 7 digits.
code_postal_label: postal code code_postal_label: postal code
code_postal_notice: It is usually composed of 5 digits. code_postal_notice: It is usually composed of 5 digits.
dgfip:
numero_fiscal_label: Tax number
numero_fiscal_notice: It is usually composed of 13 to 14 characters.
reference_avis_label: Tax notice reference
reference_avis_notice: It is usually composed of 13 to 14 characters.
header: header:
expires_at: expires_at:
brouillon: "Expires at %{date} (%{duree_conservation_totale} months after the creation of this file)" brouillon: "Expires at %{date} (%{duree_conservation_totale} months after the creation of this file)"
@ -22,3 +27,9 @@ en:
fetching_data: "Fetching data for recipient No. %{numero_allocataire} with postal code %{code_postal}." 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: "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" data_fetched_title: "Data received from la Caisse nationale dallocations familiales"
dgfip:
show:
not_filled: not filled
fetching_data: "Fetching data for declarant No. %{numero_fiscal} with tax notice reference %{reference_avis}."
data_fetched: "Data concerning %{sources} linked to the declarant Nº %{numero_fiscal} with tax notice reference %{reference_avis} has been received from the DGFiP."
data_fetched_title: "Data received from la Direction générale des Finances publiques"

View file

@ -7,6 +7,11 @@ fr:
numero_allocataire_notice: Il est généralement composé de 7 chiffres. numero_allocataire_notice: Il est généralement composé de 7 chiffres.
code_postal_label: Le code postal code_postal_label: Le code postal
code_postal_notice: Il est généralement composé de 5 chiffres. code_postal_notice: Il est généralement composé de 5 chiffres.
dgfip:
numero_fiscal_label: Le numéro fiscal
numero_fiscal_notice: Il est généralement composé de 13 ou 14 caractères.
reference_avis_label: La référence d'avis d'imposition
reference_avis_notice: Elle est généralement composée de 13 ou 14 caractères.
header: header:
expires_at: expires_at:
brouillon: "Expirera le %{date} (%{duree_conservation_totale} mois après la création du dossier)" brouillon: "Expirera le %{date} (%{duree_conservation_totale} mois après la création du dossier)"
@ -24,3 +29,9 @@ fr:
fetching_data: "La récupération automatique des données pour lallocataire Nº %{numero_allocataire} avec le code postal %{code_postal} est en cours." 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: "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" data_fetched_title: "Données obtenues de la Caisse nationale dallocations familiales"
dgfip:
show:
not_filled: non renseigné
fetching_data: "La récupération automatique des données pour le déclarant Nº %{numero_fiscal} avec la référence d'avis %{reference_avis} est en cours."
data_fetched: "Des données concernant %{sources} liées au déclarant Nº %{numero_fiscal} avec la référence d'avis %{reference_avis} ont été reçues depuis la DGFiP."
data_fetched_title: "Données obtenues de la Direction générale des Finances publiques"

View file

@ -45,9 +45,39 @@ describe Administrateurs::JetonParticulierController, type: :controller do
it 'saves the jeton' do it 'saves the jeton' do
expect(flash.alert).to be_nil expect(flash.alert).to be_nil
expect(flash.notice).to eq("Le jeton a bien été mis à jour") expect(flash.notice).to eq("Le jeton a bien été mis à jour")
expect(procedure.reload.api_particulier_token).to eql(token) procedure.reload
expect(procedure.reload.api_particulier_scopes).to contain_exactly("dgfip_avis_imposition", "dgfip_adresse", "cnaf_allocataires", "cnaf_enfants", "cnaf_adresse", "cnaf_quotient_familial", "mesri_statut_etudiant") expect(procedure.api_particulier_token).to eql(token)
expect(procedure.reload.api_particulier_sources).to be_empty expect(procedure.api_particulier_scopes).to contain_exactly(
'cnaf_adresse',
'cnaf_allocataires',
'cnaf_enfants',
'cnaf_quotient_familial',
'dgfip_adresse_fiscale_annee',
'dgfip_adresse_fiscale_taxation',
'dgfip_annee_impot',
'dgfip_annee_revenus',
'dgfip_date_etablissement',
'dgfip_date_recouvrement',
'dgfip_declarant1_date_naissance',
'dgfip_declarant1_nom',
'dgfip_declarant1_nom_naissance',
'dgfip_declarant1_prenoms',
'dgfip_declarant2_date_naissance',
'dgfip_declarant2_nom',
'dgfip_declarant2_nom_naissance',
'dgfip_declarant2_prenoms',
'dgfip_erreur_correctif',
'dgfip_impot_revenu_net_avant_corrections',
'dgfip_montant_impot',
'dgfip_nombre_parts',
'dgfip_nombre_personnes_a_charge',
'dgfip_revenu_brut_global',
'dgfip_revenu_fiscal_reference',
'dgfip_revenu_imposable',
'dgfip_situation_familiale',
'dgfip_situation_partielle'
)
expect(procedure.api_particulier_sources).to be_empty
end end
end end

View file

@ -189,6 +189,10 @@ FactoryBot.define do
type_de_champ { association :type_de_champ_cnaf, procedure: dossier.procedure } type_de_champ { association :type_de_champ_cnaf, procedure: dossier.procedure }
end end
factory :champ_dgfip, class: 'Champs::DgfipChamp' do
type_de_champ { association :type_de_champ_dgfip, procedure: dossier.procedure }
end
factory :champ_siret, class: 'Champs::SiretChamp' do factory :champ_siret, class: 'Champs::SiretChamp' do
type_de_champ { association :type_de_champ_siret, procedure: dossier.procedure } type_de_champ { association :type_de_champ_siret, procedure: dossier.procedure }
association :etablissement, factory: [:etablissement] association :etablissement, factory: [:etablissement]

View file

@ -200,6 +200,12 @@ FactoryBot.define do
end end
end end
trait :with_dgfip do
after(:build) do |procedure, _evaluator|
build(:type_de_champ_dgfip, procedure: procedure)
end
end
trait :with_explication do trait :with_explication do
after(:build) do |procedure, _evaluator| after(:build) do |procedure, _evaluator|
build(:type_de_champ_explication, procedure: procedure) build(:type_de_champ_explication, procedure: procedure)

View file

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

View file

@ -0,0 +1,84 @@
---
http_interactions:
- request:
method: get
uri: https://particulier.api.gouv.fr/api/v2/avis-imposition?numeroFiscal=2097699999077&referenceAvis=2097699999077
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Accept:
- application/json
X-Api-Key:
- d7e9c9f4c3ca00caadde31f50fd4521a
Expect:
- ''
response:
status:
code: 200
message: OK
headers:
Date:
- Tue, 16 Mar 2021 17:01:19 GMT
Content-Type:
- application/json; charset=utf-8
Content-Length:
- '747'
Connection:
- keep-alive
Keep-Alive:
- timeout=5
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Expose-Headers:
- Range,Content-Range,X-Content-Range,X-API-Key
Etag:
- W/"2eb-A0NiRd+gbJKIAT0y4tR4j9tjXb0"
Server:
- nginx
Strict-Transport-Security:
- max-age=15552000
- max-age=15552000
Vary:
- Origin, Accept
X-Gravitee-Request-Id:
- 7bfb7f99-ac2d-4443-bb7f-99ac2d0443c5
X-Gravitee-Transaction-Id:
- f5dca8b3-2ab7-4c9a-9ca8-b32ab70c9a2b
body:
encoding: ASCII-8BIT
string: '{
"declarant1": {
"nom": "FERRI",
"nomNaissance": "FERRI",
"prenoms": "Karine",
"dateNaissance": "12/08/1978"
},
"declarant2": {
"nom": "",
"nomNaissance": "",
"prenoms": "",
"dateNaissance": "12/08/1978"
},
"dateRecouvrement": "09/10/2020",
"dateEtablissement": "07/07/2020",
"nombreParts": 1,
"situationFamille": "Célibataire",
"nombrePersonnesCharge": 0,
"revenuBrutGlobal": 38814,
"revenuImposable": 38814,
"impotRevenuNetAvantCorrections": 38814,
"montantImpot": 38814,
"revenuFiscalReference": 38814,
"foyerFiscal": {
"annee": 2020,
"adresse": "13 rue de la Plage 97615 Pamanzi"
},
"anneeImpots": "2020",
"anneeRevenus": "2020",
"situationPartielle": "SUP DOM"
}'
recorded_at: Tue, 16 Mar 2021 17:01:18 GMT
recorded_with: VCR 6.0.0

View file

@ -0,0 +1,84 @@
---
http_interactions:
- request:
method: get
uri: https://particulier.api.gouv.fr/api/v2/avis-imposition?numeroFiscal=2097699999077&referenceAvis=2097699999077
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Accept:
- application/json
X-Api-Key:
- d7e9c9f4c3ca00caadde31f50fd4521a
Expect:
- ''
response:
status:
code: 200
message: OK
headers:
Date:
- Tue, 16 Mar 2021 17:01:19 GMT
Content-Type:
- application/json; charset=utf-8
Content-Length:
- '747'
Connection:
- keep-alive
Keep-Alive:
- timeout=5
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Expose-Headers:
- Range,Content-Range,X-Content-Range,X-API-Key
Etag:
- W/"2eb-A0NiRd+gbJKIAT0y4tR4j9tjXb0"
Server:
- nginx
Strict-Transport-Security:
- max-age=15552000
- max-age=15552000
Vary:
- Origin, Accept
X-Gravitee-Request-Id:
- 7bfb7f99-ac2d-4443-bb7f-99ac2d0443c5
X-Gravitee-Transaction-Id:
- f5dca8b3-2ab7-4c9a-9ca8-b32ab70c9a2b
body:
encoding: ASCII-8BIT
string: '{
"declarant1": {
"nom": "FERRI",
"nomNaissance": "FERRI",
"prenoms": "Karine",
"dateNaissance": "12/08/1978"
},
"declarant2": {
"nom": "",
"nomNaissance": "",
"prenoms": "",
"dateNaissance": "12/08/1978"
},
"dateRecouvrement": "09/10/2020",
"dateEtablissement": "07/07/2020",
"nombreParts": 1,
"situationFamille": "Célibataire",
"nombrePersonnesCharge": 0,
"revenuBrutGlobal": 38814,
"revenuImposable": 38814,
"impotRevenuNetAvantCorrections": 38814,
"montantImpot": 38814,
"revenuFiscalReference": 38814,
"foyerFiscal": {
"annee": 2020,
"adresse": "13 rue de la Plage 97615 Pamanzi"
},
"anneeImpots": "99",
"anneeRevenus": "2020",
"situationPartielle": "SUP DOM"
}'
recorded_at: Tue, 16 Mar 2021 17:01:18 GMT
recorded_with: VCR 6.0.0

View file

@ -38,7 +38,6 @@ http_interactions:
- max-age=15552000 - max-age=15552000
body: body:
encoding: UTF-8 encoding: UTF-8
string: '{"_id":"1d99db5a-a099-4314-ad2f-2707c6b505a6","name":"Application de string: '{"_id":"1d99db5a-a099-4314-ad2f-2707c6b505a6","name":"Application de sandbox","scopes":["cnaf_allocataires","cnaf_enfants","cnaf_adresse","cnaf_quotient_familial","dgfip_declarant1_nom","dgfip_declarant1_nom_naissance","dgfip_declarant1_prenoms","dgfip_declarant1_date_naissance","dgfip_declarant2_nom","dgfip_declarant2_nom_naissance","dgfip_declarant2_prenoms","dgfip_declarant2_date_naissance","dgfip_date_recouvrement","dgfip_date_etablissement","dgfip_adresse_fiscale_taxation","dgfip_adresse_fiscale_annee","dgfip_nombre_parts","dgfip_nombre_personnes_a_charge","dgfip_situation_familiale","dgfip_revenu_brut_global","dgfip_revenu_imposable","dgfip_impot_revenu_net_avant_corrections","dgfip_montant_impot","dgfip_revenu_fiscal_reference","dgfip_annee_impot","dgfip_annee_revenus","dgfip_erreur_correctif","dgfip_situation_partielle"]}'
sandbox","scopes":["dgfip_avis_imposition","dgfip_adresse","cnaf_allocataires","cnaf_enfants","cnaf_adresse","cnaf_quotient_familial","mesri_statut_etudiant"]}'
recorded_at: Tue, 16 Mar 2021 15:25:24 GMT recorded_at: Tue, 16 Mar 2021 15:25:24 GMT
recorded_with: VCR 6.0.0 recorded_with: VCR 6.0.0

View file

@ -0,0 +1,37 @@
{
"declarant1": {
"nom": "FERRI",
"nomNaissance": "FERRI",
"prenoms": "Karine",
"dateNaissance": "12/08/1978"
},
"declarant2": {
"nom": "",
"nomNaissance": "",
"prenoms": "",
"dateNaissance": "12/08/1978"
},
"echeance_avis": {
"dateRecouvrement": "09/10/2020",
"dateEtablissement": "07/07/2020"
},
"foyer_fiscal": {
"annee": 2020,
"adresse": "13 rue de la Plage 97615 Pamanzi",
"nombreParts": 1,
"situationFamille": "Célibataire",
"nombrePersonnesCharge": 0
},
"agregats_fiscaux": {
"revenuBrutGlobal": 38814,
"revenuImposable": 38814,
"impotRevenuNetAvantCorrections": 38814,
"montantImpot": 38814,
"revenuFiscalReference": 38814,
"anneeImpots": "2020",
"anneeRevenus": "2020"
},
"complements": {
"situationPartielle": "SUP DOM"
}
}

View file

@ -9,7 +9,36 @@ describe APIParticulier::API do
it "doit retourner une liste de scopes" do it "doit retourner une liste de scopes" do
VCR.use_cassette("api_particulier/success/introspect") do VCR.use_cassette("api_particulier/success/introspect") do
expect(subject).to match_array(['dgfip_avis_imposition', 'dgfip_adresse', 'cnaf_allocataires', 'cnaf_enfants', 'cnaf_adresse', 'cnaf_quotient_familial', 'mesri_statut_etudiant']) expect(subject).to contain_exactly(
'cnaf_adresse',
'cnaf_allocataires',
'cnaf_enfants',
'cnaf_quotient_familial',
'dgfip_adresse_fiscale_annee',
'dgfip_adresse_fiscale_taxation',
'dgfip_annee_impot',
'dgfip_annee_revenus',
'dgfip_date_etablissement',
'dgfip_date_recouvrement',
'dgfip_declarant1_date_naissance',
'dgfip_declarant1_nom',
'dgfip_declarant1_nom_naissance',
'dgfip_declarant1_prenoms',
'dgfip_declarant2_date_naissance',
'dgfip_declarant2_nom',
'dgfip_declarant2_nom_naissance',
'dgfip_declarant2_prenoms',
'dgfip_erreur_correctif',
'dgfip_impot_revenu_net_avant_corrections',
'dgfip_montant_impot',
'dgfip_nombre_parts',
'dgfip_nombre_personnes_a_charge',
'dgfip_revenu_brut_global',
'dgfip_revenu_fiscal_reference',
'dgfip_revenu_imposable',
'dgfip_situation_familiale',
'dgfip_situation_partielle'
)
end end
end end

View file

@ -57,7 +57,7 @@ describe APIParticulier::CnafAdapter do
end end
end end
context 'when the api answer is valid' do context 'when the api answer is invalid' do
let(:cassette) { "api_particulier/success/composition_familiale_invalid" } let(:cassette) { "api_particulier/success/composition_familiale_invalid" }
context 'when no sources is requested' do context 'when no sources is requested' do

View file

@ -0,0 +1,66 @@
describe APIParticulier::DgfipAdapter do
let(:adapter) { described_class.new(api_particulier_token, numero_fiscal, reference_avis, requested_sources) }
before { stub_const('API_PARTICULIER_URL', 'https://particulier.api.gouv.fr/api') }
describe '#to_params' do
let(:api_particulier_token) { '29eb50b65f64e8e00c0847a8bbcbd150e1f847' }
let(:numero_fiscal) { '2097699999077' }
let(:reference_avis) { '2097699999077' }
subject { VCR.use_cassette(cassette) { adapter.to_params } }
context 'when the api answer is valid' do
let(:cassette) { 'api_particulier/success/avis_imposition' }
context 'when the token has all the dgfip scopes' do
context 'and all the sources are requested' do
let(:requested_sources) do
{
"dgfip" => {
"declarant1" => ["nom", "nomNaissance", "prenoms", "dateNaissance"],
"declarant2" => ["nom", "nomNaissance", "prenoms", "dateNaissance"],
"echeance_avis" => ["dateRecouvrement", "dateEtablissement"],
"foyer_fiscal" => ["adresse", "annee", "nombreParts", "nombrePersonnesCharge", "situationFamille"],
"agregats_fiscaux" => ["revenuBrutGlobal", "revenuImposable", "impotRevenuNetAvantCorrections", "montantImpot", "revenuFiscalReference", "anneeImpots", "anneeRevenus"],
"complements" => ["erreurCorrectif", "situationPartielle"]
}
}
end
let(:result) { JSON.parse(File.read('spec/fixtures/files/api_particulier/avis_imposition.json')) }
it { is_expected.to eq(result) }
end
context 'when no sources is requested' do
let(:requested_sources) { {} }
it { is_expected.to eq({}) }
end
context 'when a declarer name is requested' do
let(:requested_sources) { { 'dgfip' => { 'declarant1' => ['nom'] } } }
it { is_expected.to eq('declarant1' => { 'nom' => 'FERRI' }) }
end
context 'when a revenue is requested' do
let(:requested_sources) { { 'dgfip' => { 'agregats_fiscaux' => ['revenuBrutGlobal'] } } }
it { is_expected.to eq('agregats_fiscaux' => { 'revenuBrutGlobal' => 38814 }) }
end
end
end
context 'when the api answer is invalid' do
let(:cassette) { 'api_particulier/success/avis_imposition_invalid' }
context 'when no sources is requested' do
let(:requested_sources) { {} }
it { expect { subject }.to raise_error(APIParticulier::DgfipAdapter::InvalidSchemaError) }
end
end
end
end

View file

@ -17,7 +17,7 @@ describe APIParticulier::Services::SourcesService do
it { is_expected.to eq({}) } it { is_expected.to eq({}) }
end end
context 'when a procedure has a cnaf_allocataires and a cnaf_adresse scopes' do context 'when a procedure has a cnaf_allocataires and a cnaf_enfants scopes' do
let(:api_particulier_scopes) { ['cnaf_allocataires', 'cnaf_enfants'] } let(:api_particulier_scopes) { ['cnaf_allocataires', 'cnaf_enfants'] }
let(:cnaf_allocataires_and_enfants) do let(:cnaf_allocataires_and_enfants) do
@ -32,6 +32,21 @@ describe APIParticulier::Services::SourcesService do
it { is_expected.to match(cnaf_allocataires_and_enfants) } it { is_expected.to match(cnaf_allocataires_and_enfants) }
end end
context 'when a procedure has a dgfip_declarant1_nom , prenom and a dgfip_adresse_fiscale_taxation scopes' do
let(:api_particulier_scopes) { ['dgfip_declarant1_nom', 'dgfip_declarant1_prenoms', 'dgfip_adresse_fiscale_taxation'] }
let(:dgfip_avis_imposition_et_adresse) do
{
'dgfip' => {
'declarant1' => ['nom', 'prenoms'],
'foyer_fiscal' => ['adresse']
}
}
end
it { is_expected.to match(dgfip_avis_imposition_et_adresse) }
end
context 'when a procedure has an unknown scope' do context 'when a procedure has an unknown scope' do
let(:api_particulier_scopes) { ['unknown_scope'] } let(:api_particulier_scopes) { ['unknown_scope'] }

View file

@ -0,0 +1,103 @@
describe Champs::DgfipChamp, type: :model do
let(:champ) { described_class.new }
describe 'numero_fiscal and reference_avis' do
before do
champ.numero_fiscal = '1122299999092'
champ.reference_avis = 'FC22299999092'
end
it 'saves numero_fiscal and reference_avis' do
expect(champ.numero_fiscal).to eq('1122299999092')
expect(champ.reference_avis).to eq('FC22299999092')
end
end
describe 'external_id' do
context 'when only one data is given' do
before do
champ.numero_fiscal = '1122299999092'
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_fiscal = '1122299999092'
champ.reference_avis = 'FC22299999092'
champ.save
end
it { expect(JSON.parse(champ.external_id)).to eq({ "reference_avis" => "FC22299999092", "numero_fiscal" => "1122299999092" }) }
end
end
describe '#validate' do
let(:numero_fiscal) { '1122299999092' }
let(:reference_avis) { 'FC22299999092' }
let(:champ) { described_class.new(dossier: create(:dossier), type_de_champ: create(:type_de_champ_dgfip)) }
let(:validation_context) { :create }
subject { champ.valid?(validation_context) }
before do
champ.numero_fiscal = numero_fiscal
champ.reference_avis = reference_avis
end
context 'when numero_fiscal and reference_avis are valid' do
it { is_expected.to be true }
end
context 'when numero_fiscal and reference_avis are nil' do
let(:numero_fiscal) { nil }
let(:reference_avis) { nil }
it { is_expected.to be true }
end
context 'when only reference_avis is nil' do
let(:reference_avis) { nil }
it do
is_expected.to be false
expect(champ.errors.full_messages).to eq(["Reference avis doit posséder 13 ou 14 caractères"])
end
end
context 'when only numero_fiscal is nil' do
let(:numero_fiscal) { nil }
it do
is_expected.to be false
expect(champ.errors.full_messages).to eq(["Numero fiscal doit posséder 13 ou 14 caractères"])
end
end
context 'when numero_fiscal is invalid' do
let(:numero_fiscal) { '11222' }
it do
is_expected.to be false
expect(champ.errors.full_messages).to eq(["Numero fiscal doit posséder 13 ou 14 caractères"])
end
context 'and the validation_context is :brouillon' do
let(:validation_context) { :brouillon }
it { is_expected.to be true }
end
end
context 'when reference_avis is invalid' do
let(:reference_avis) { 'FC222' }
it do
is_expected.to be false
expect(champ.errors.full_messages).to eq(["Reference avis doit posséder 13 ou 14 caractères"])
end
end
end
end

View file

@ -78,6 +78,7 @@ describe ProcedureExportService do
"iban", "iban",
"annuaire_education", "annuaire_education",
"cnaf", "cnaf",
"dgfip",
"text" "text"
] ]
end end
@ -166,6 +167,7 @@ describe ProcedureExportService do
"iban", "iban",
"annuaire_education", "annuaire_education",
"cnaf", "cnaf",
"dgfip",
"text" "text"
] ]
end end
@ -250,6 +252,7 @@ describe ProcedureExportService do
"iban", "iban",
"annuaire_education", "annuaire_education",
"cnaf", "cnaf",
"dgfip",
"text" "text"
] ]
end end

View file

@ -5,35 +5,44 @@ describe 'fetch API Particulier Data', js: true do
let(:expected_sources) do let(:expected_sources) do
{ {
"cnaf" => 'cnaf' =>
{ {
"adresse" => ["identite", "complementIdentite", "complementIdentiteGeo", "numeroRue", "lieuDit", "codePostalVille", "pays"], 'adresse' => ['identite', 'complementIdentite', 'complementIdentiteGeo', 'numeroRue', 'lieuDit', 'codePostalVille', 'pays'],
"allocataires" => ["nomPrenom", "dateDeNaissance", "sexe"], 'allocataires' => ['nomPrenom', 'dateDeNaissance', 'sexe'],
"enfants" => ["nomPrenom", "dateDeNaissance", "sexe"], 'enfants' => ['nomPrenom', 'dateDeNaissance', 'sexe'],
"quotient_familial" => ["quotientFamilial", "annee", "mois"] 'quotient_familial' => ['quotientFamilial', 'annee', 'mois']
},
'dgfip' =>
{
'declarant1' => ['dateNaissance', 'nom', 'nomNaissance', 'prenoms'],
'declarant2' => ['dateNaissance', 'nom', 'nomNaissance', 'prenoms'],
'echeance_avis' => ['dateEtablissement', 'dateRecouvrement'],
'foyer_fiscal' => ['adresse', 'annee', 'nombreParts', 'nombrePersonnesCharge', 'situationFamille'],
'agregats_fiscaux' => ['anneeImpots', 'anneeRevenus', 'impotRevenuNetAvantCorrections', 'montantImpot', 'revenuBrutGlobal', 'revenuFiscalReference', 'revenuImposable'],
'complements' => ['situationPartielle', 'erreurCorrectif']
} }
} }
end end
before do before do
stub_const("API_PARTICULIER_URL", "https://particulier.api.gouv.fr/api") stub_const('API_PARTICULIER_URL', 'https://particulier.api.gouv.fr/api')
Flipper.enable(:api_particulier) Flipper.enable(:api_particulier)
end end
context "when an administrateur is logged" do context 'when an administrateur is logged' do
let(:procedure) do let(:procedure) do
create(:procedure, :with_service, :with_instructeur, create(:procedure, :with_service, :with_instructeur,
aasm_state: :brouillon, aasm_state: :brouillon,
administrateurs: [administrateur], administrateurs: [administrateur],
libelle: "libellé de la procédure", libelle: 'libellé de la procédure',
path: "libelle-de-la-procedure") path: 'libelle-de-la-procedure')
end end
before { login_as administrateur.user, scope: :user } before { login_as administrateur.user, scope: :user }
scenario 'it can enable api particulier' do scenario 'it can enable api particulier' do
visit admin_procedure_path(procedure) visit admin_procedure_path(procedure)
expect(page).to have_content("Configurer le jeton API particulier") expect(page).to have_content('Configurer le jeton API particulier')
find('#api-particulier').click find('#api-particulier').click
expect(page).to have_current_path(admin_procedure_api_particulier_path(procedure)) expect(page).to have_current_path(admin_procedure_api_particulier_path(procedure))
@ -42,7 +51,7 @@ describe 'fetch API Particulier Data', js: true do
expect(page).to have_current_path(admin_procedure_api_particulier_jeton_path(procedure)) expect(page).to have_current_path(admin_procedure_api_particulier_jeton_path(procedure))
fill_in 'procedure_api_particulier_token', with: expected_token fill_in 'procedure_api_particulier_token', with: expected_token
VCR.use_cassette("api_particulier/success/introspect") { click_on 'Enregistrer' } 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_text('Le jeton a bien été mis à jour')
expect(page).to have_current_path(admin_procedure_api_particulier_sources_path(procedure)) expect(page).to have_current_path(admin_procedure_api_particulier_sources_path(procedure))
@ -54,7 +63,7 @@ describe 'fetch API Particulier Data', js: true do
end end
end end
within("#adresse") do within('#adresse') do
check('identité') check('identité')
check('complément didentité') check('complément didentité')
check('complément didentité géographique') check('complément didentité géographique')
@ -64,19 +73,67 @@ describe 'fetch API Particulier Data', js: true do
check('pays') check('pays')
end end
within("#quotient_familial") do within('#quotient_familial') do
check('quotient familial') check('quotient familial')
check('année') check('année')
check('mois') check('mois')
end end
click_on "Enregistrer" ['declarant1', 'declarant2'].each do |scope|
within("##{scope}") do
check('nom')
check('nom de naissance')
check('prénoms')
check('date de naissance')
end
end
within("#enfants") do scroll_to(find('#echeance_avis'))
within ('#echeance_avis') do
check('date de recouvrement')
check("date détablissement")
end
within('#foyer_fiscal') do
check('année')
check('adresse')
check('nombre de parts')
check('situation familiale')
check('nombre de personnes à charge')
end
within('#agregats_fiscaux') do
check('revenu brut global')
check('revenu imposable')
check('impôt sur le revenu net avant correction')
check("montant de limpôt")
check('revenu fiscal de référence')
check("année dimposition")
check('année des revenus')
end
within('#complements') do
check('erreur correctif')
check('situation partielle')
end
click_on 'Enregistrer'
within('#enfants') do
expect(find('input[value=nomPrenom]')).to be_checked expect(find('input[value=nomPrenom]')).to be_checked
end end
expect(procedure.reload.api_particulier_sources).to eq(expected_sources) procedure.reload
expect(procedure.api_particulier_sources.keys).to contain_exactly('cnaf', 'dgfip')
expect(procedure.api_particulier_sources['cnaf'].keys).to contain_exactly('adresse', 'allocataires', 'enfants', 'quotient_familial')
expect(procedure.api_particulier_sources['dgfip'].keys).to contain_exactly('declarant1', 'declarant2', 'echeance_avis', 'foyer_fiscal', 'agregats_fiscaux', 'complements')
procedure.api_particulier_sources.each do |provider, scopes|
scopes.each do |scope, fields|
expect(fields).to match_array(expected_sources[provider][scope])
end
end
visit champs_admin_procedure_path(procedure) visit champs_admin_procedure_path(procedure)
@ -101,12 +158,14 @@ describe 'fetch API Particulier Data', js: true do
let(:api_particulier_token) { '29eb50b65f64e8e00c0847a8bbcbd150e1f847' } let(:api_particulier_token) { '29eb50b65f64e8e00c0847a8bbcbd150e1f847' }
let(:numero_allocataire) { '5843972' } let(:numero_allocataire) { '5843972' }
let(:code_postal) { '92110' } let(:code_postal) { '92110' }
let(:numero_fiscal) { '2097699999077' }
let(:reference_avis) { '2097699999077' }
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
let(:procedure) do let(:procedure) do
create(:procedure, :for_individual, :with_service, :with_cnaf, :published, create(:procedure, :for_individual, :with_service, :with_cnaf, :with_dgfip, :published,
libelle: "libellé de la procédure", libelle: 'libellé de la procédure',
path: "libelle-de-la-procedure", path: 'libelle-de-la-procedure',
instructeurs: [instructeur], instructeurs: [instructeur],
api_particulier_sources: expected_sources, api_particulier_sources: expected_sources,
api_particulier_token: api_particulier_token) api_particulier_token: api_particulier_token)
@ -138,7 +197,7 @@ describe 'fetch API Particulier Data', js: true do
fill_in 'Le code postal', with: code_postal fill_in 'Le code postal', with: code_postal
VCR.use_cassette("api_particulier/success/composition_familiale") do VCR.use_cassette('api_particulier/success/composition_familiale') do
perform_enqueued_jobs { click_on 'Déposer le dossier' } perform_enqueued_jobs { click_on 'Déposer le dossier' }
end end
@ -163,5 +222,69 @@ describe 'fetch API Particulier Data', js: true do
expect(page).to have_content('PAUL SNOW masculin 04/01/2018') expect(page).to have_content('PAUL SNOW masculin 04/01/2018')
expect(page).to have_content('1856 6 2021') expect(page).to have_content('1856 6 2021')
end end
scenario 'it can fill a DGFiP field' do
visit commencer_path(path: procedure.path)
click_on 'Commencer la démarche'
choose 'Madame'
fill_in 'individual_nom', with: 'FERRI'
fill_in 'individual_prenom', with: 'Karine'
click_button('Continuer')
fill_in 'Le numéro fiscal', with: numero_fiscal
fill_in "La référence d'avis d'imposition", with: 'wrong_code'
blur
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
dossier = Dossier.last
expect(dossier.champs.second.reference_avis).to eq('wrong_code')
click_on 'Déposer le dossier'
expect(page).to have_content(/reference avis doit posséder 13 ou 14 caractères/)
fill_in "La référence d'avis d'imposition", with: reference_avis
VCR.use_cassette('api_particulier/success/avis_imposition') 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 DGFiP/)
log_out
login_as instructeur.user, scope: :user
visit instructeur_dossier_path(procedure, dossier)
expect(page).to have_content('nom FERRI')
expect(page).to have_content('nom de naissance FERRI')
expect(page).to have_content('prénoms Karine')
expect(page).to have_content('date de naissance 12/08/1978')
expect(page).to have_content('date de recouvrement 09/10/2020')
expect(page).to have_content("date détablissement 07/07/2020")
expect(page).to have_content('année 2020')
expect(page).to have_content("adresse fiscale de lannée passée 13 rue de la Plage 97615 Pamanzi")
expect(page).to have_content('nombre de parts 1')
expect(page).to have_content('situation familiale Célibataire')
expect(page).to have_content('nombre de personnes à charge 0')
expect(page).to have_content('revenu brut global 38814')
expect(page).to have_content('revenu imposable 38814')
expect(page).to have_content('impôt sur le revenu net avant correction 38814')
expect(page).to have_content("montant de limpôt 38814")
expect(page).to have_content('revenu fiscal de référence 38814')
expect(page).to have_content("année dimposition 2020")
expect(page).to have_content('année des revenus 2020')
expect(page).to have_content('situation partielle SUP DOM')
expect(page).not_to have_content('erreur correctif')
end
end end
end end