Merge branch 'dev'

This commit is contained in:
gregoirenovel 2018-04-05 12:03:21 +02:00
commit 7b9dbe8880
34 changed files with 352 additions and 36 deletions

View file

@ -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'

View file

@ -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

View 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');
}
}
});
});

View file

@ -4,6 +4,7 @@
.dossier-edit {
.dossier-header {
background-color: $light-grey;
margin-bottom: $default-padding;
.container {
padding: $default-padding;

View file

@ -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],

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -5,12 +5,4 @@ class Individual < ApplicationRecord
validates :gender, presence: true, allow_nil: false, on: :update
validates :nom, presence: true, allow_blank: false, allow_nil: false, on: :update
validates :prenom, presence: true, allow_blank: false, allow_nil: false, on: :update
def birthdate
second_birthdate
end
def birthdate=(date)
self.second_birthdate = date
end
end

View file

@ -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

View file

@ -0,0 +1,2 @@
class TypesDeChamp::SiretTypeDeChamp < TypeDeChamp
end

View 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

View 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);
})();

View file

@ -1,19 +1,22 @@
#map
- if dossier.json_latlngs.nil?
%h2.empty-text Non renseigné
- else
#map
- if dossier.quartier_prioritaires.any?
- if dossier.quartier_prioritaires.any?
.card-title Quartiers prioritaires
%ul
- dossier.quartier_prioritaires.each do |q|
%li= q.nom
- if dossier.cadastres.any?
- if dossier.cadastres.any?
.card-title Parcelles cadastrales
%ul
- dossier.cadastres.each do |p|
%li
= "Parcelle n° #{p.numero} - Feuille #{p.code_arr} #{p.section} #{p.feuille}"
:javascript
:javascript
var getPositionUrl = "#{position_gestionnaire_dossier_path(dossier.procedure, dossier)}";
var dossierJsonLatLngs = #{raw(ensure_safe_json(dossier.json_latlngs))};
var dossierCadastres = #{raw(ensure_safe_json(dossier.cadastres.to_json))};

View file

@ -4,6 +4,10 @@
%h1= @dossier.procedure.libelle
.container
%p
Pour vous aider à remplir votre dossier, vous pouvez consulter
= link_to 'le guide de cette démarche', @dossier.procedure.lien_notice, { target: '_blank' }
%p.thanks Les champs avec une asterisque (*) sont obligatoires.
= form_for @dossier, html: { class: 'form', multipart: true } do |f|

View file

@ -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

View 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 }

View file

@ -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

View file

@ -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)

View 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 }

View file

@ -2,3 +2,5 @@ remote_storage: false
weekly_overview: false
champ_pj_allowed_for_admin_ids:
- 0
champ_siret_allowed_for_admin_ids:
- 0

View file

@ -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'

View file

@ -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
View file

@ -0,0 +1,2 @@
# This is a signing key used in dev and test environments
key: 'aef3153a9829fa4ba10acb02927ac855df6b92795b1ad265d654443c4b14a017'

View file

@ -0,0 +1,6 @@
class ChangeBirthdateTypeFromIndividual < ActiveRecord::Migration[5.2]
def up
remove_column :individuals, :birthdate, :string
rename_column :individuals, :second_birthdate, :birthdate
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2018_04_03_094135) do
ActiveRecord::Schema.define(version: 2018_04_04_113409) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -361,12 +361,11 @@ ActiveRecord::Schema.define(version: 2018_04_03_094135) do
create_table "individuals", id: :serial, force: :cascade do |t|
t.string "nom"
t.string "prenom"
t.string "birthdate"
t.integer "dossier_id"
t.string "gender"
t.datetime "created_at"
t.datetime "updated_at"
t.date "second_birthdate"
t.date "birthdate"
t.index ["dossier_id"], name: "index_individuals_on_dossier_id"
end

View file

@ -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']))

View file

@ -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

View file

@ -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

View file

@ -21,21 +21,18 @@ describe Individual do
let(:birthdate_from_user) { "12/11/1980" }
it { expect(individual.birthdate).to eq(Date.new(1980, 11, 12)) }
it { expect(individual.second_birthdate).to eq(Date.new(1980, 11, 12)) }
end
context "and the format is ISO" do
let(:birthdate_from_user) { "1980-11-12" }
it { expect(individual.birthdate).to eq(Date.new(1980, 11, 12)) }
it { expect(individual.second_birthdate).to eq(Date.new(1980, 11, 12)) }
end
context "and the format is WTF" do
let(:birthdate_from_user) { "1980 1 12" }
it { expect(individual.birthdate).to be_nil }
it { expect(individual.second_birthdate).to be_nil }
end
end
end

View 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