Merge pull request #1512 from tchak/siret-type-de-champ
SIRET Type De Champ
This commit is contained in:
commit
92c195ad51
27 changed files with 319 additions and 5 deletions
2
Gemfile
2
Gemfile
|
@ -58,6 +58,8 @@ gem 'fog-openstack'
|
|||
|
||||
gem 'pg'
|
||||
|
||||
gem 'rbnacl-libsodium'
|
||||
|
||||
gem 'rgeo-geojson'
|
||||
gem 'leaflet-rails'
|
||||
gem 'leaflet-markercluster-rails', '~> 0.7.0'
|
||||
|
|
|
@ -591,6 +591,10 @@ GEM
|
|||
rb-fsevent (0.10.2)
|
||||
rb-inotify (0.9.10)
|
||||
ffi (>= 0.5.0, < 2)
|
||||
rbnacl (5.0.0)
|
||||
ffi
|
||||
rbnacl-libsodium (1.0.16)
|
||||
rbnacl (>= 3.0.1)
|
||||
rbovirt (0.1.5)
|
||||
nokogiri
|
||||
rest-client (> 1.7.0)
|
||||
|
@ -836,6 +840,7 @@ DEPENDENCIES
|
|||
rack-mini-profiler
|
||||
rails (~> 5.2.0.rc1)
|
||||
rails-controller-testing
|
||||
rbnacl-libsodium
|
||||
rest-client
|
||||
rgeo-geojson
|
||||
rspec-rails
|
||||
|
|
27
app/assets/javascripts/new_design/champs/siret.js
Normal file
27
app/assets/javascripts/new_design/champs/siret.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
document.addEventListener('turbolinks:load', function() {
|
||||
$('[data-siret]').on('input', function(evt) {
|
||||
var input = $(evt.target);
|
||||
var value = input.val();
|
||||
var url = input.attr('data-siret');
|
||||
switch (value.length) {
|
||||
case 0:
|
||||
$.get(url+'?siret=blank');
|
||||
break;
|
||||
case 14:
|
||||
input.attr('disabled', 'disabled');
|
||||
$.get(url+'?siret='+value).then(function() {
|
||||
input.removeAttr('data-invalid');
|
||||
input.removeAttr('disabled');
|
||||
}, function() {
|
||||
input.removeAttr('disabled');
|
||||
input.attr('data-invalid', true);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
if (!input.attr('data-invalid')) {
|
||||
input.attr('data-invalid', true);
|
||||
$.get(url+'?siret=invalid');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
|
@ -48,6 +48,10 @@
|
|||
.updated-at.highlighted {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.etablissement-titre {
|
||||
margin-bottom: 2 * $default-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.radios {
|
||||
|
@ -82,6 +86,10 @@
|
|||
&.small {
|
||||
padding: $default-padding / 2;
|
||||
}
|
||||
|
||||
&.small-margin {
|
||||
margin-bottom: $default-padding / 2;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
|
|
29
app/controllers/champs/siret_controller.rb
Normal file
29
app/controllers/champs/siret_controller.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
class Champs::SiretController < ApplicationController
|
||||
def index
|
||||
siret, champ_id = params.required([:siret, :champ_id])
|
||||
@champ = Champs::SiretChamp.find(champ_id)
|
||||
@etablissement = @champ.etablissement
|
||||
if siret == 'blank'
|
||||
if @etablissement
|
||||
@etablissement.mark_for_destruction
|
||||
end
|
||||
@blank = true
|
||||
elsif siret == 'invalid'
|
||||
if @etablissement
|
||||
@etablissement.mark_for_destruction
|
||||
end
|
||||
@error = "SIRET invalide"
|
||||
else
|
||||
etablissement_attributes = SIRETService.fetch(siret, @champ.dossier.procedure_id)
|
||||
if etablissement_attributes.present?
|
||||
@etablissement = @champ.build_etablissement(etablissement_attributes)
|
||||
@etablissement.champ = @champ
|
||||
else
|
||||
@error = "SIRET invalide"
|
||||
end
|
||||
end
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
end
|
|
@ -198,7 +198,10 @@ module NewGestionnaire
|
|||
end
|
||||
|
||||
def champs_private_params
|
||||
params.require(:dossier).permit(champs_private_attributes: [:id, :piece_justificative_file, :value, value: []])
|
||||
params.require(:dossier).permit(champs_private_attributes: [
|
||||
:id, :piece_justificative_file, :value, value: [],
|
||||
etablissement_attributes: Champs::SiretChamp::ETABLISSEMENT_ATTRIBUTES
|
||||
])
|
||||
end
|
||||
|
||||
def check_attestation_emailable
|
||||
|
|
|
@ -116,7 +116,12 @@ module NewUser
|
|||
|
||||
# FIXME: require(:dossier) when all the champs are united
|
||||
def champs_params
|
||||
params.permit(dossier: { champs_attributes: [:id, :value, :piece_justificative_file, value: []] })
|
||||
params.permit(dossier: {
|
||||
champs_attributes: [
|
||||
:id, :value, :piece_justificative_file, value: [],
|
||||
etablissement_attributes: Champs::SiretChamp::ETABLISSEMENT_ATTRIBUTES
|
||||
]
|
||||
})
|
||||
end
|
||||
|
||||
def dossier
|
||||
|
|
|
@ -6,6 +6,10 @@ module TypeDeChampHelper
|
|||
tdcs.reject! { |tdc| tdc.last == "piece_justificative" }
|
||||
end
|
||||
|
||||
if !current_administrateur.feature_enabled?(:champ_siret_allowed)
|
||||
tdcs.reject! { |tdc| tdc.last == "siret" }
|
||||
end
|
||||
|
||||
tdcs
|
||||
end
|
||||
end
|
||||
|
|
42
app/models/champs/siret_champ.rb
Normal file
42
app/models/champs/siret_champ.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
class Champs::SiretChamp < Champ
|
||||
ETABLISSEMENT_ATTRIBUTES = [
|
||||
:id,
|
||||
:_destroy,
|
||||
:signature,
|
||||
:siret,
|
||||
:siege_social,
|
||||
:naf,
|
||||
:libelle_naf,
|
||||
:adresse,
|
||||
:numero_voie,
|
||||
:type_voie,
|
||||
:nom_voie,
|
||||
:code_postal,
|
||||
:localite,
|
||||
:code_insee_localite,
|
||||
:entreprise_siren,
|
||||
:entreprise_capital_social,
|
||||
:entreprise_numero_tva_intracommunautaire,
|
||||
:entreprise_forme_juridique,
|
||||
:entreprise_forme_juridique_code,
|
||||
:entreprise_nom_commercial,
|
||||
:entreprise_raison_sociale,
|
||||
:entreprise_siret_siege_social,
|
||||
:entreprise_code_effectif_entreprise,
|
||||
:entreprise_date_creation,
|
||||
:entreprise_nom,
|
||||
:entreprise_prenom,
|
||||
:association_rna,
|
||||
:association_titre,
|
||||
:association_objet,
|
||||
:association_date_creation,
|
||||
:association_date_declaration,
|
||||
:association_date_publication,
|
||||
exercices_attributes: [
|
||||
[:id, :ca, :date_fin_exercice, :date_fin_exercice_timestamp]
|
||||
]
|
||||
]
|
||||
|
||||
belongs_to :etablissement, dependent: :destroy
|
||||
accepts_nested_attributes_for :etablissement, allow_destroy: true, update_only: true
|
||||
end
|
|
@ -2,12 +2,16 @@ class Etablissement < ApplicationRecord
|
|||
belongs_to :dossier
|
||||
belongs_to :entreprise, dependent: :destroy
|
||||
|
||||
has_one :champ, class_name: 'Champs::SiretChamp'
|
||||
has_many :exercices, dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :exercices
|
||||
accepts_nested_attributes_for :entreprise, update_only: true
|
||||
|
||||
validates :dossier_id, uniqueness: true
|
||||
validates :siret, presence: true
|
||||
validates :dossier_id, uniqueness: { allow_nil: true }
|
||||
|
||||
validate :validate_signature
|
||||
|
||||
def geo_adresse
|
||||
[numero_voie, type_voie, nom_voie, complement_adresse, code_postal, localite].join(' ')
|
||||
|
@ -17,4 +21,32 @@ class Etablissement < ApplicationRecord
|
|||
# squeeze needed because of space in excess in the data
|
||||
"#{numero_voie} #{type_voie} #{nom_voie}, #{complement_adresse}, #{code_postal} #{localite}".squeeze(' ')
|
||||
end
|
||||
|
||||
def titre
|
||||
entreprise_raison_sociale || association_titre
|
||||
end
|
||||
|
||||
def verify
|
||||
SignatureService.verify(signature, message_for_signature)
|
||||
end
|
||||
|
||||
def sign
|
||||
SignatureService.sign(message_for_signature)
|
||||
end
|
||||
|
||||
attr_accessor :signature
|
||||
|
||||
private
|
||||
|
||||
def validate_signature
|
||||
if champ && !verify
|
||||
errors.add(:base, 'Numéro SIRET introuvable.')
|
||||
end
|
||||
end
|
||||
|
||||
def message_for_signature
|
||||
JSON.pretty_generate(as_json(include: {
|
||||
exercices: { only: [:ca, :date_fin_exercice, :date_fin_exercice_timestamp] }
|
||||
}).delete_if { |k,v| v.blank? })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,8 @@ class TypeDeChamp < ApplicationRecord
|
|||
header_section: 'header_section',
|
||||
explication: 'explication',
|
||||
dossier_link: 'dossier_link',
|
||||
piece_justificative: 'piece_justificative'
|
||||
piece_justificative: 'piece_justificative',
|
||||
siret: 'siret'
|
||||
}
|
||||
|
||||
belongs_to :procedure
|
||||
|
|
2
app/models/types_de_champ/siret_type_de_champ.rb
Normal file
2
app/models/types_de_champ/siret_type_de_champ.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
class TypesDeChamp::SiretTypeDeChamp < TypeDeChamp
|
||||
end
|
38
app/services/signature_service.rb
Normal file
38
app/services/signature_service.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
class SignatureService
|
||||
CONFIG_PATH = Rails.root.join("config", "signing_key.yml")
|
||||
|
||||
class << self
|
||||
def generate
|
||||
RbNaCl::Util.bin2hex(RbNaCl::SigningKey.generate)
|
||||
end
|
||||
|
||||
def verify(signature, message)
|
||||
message = Base64.urlsafe_encode64(message)
|
||||
begin
|
||||
signing_key.verify_key
|
||||
.verify(RbNaCl::Util.hex2bin(signature), message)
|
||||
rescue RbNaCl::BadSignatureError, RbNaCl::LengthError
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def sign(message)
|
||||
message = Base64.urlsafe_encode64(message)
|
||||
RbNaCl::Util.bin2hex(signing_key.sign(message))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def signing_key
|
||||
@@signing_key ||= RbNaCl::SigningKey.new(RbNaCl::Util.hex2bin(config[:key]))
|
||||
end
|
||||
|
||||
def config
|
||||
if File.exist?(CONFIG_PATH)
|
||||
YAML.safe_load(File.read(CONFIG_PATH)).symbolize_keys
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
8
app/views/champs/siret/index.js.erb
Normal file
8
app/views/champs/siret/index.js.erb
Normal file
|
@ -0,0 +1,8 @@
|
|||
(function() {
|
||||
<% if @blank || @error %>
|
||||
var html = "<%= escape_javascript(render partial: 'shared/champs/siret/delete_etablissement', locals: { message: @error, position: @champ.order_place, etablissement: @etablissement }) %>";
|
||||
<% else %>
|
||||
var html = "<%= escape_javascript(render partial: 'shared/champs/siret/etablissement', locals: { position: @champ.order_place, etablissement: @etablissement }) %>";
|
||||
<% end %>
|
||||
$("#etablissement-for-<%= @champ.id %>").html(html);
|
||||
})();
|
|
@ -0,0 +1,6 @@
|
|||
= message
|
||||
- if etablissement.present?
|
||||
- champ_attributes = etablissement.champ.private? ? 'champs_private_attributes' : 'champs_attributes'
|
||||
= fields_for "dossier[#{champ_attributes}][#{position}][etablissement_attributes]", etablissement do |form|
|
||||
= form.hidden_field :_destroy
|
||||
|
4
app/views/shared/champs/siret/_etablissement.html.haml
Normal file
4
app/views/shared/champs/siret/_etablissement.html.haml
Normal file
|
@ -0,0 +1,4 @@
|
|||
= render partial: 'shared/dossiers/editable_champs/etablissement_titre', locals: { etablissement: etablissement }
|
||||
- champ_attributes = etablissement.champ.private? ? 'champs_private_attributes' : 'champs_attributes'
|
||||
= fields_for "dossier[#{champ_attributes}][#{position}][etablissement_attributes]", etablissement do |form|
|
||||
= render partial: 'shared/dossiers/editable_champs/etablissement', locals: { form: form, signature: etablissement.sign }
|
|
@ -0,0 +1,34 @@
|
|||
= form.hidden_field :signature, value: signature
|
||||
= form.hidden_field :siret
|
||||
= form.hidden_field :siege_social
|
||||
= form.hidden_field :naf
|
||||
= form.hidden_field :libelle_naf
|
||||
= form.hidden_field :adresse
|
||||
= form.hidden_field :numero_voie
|
||||
= form.hidden_field :type_voie
|
||||
= form.hidden_field :nom_voie
|
||||
= form.hidden_field :code_postal
|
||||
= form.hidden_field :localite
|
||||
= form.hidden_field :code_insee_localite
|
||||
= form.hidden_field :entreprise_siren
|
||||
= form.hidden_field :entreprise_capital_social
|
||||
= form.hidden_field :entreprise_numero_tva_intracommunautaire
|
||||
= form.hidden_field :entreprise_forme_juridique
|
||||
= form.hidden_field :entreprise_forme_juridique_code
|
||||
= form.hidden_field :entreprise_nom_commercial
|
||||
= form.hidden_field :entreprise_raison_sociale
|
||||
= form.hidden_field :entreprise_siret_siege_social
|
||||
= form.hidden_field :entreprise_code_effectif_entreprise
|
||||
= form.hidden_field :entreprise_date_creation
|
||||
= form.hidden_field :entreprise_nom
|
||||
= form.hidden_field :entreprise_prenom
|
||||
= form.hidden_field :association_rna
|
||||
= form.hidden_field :association_titre
|
||||
= form.hidden_field :association_objet
|
||||
= form.hidden_field :association_date_creation
|
||||
= form.hidden_field :association_date_declaration
|
||||
= form.hidden_field :association_date_publication
|
||||
= form.fields_for :exercices do |form|
|
||||
= form.hidden_field :ca
|
||||
= form.hidden_field :date_fin_exercice
|
||||
= form.hidden_field :date_fin_exercice_timestamp
|
|
@ -0,0 +1,6 @@
|
|||
.etablissement-titre
|
||||
= etablissement.titre
|
||||
= etablissement.entreprise_forme_juridique
|
||||
- if etablissement.entreprise_capital_social.present?
|
||||
au capital social de
|
||||
= number_to_currency(etablissement.entreprise_capital_social)
|
10
app/views/shared/dossiers/editable_champs/_siret.html.haml
Normal file
10
app/views/shared/dossiers/editable_champs/_siret.html.haml
Normal file
|
@ -0,0 +1,10 @@
|
|||
= form.text_field :value,
|
||||
placeholder: champ.libelle,
|
||||
class: 'small-margin',
|
||||
data: { siret: champs_siret_path(format: :js, champ_id: champ) },
|
||||
required: champ.mandatory?
|
||||
%div{ id: "etablissement-for-#{champ.id}" }
|
||||
- if champ.etablissement.present?
|
||||
= render partial: 'shared/dossiers/editable_champs/etablissement_titre', locals: { etablissement: champ.etablissement }
|
||||
= form.fields_for :etablissement do |form|
|
||||
= render partial: 'shared/dossiers/editable_champs/etablissement', locals: { form: form, signature: champ.etablissement.sign }
|
|
@ -2,3 +2,5 @@ remote_storage: false
|
|||
weekly_overview: false
|
||||
champ_pj_allowed_for_admin_ids:
|
||||
- 0
|
||||
champ_siret_allowed_for_admin_ids:
|
||||
- 0
|
||||
|
|
|
@ -26,3 +26,4 @@ fr:
|
|||
multiple_drop_down_list: 'Menu déroulant à choix multiples'
|
||||
dossier_link: 'Lien vers un autre dossier'
|
||||
piece_justificative: 'Pièce justificative'
|
||||
siret: 'SIRET'
|
||||
|
|
|
@ -72,6 +72,10 @@ Rails.application.routes.draw do
|
|||
get 'particulier/callback' => 'particulier#callback'
|
||||
end
|
||||
|
||||
namespace :champs do
|
||||
get ':champ_id/siret' => 'siret#index', as: 'siret'
|
||||
end
|
||||
|
||||
namespace :users do
|
||||
namespace :dossiers do
|
||||
resources :invites, only: [:index, :show]
|
||||
|
|
2
config/signing_key.yml
Normal file
2
config/signing_key.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
# This is a signing key used in dev and test environments
|
||||
key: 'aef3153a9829fa4ba10acb02927ac855df6b92795b1ad265d654443c4b14a017'
|
|
@ -235,7 +235,7 @@ describe NewUser::DossiersController, type: :controller do
|
|||
|
||||
context 'when the update fails' do
|
||||
before do
|
||||
expect_any_instance_of(Dossier).to receive(:update).and_return(false)
|
||||
expect_any_instance_of(Dossier).to receive(:save).and_return(false)
|
||||
expect_any_instance_of(Dossier).to receive(:errors)
|
||||
.and_return(double(full_messages: ['nop']))
|
||||
|
||||
|
|
|
@ -75,6 +75,9 @@ FactoryBot.define do
|
|||
factory :type_de_champ_piece_justificative, class: 'TypesDeChamp::PieceJustificativeTypeDeChamp' do
|
||||
type_champ 'piece_justificative'
|
||||
end
|
||||
factory :type_de_champ_siret, class: 'TypesDeChamp::SiretTypeDeChamp' do
|
||||
type_champ 'siret'
|
||||
end
|
||||
|
||||
trait :private do
|
||||
private true
|
||||
|
|
|
@ -14,4 +14,24 @@ describe Etablissement do
|
|||
|
||||
it { expect(etablissement.inline_adresse).to eq '6 RUE green moon, IMMEUBLE BORA, 92270 BOIS COLOMBES' }
|
||||
end
|
||||
|
||||
describe '#verify' do
|
||||
let(:etablissement) { create(:etablissement) }
|
||||
let(:etablissement2) { create(:etablissement) }
|
||||
|
||||
it 'should verify signed etablissement' do
|
||||
etablissement.signature = etablissement.sign
|
||||
expect(etablissement.verify).to eq(true)
|
||||
end
|
||||
|
||||
it 'should reject etablissement with other etablissement signature' do
|
||||
etablissement.signature = etablissement2.sign
|
||||
expect(etablissement.verify).to eq(false)
|
||||
end
|
||||
|
||||
it 'should reject etablissement with wrong signature' do
|
||||
etablissement.signature = "fd7687fdsgdf6gd7f8g"
|
||||
expect(etablissement.verify).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
16
spec/services/signature_service_spec.rb
Normal file
16
spec/services/signature_service_spec.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SignatureService do
|
||||
let(:service) { SignatureService }
|
||||
let(:message) { { hello: 'World!' }.to_json }
|
||||
let(:message2) { { hello: 'World' }.to_json }
|
||||
|
||||
it "sign and verify" do
|
||||
signature = service.sign(message)
|
||||
signature2 = service.sign(message2)
|
||||
|
||||
expect(service.verify(signature, message)).to eq(true)
|
||||
expect(service.verify(signature2, message)).to eq(false)
|
||||
expect(service.verify(signature, message2)).to eq(false)
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue