commit
bc4aceda3c
17 changed files with 275 additions and 13 deletions
|
@ -0,0 +1,30 @@
|
||||||
|
document.addEventListener('turbolinks:load', function() {
|
||||||
|
var primaries, i, primary, secondary, secondaryOptions;
|
||||||
|
|
||||||
|
primaries = document.querySelectorAll('select[data-secondary-options]');
|
||||||
|
for (i = 0; i < primaries.length; i++) {
|
||||||
|
primary = primaries[i];
|
||||||
|
secondary = document.querySelector('select[data-secondary-id="' + primary.dataset.primaryId + '"]');
|
||||||
|
secondaryOptions = JSON.parse(primary.dataset.secondaryOptions);
|
||||||
|
|
||||||
|
primary.addEventListener('change', function(e) {
|
||||||
|
var option, options, element;
|
||||||
|
|
||||||
|
while ((option = secondary.firstChild)) {
|
||||||
|
secondary.removeChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = secondaryOptions[e.target.value];
|
||||||
|
|
||||||
|
for (i = 0; i < options.length; i++) {
|
||||||
|
option = options[i];
|
||||||
|
element = document.createElement("option");
|
||||||
|
element.textContent = option;
|
||||||
|
element.value = option;
|
||||||
|
secondary.appendChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
secondary.selectedIndex = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -145,7 +145,7 @@ module NewUser
|
||||||
def champs_params
|
def champs_params
|
||||||
params.permit(dossier: {
|
params.permit(dossier: {
|
||||||
champs_attributes: [
|
champs_attributes: [
|
||||||
:id, :value, :piece_justificative_file, value: [],
|
:id, :value, :primary_value, :secondary_value, :piece_justificative_file, value: [],
|
||||||
etablissement_attributes: Champs::SiretChamp::ETABLISSEMENT_ATTRIBUTES
|
etablissement_attributes: Champs::SiretChamp::ETABLISSEMENT_ATTRIBUTES
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,6 +10,7 @@ class Champ < ApplicationRecord
|
||||||
scope :updated_since?, -> (date) { where('champs.updated_at > ?', date) }
|
scope :updated_since?, -> (date) { where('champs.updated_at > ?', date) }
|
||||||
scope :public_only, -> { where(private: false) }
|
scope :public_only, -> { where(private: false) }
|
||||||
scope :private_only, -> { where(private: true) }
|
scope :private_only, -> { where(private: true) }
|
||||||
|
scope :ordered, -> { includes(:type_de_champ).order('types_de_champ.order_place') }
|
||||||
|
|
||||||
def public?
|
def public?
|
||||||
!private?
|
!private?
|
||||||
|
@ -35,6 +36,10 @@ class Champ < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def main_value_name
|
||||||
|
:value
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def string_value
|
def string_value
|
||||||
|
|
|
@ -1,2 +1,36 @@
|
||||||
class Champs::LinkedDropDownListChamp < Champ
|
class Champs::LinkedDropDownListChamp < Champ
|
||||||
|
attr_reader :primary_value, :secondary_value
|
||||||
|
delegate :primary_options, :secondary_options, to: :type_de_champ
|
||||||
|
|
||||||
|
after_initialize :unpack_value
|
||||||
|
|
||||||
|
def unpack_value
|
||||||
|
if value.present?
|
||||||
|
primary, secondary = JSON.parse(value)
|
||||||
|
else
|
||||||
|
primary = secondary = ''
|
||||||
|
end
|
||||||
|
@primary_value ||= primary
|
||||||
|
@secondary_value ||= secondary
|
||||||
|
end
|
||||||
|
|
||||||
|
def primary_value=(value)
|
||||||
|
@primary_value = value
|
||||||
|
pack_value
|
||||||
|
end
|
||||||
|
|
||||||
|
def secondary_value=(value)
|
||||||
|
@secondary_value = value
|
||||||
|
pack_value
|
||||||
|
end
|
||||||
|
|
||||||
|
def main_value_name
|
||||||
|
:primary_value
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def pack_value
|
||||||
|
self.value = JSON.generate([ primary_value, secondary_value ])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -104,15 +104,11 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def ordered_champs
|
def ordered_champs
|
||||||
# TODO: use the line below when the procedure preview does not leak champ with dossier_id == 0
|
champs.ordered
|
||||||
# champs.joins(:type_de_champ).order('types_de_champ.order_place')
|
|
||||||
champs.joins(', types_de_champ').where("champs.type_de_champ_id = types_de_champ.id AND types_de_champ.procedure_id = #{procedure.id}").order('order_place')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def ordered_champs_private
|
def ordered_champs_private
|
||||||
# TODO: use the line below when the procedure preview does not leak champ with dossier_id == 0
|
champs_private.ordered
|
||||||
# champs_private.includes(:type_de_champ).order('types_de_champ.order_place')
|
|
||||||
champs_private.joins(', types_de_champ').where("champs.type_de_champ_id = types_de_champ.id AND types_de_champ.procedure_id = #{procedure.id}").order('order_place')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def ordered_pieces_justificatives
|
def ordered_pieces_justificatives
|
||||||
|
|
|
@ -1,2 +1,31 @@
|
||||||
class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypeDeChamp
|
class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypeDeChamp
|
||||||
|
PRIMARY_PATTERN = /^--(.*)--$/
|
||||||
|
|
||||||
|
def primary_options
|
||||||
|
primary_options = unpack_options.map(&:first)
|
||||||
|
if primary_options.present?
|
||||||
|
primary_options.unshift('')
|
||||||
|
end
|
||||||
|
primary_options
|
||||||
|
end
|
||||||
|
|
||||||
|
def secondary_options
|
||||||
|
secondary_options = unpack_options.to_h
|
||||||
|
if secondary_options.present?
|
||||||
|
secondary_options[''] = []
|
||||||
|
end
|
||||||
|
secondary_options
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def unpack_options
|
||||||
|
_, *options = drop_down_list.options
|
||||||
|
chunked = options.slice_before(PRIMARY_PATTERN)
|
||||||
|
chunked.map do |chunk|
|
||||||
|
primary, *secondary = chunk
|
||||||
|
secondary.unshift('')
|
||||||
|
[PRIMARY_PATTERN.match(primary)[1], secondary]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
- if nav_bar_profile == :user
|
- if nav_bar_profile == :user
|
||||||
%ul.header-tabs
|
%ul.header-tabs
|
||||||
%li
|
%li
|
||||||
= active_link_to "Dossiers", dossiers_path, active: :inclusive, class: 'tab-link'
|
= active_link_to "Dossiers", users_dossiers_path, active: :inclusive, class: 'tab-link'
|
||||||
|
|
||||||
%ul.header-right-content
|
%ul.header-right-content
|
||||||
- if nav_bar_profile == :gestionnaire && gestionnaire_signed_in?
|
- if nav_bar_profile == :gestionnaire && gestionnaire_signed_in?
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
= stylesheet_link_tag "new_design/new_application", media: "all", "data-turbolinks-track": "reload"
|
= stylesheet_link_tag "new_design/new_application", media: "all", "data-turbolinks-track": "reload"
|
||||||
= stylesheet_link_tag "new_design/print", media: "print", "data-turbolinks-track": true
|
= stylesheet_link_tag "new_design/print", media: "print", "data-turbolinks-track": true
|
||||||
|
|
||||||
|
- if Rails.env.development?
|
||||||
|
= stylesheet_link_tag :xray
|
||||||
|
|
||||||
%body
|
%body
|
||||||
.page-wrapper
|
.page-wrapper
|
||||||
= render partial: "layouts/support_navigator_banner"
|
= render partial: "layouts/support_navigator_banner"
|
||||||
|
@ -35,6 +38,10 @@
|
||||||
= render partial: "layouts/mailjet_newsletter"
|
= render partial: "layouts/mailjet_newsletter"
|
||||||
|
|
||||||
= javascript_include_tag "new_design/application", "data-turbolinks-eval": false
|
= javascript_include_tag "new_design/application", "data-turbolinks-eval": false
|
||||||
|
|
||||||
|
- if Rails.env.development?
|
||||||
|
= javascript_include_tag :xray
|
||||||
|
|
||||||
= yield :charts_js
|
= yield :charts_js
|
||||||
- if Rails.env == "test"
|
- if Rails.env == "test"
|
||||||
%script{ type: "text/javascript" }
|
%script{ type: "text/javascript" }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
= form.label :value do
|
= form.label champ.main_value_name do
|
||||||
#{champ.libelle}
|
#{champ.libelle}
|
||||||
- if champ.mandatory?
|
- if champ.mandatory?
|
||||||
%span.mandatory *
|
%span.mandatory *
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
- if champ.drop_down_list && champ.drop_down_list.options.any?
|
||||||
|
- champ_id = champ.object_id
|
||||||
|
= form.select :primary_value,
|
||||||
|
champ.primary_options,
|
||||||
|
{ required: champ.mandatory? },
|
||||||
|
{ data: { "secondary-options" => champ.secondary_options, "primary-id" => champ_id } }
|
||||||
|
= form.select :secondary_value,
|
||||||
|
champ.secondary_options[champ.primary_value],
|
||||||
|
{ required: champ.mandatory? },
|
||||||
|
{ data: { "secondary-id" => champ_id } }
|
|
@ -50,7 +50,7 @@
|
||||||
= link_to "", france_connect_particulier_path, class: "login-with-fc"
|
= link_to "", france_connect_particulier_path, class: "login-with-fc"
|
||||||
|
|
||||||
.center
|
.center
|
||||||
= link_to "Qu’est-ce que FranceConnect ?", "https://franceconnect.gouv.fr/", target: "_blank", class: "link"
|
= link_to "Qu’est-ce que FranceConnect ?", "https://app.franceconnect.gouv.fr/en-savoir-plus", target: "_blank", class: "link"
|
||||||
|
|
||||||
- if resource_name == :user
|
- if resource_name == :user
|
||||||
%hr
|
%hr
|
||||||
|
|
|
@ -3,9 +3,9 @@ namespace :'2018_05_21_cerfa_to_pj' do
|
||||||
dossiers = Cerfa.includes(dossier: [:procedure]).all.reject(&:empty?).map(&:dossier).compact.uniq
|
dossiers = Cerfa.includes(dossier: [:procedure]).all.reject(&:empty?).map(&:dossier).compact.uniq
|
||||||
|
|
||||||
dossiers.group_by(&:procedure).each do |procedure, dossiers|
|
dossiers.group_by(&:procedure).each do |procedure, dossiers|
|
||||||
if !procedure.type_de_champs.find_by(libelle: 'CERFA')
|
if !procedure.types_de_champ.find_by(libelle: 'CERFA')
|
||||||
procedure.administrateur.enable_feature(:champ_pj)
|
procedure.administrateur.enable_feature(:champ_pj)
|
||||||
type_de_champ = procedure.type_de_champs.create(
|
type_de_champ = procedure.types_de_champ.create(
|
||||||
type_champ: 'piece_justificative',
|
type_champ: 'piece_justificative',
|
||||||
libelle: 'CERFA'
|
libelle: 'CERFA'
|
||||||
)
|
)
|
||||||
|
|
|
@ -31,4 +31,21 @@ namespace :support do
|
||||||
pp.update(administrateur: new_owner)
|
pp.update(administrateur: new_owner)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc <<~EOD
|
||||||
|
Delete the user account for a given USER_MAIL.
|
||||||
|
Only works if the user has no dossier where the instruction has started.
|
||||||
|
EOD
|
||||||
|
task delete_user_account: :environment do
|
||||||
|
user_mail = ENV['USER_MAIL']
|
||||||
|
if user_mail.nil?
|
||||||
|
fail "Must specify a USER_MAIL"
|
||||||
|
end
|
||||||
|
user = User.find_by(email: user_mail)
|
||||||
|
if user.dossiers.state_instruction_commencee.any?
|
||||||
|
fail "Cannot delete this user because instruction has started for some dossiers"
|
||||||
|
end
|
||||||
|
user.dossiers.each { |d| d.delete_and_keep_track }
|
||||||
|
user.destroy
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
76
spec/features/new_user/linked_dropdown_spec.rb
Normal file
76
spec/features/new_user/linked_dropdown_spec.rb
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'linked dropdown lists' do
|
||||||
|
let(:password) { 'secret_password' }
|
||||||
|
let!(:user) { create(:user, password: password) }
|
||||||
|
|
||||||
|
let(:list_items) do
|
||||||
|
<<~END_OF_LIST
|
||||||
|
--Primary 1--
|
||||||
|
Secondary 1.1
|
||||||
|
Secondary 1.2
|
||||||
|
--Primary 2--
|
||||||
|
Secondary 2.1
|
||||||
|
Secondary 2.2
|
||||||
|
Secondary 2.3
|
||||||
|
END_OF_LIST
|
||||||
|
end
|
||||||
|
let(:drop_down_list) { create(:drop_down_list, value: list_items) }
|
||||||
|
let(:type_de_champ) { create(:type_de_champ_linked_drop_down_list, libelle: 'linked dropdown', drop_down_list: drop_down_list) }
|
||||||
|
|
||||||
|
let!(:procedure) do
|
||||||
|
p = create(:procedure, :published, :for_individual)
|
||||||
|
p.types_de_champ << type_de_champ
|
||||||
|
p
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:user_dossier) { user.dossiers.first }
|
||||||
|
|
||||||
|
scenario 'change primary value, secondary options are updated', js: true do
|
||||||
|
log_in(user.email, password, procedure)
|
||||||
|
|
||||||
|
fill_individual
|
||||||
|
|
||||||
|
# Select a primary value
|
||||||
|
select('Primary 2', from: primary_id_for('linked dropdown'))
|
||||||
|
|
||||||
|
# Secondary menu reflects chosen primary value
|
||||||
|
expect(page).to have_select(secondary_id_for('linked dropdown'), options: ['', 'Secondary 2.1', 'Secondary 2.2', 'Secondary 2.3'])
|
||||||
|
|
||||||
|
# Select another primary value
|
||||||
|
select('Primary 1', from: primary_id_for('linked dropdown'))
|
||||||
|
|
||||||
|
# Secondary menu gets updated
|
||||||
|
expect(page).to have_select(secondary_id_for('linked dropdown'), options: ['', 'Secondary 1.1', 'Secondary 1.2'])
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def log_in(email, password, procedure)
|
||||||
|
visit "/commencer/#{procedure.procedure_path.path}"
|
||||||
|
expect(page).to have_current_path(new_user_session_path)
|
||||||
|
|
||||||
|
fill_in 'user_email', with: email
|
||||||
|
fill_in 'user_password', with: password
|
||||||
|
click_on 'Se connecter'
|
||||||
|
expect(page).to have_current_path(identite_dossier_path(user_dossier))
|
||||||
|
end
|
||||||
|
|
||||||
|
def fill_individual
|
||||||
|
fill_in('individual_prenom', with: 'prenom')
|
||||||
|
fill_in('individual_nom', with: 'nom')
|
||||||
|
check 'dossier_autorisation_donnees'
|
||||||
|
click_on 'Continuer'
|
||||||
|
expect(page).to have_current_path(modifier_dossier_path(user_dossier))
|
||||||
|
end
|
||||||
|
|
||||||
|
def primary_id_for(libelle)
|
||||||
|
find(:xpath, ".//label[contains(text()[normalize-space()], '#{libelle}')]")[:for]
|
||||||
|
end
|
||||||
|
|
||||||
|
def secondary_id_for(libelle)
|
||||||
|
primary_id = primary_id_for(libelle)
|
||||||
|
link = find("\##{primary_id}")['data-primary-id']
|
||||||
|
find("[data-secondary-id=\"#{link}\"]")['id']
|
||||||
|
end
|
||||||
|
end
|
18
spec/models/champs/linked_drop_down_list_champ_spec.rb
Normal file
18
spec/models/champs/linked_drop_down_list_champ_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Champs::LinkedDropDownListChamp do
|
||||||
|
describe '#unpack_value' do
|
||||||
|
let(:champ) { described_class.new(value: '["tata", "tutu"]') }
|
||||||
|
|
||||||
|
it { expect(champ.primary_value).to eq('tata') }
|
||||||
|
it { expect(champ.secondary_value).to eq('tutu') }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#pack_value' do
|
||||||
|
let(:champ) { described_class.new(primary_value: 'tata', secondary_value: 'tutu') }
|
||||||
|
|
||||||
|
before { champ.save }
|
||||||
|
|
||||||
|
it { expect(champ.value).to eq('["tata","tutu"]') }
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,40 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe TypesDeChamp::LinkedDropDownListTypeDeChamp do
|
||||||
|
describe '#unpack_options' do
|
||||||
|
let(:drop_down_list) { build(:drop_down_list, value: menu_options) }
|
||||||
|
let(:type_de_champ) { described_class.new(drop_down_list: drop_down_list) }
|
||||||
|
|
||||||
|
context 'with no options' do
|
||||||
|
let(:menu_options) { '' }
|
||||||
|
it { expect(type_de_champ.secondary_options).to eq({}) }
|
||||||
|
it { expect(type_de_champ.primary_options).to eq([]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with two primary options' do
|
||||||
|
let(:menu_options) do
|
||||||
|
<<~END_OPTIONS
|
||||||
|
--Primary 1--
|
||||||
|
secondary 1.1
|
||||||
|
secondary 1.2
|
||||||
|
--Primary 2--
|
||||||
|
secondary 2.1
|
||||||
|
secondary 2.2
|
||||||
|
secondary 2.3
|
||||||
|
END_OPTIONS
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(type_de_champ.secondary_options).to eq(
|
||||||
|
{
|
||||||
|
'' => [],
|
||||||
|
'Primary 1' => [ '', 'secondary 1.1', 'secondary 1.2'],
|
||||||
|
'Primary 2' => [ '', 'secondary 2.1', 'secondary 2.2', 'secondary 2.3']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(type_de_champ.primary_options).to eq([ '', 'Primary 1', 'Primary 2' ]) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,7 +15,7 @@ describe 'layouts/_new_header.html.haml', type: :view do
|
||||||
let(:profile) { :user }
|
let(:profile) { :user }
|
||||||
|
|
||||||
it { is_expected.to have_css("a.header-logo[href=\"#{users_dossiers_path}\"]") }
|
it { is_expected.to have_css("a.header-logo[href=\"#{users_dossiers_path}\"]") }
|
||||||
it { is_expected.to have_link("Dossiers", href: dossiers_path) }
|
it { is_expected.to have_link("Dossiers", href: users_dossiers_path) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when rendering for gestionnaire' do
|
context 'when rendering for gestionnaire' do
|
||||||
|
|
Loading…
Reference in a new issue