[fix #1411] user: new form ui

This commit is contained in:
simon lehericey 2018-02-21 18:32:07 +01:00
parent 5aa9a5ac6c
commit d702a0c083
11 changed files with 461 additions and 52 deletions

View file

@ -42,4 +42,16 @@ addEventListener("direct-upload:end", function (event) {
element = document.getElementById("direct-upload-" + id);
element.classList.add("direct-upload--complete");
});
});
addEventListener('load', function() {
var submitButtons = document.querySelectorAll('form button[type=submit][data-action]');
var hiddenInput = document.querySelector('form input[type=hidden][name=submit_action]');
submitButtons = [].slice.call(submitButtons);
submitButtons.forEach(function(button) {
button.addEventListener('click', function() {
hiddenInput.value = button.getAttribute('data-action');
});
});
});

View file

@ -0,0 +1,20 @@
@import "colors";
@import "constants";
.dossier-edit {
.dossier-header {
background-color: $light-grey;
.container {
padding: $default-padding;
}
h1 {
font-size: 22px;
}
}
.thanks {
padding: (1.5 * $default-padding) 0;
}
}

View file

@ -15,6 +15,7 @@
label {
margin-bottom: $default-padding;
display: block;
font-weight: bold;
.mandatory {
color: $dark-red;
@ -115,7 +116,7 @@
input.touched:invalid,
textarea.touched:invalid {
border-color: $dark-red;
border: 1px solid $dark-red;
box-shadow: 0px 0px 5px $dark-red;
}
@ -239,4 +240,15 @@
margin-right: 0;
}
}
.pj-input {
input[type=file] {
margin: $default-padding 0 (2 * $default-padding);
padding: 2px;
}
.piece-description {
margin-bottom: $default-padding;
}
}
}

View file

@ -31,12 +31,62 @@ module NewUser
end
end
def modifier
@dossier = dossier_with_champs
# TODO: remove when the champs are unifed
if !@dossier.autorisation_donnees
if dossier.procedure.for_individual
redirect_to identite_dossier_path(@dossier)
else
redirect_to users_dossier_path(@dossier)
end
end
end
# FIXME: remove PiecesJustificativesService
# delegate draft save logic to champ ?
def update
@dossier = dossier_with_champs
errors = PiecesJustificativesService.upload!(@dossier, current_user, params)
if !@dossier.update(champs_params)
errors += @dossier.errors.full_messages
end
if !draft?
errors += @dossier.champs.select(&:mandatory_and_blank?)
.map { |c| "Le champ #{c.libelle.truncate(200)} doit être rempli." }
errors += PiecesJustificativesService.missing_pj_error_messages(@dossier)
end
if errors.present?
flash.now.alert = errors
render :modifier
elsif draft?
flash.now.notice = 'Votre brouillon a bien été sauvegardé.'
render :modifier
else
@dossier.en_construction!
redirect_to users_dossier_recapitulatif_path(@dossier)
end
end
private
def champs_params
params.require(:dossier).permit(champs_attributes: [:id, :value, :piece_justificative_file, value: []])
end
def dossier
Dossier.find(params[:id] || params[:dossier_id])
end
def dossier_with_champs
@dossier_with_champs ||= current_user.dossiers.includes(champs: :type_de_champ).find(params[:id])
end
def ensure_ownership!
if dossier.user != current_user
flash[:alert] = "Vous n'avez pas accès à ce dossier"
@ -51,5 +101,9 @@ module NewUser
def dossier_params
params.require(:dossier).permit(:autorisation_donnees)
end
def draft?
params[:submit_action] == 'draft'
end
end
end

View file

@ -108,6 +108,10 @@ class Dossier < ActiveRecord::Base
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
def ordered_champs_v2
champs.includes(:type_de_champ).order('types_de_champ.order_place')
end
def ordered_champs_private
# TODO: use the line below when the procedure preview does not leak champ with dossier_id == 0
# champs_private.includes(:type_de_champ).order('types_de_champ.order_place')

View file

@ -0,0 +1,62 @@
.dossier-edit
.dossier-header
.container
%h1= @dossier.procedure.libelle
.container
%p.thanks Les champs avec une asterisque (*) sont obligatoires.
= form_for @dossier, html: { class: 'form', multipart: true } do |f|
= f.fields_for :champs, @dossier.ordered_champs_v2 do |champ_form|
- champ = champ_form.object
= render partial: "new_gestionnaire/dossiers/editable_champs/editable_champ",
locals: { champ: champ, form: champ_form }
- tpjs = @dossier.types_de_piece_justificative.order('order_place ASC')
- if tpjs.present?
.card.featured
.card-title
Pièces-jointes
- tpjs.each do |tpj|
.pj-input
%label{ for: "piece_justificative_#{tpj.id}" }
= tpj.libelle
- if tpj.mandatory?
%span.mandatory *
%p.piece-description= tpj.description
- if tpj.lien_demarche.present?
%p.piece-description
Récupérer le formulaire vierge pour mon dossier :
= link_to "Télécharger", tpj.lien_demarche, target: :blank
- if @dossier.was_piece_justificative_uploaded_for_type_id?(tpj.id)
- pj = @dossier.retrieve_last_piece_justificative_by_type(tpj.id)
%p
Pièce-jointe déjà importée :
= link_to pj.original_filename, pj.content_url, target: :blank
= file_field_tag "piece_justificative_#{tpj.id}",
accept: PieceJustificative.accept_format,
max_file_size: 6.megabytes,
required: (tpj.mandatory? && !@dossier.was_piece_justificative_uploaded_for_type_id?(tpj.id))
.send-wrapper
= hidden_field_tag 'submit_action', 'draft'
- if @dossier.brouillon?
= f.button 'Enregistrer le brouillon',
formnovalidate: true,
class: 'button send',
data: { action: 'draft', disable_with: 'Envoi...' }
= f.button 'Soumettre le dossier',
class: 'button send primary',
data: { action: 'submit', disable_with: 'Envoi...' }
- else
= f.button 'Modifier le dossier',
class: 'button send primary',
data: { action: 'submit', disable_with: 'Envoi...' }

View file

@ -202,10 +202,11 @@ Rails.application.routes.draw do
get "patron" => "root#patron"
scope module: 'new_user' do
resources :dossiers, only: [] do
resources :dossiers, only: [:update] do
member do
get 'identite'
patch 'update_identite'
get 'modifier'
end
get 'attestation'
end

View file

@ -103,4 +103,114 @@ describe NewUser::DossiersController, type: :controller do
end
end
end
describe '#modifier' do
before { sign_in(user) }
let!(:dossier) { create(:dossier, user: user, autorisation_donnees: true) }
subject { get :modifier, params: { id: dossier.id } }
context 'when autorisation_donnees is checked' do
it { is_expected.to render_template(:modifier) }
end
context 'when autorisation_donnees is not checked' do
before { dossier.update_columns(autorisation_donnees: false) }
context 'when the dossier is for personne morale' do
it { is_expected.to redirect_to(users_dossier_path(dossier)) }
end
context 'when the dossier is for an personne physique' do
before { dossier.procedure.update_attributes(for_individual: true) }
it { is_expected.to redirect_to(identite_dossier_path(dossier)) }
end
end
end
describe '#edit' do
before { sign_in(user) }
let!(:dossier) { create(:dossier, user: user) }
it 'returns the edit page' do
get :modifier, params: { id: dossier.id }
expect(response).to have_http_status(:success)
end
end
describe '#update' do
before { sign_in(user) }
let!(:dossier) { create(:dossier, user: user) }
let(:first_champ) { dossier.champs.first }
let(:value) { 'beautiful value' }
let(:submit_payload) do
{
id: dossier.id,
dossier: {
champs_attributes: {
id: first_champ.id,
value: value
}
}
}
end
let(:payload) { submit_payload }
subject { patch :update, params: payload }
it 'updates the champs' do
subject
expect(response).to redirect_to(users_dossier_recapitulatif_path(dossier))
expect(first_champ.reload.value).to eq('beautiful value')
expect(dossier.reload.state).to eq('en_construction')
end
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(:errors)
.and_return(double(full_messages: ['nop']))
subject
end
it { expect(response).to render_template(:modifier) }
it { expect(flash.alert).to eq(['nop']) }
end
context 'when the pj service returns an error' do
before do
expect(PiecesJustificativesService).to receive(:upload!).and_return(['nop'])
subject
end
it { expect(response).to render_template(:modifier) }
it { expect(flash.alert).to eq(['nop']) }
end
context 'when a mandatory champ is missing' do
let(:value) { nil }
before do
first_champ.type_de_champ.update_attributes(mandatory: true, libelle: 'l')
allow(PiecesJustificativesService).to receive(:missing_pj_error_messages).and_return(['pj'])
subject
end
it { expect(response).to render_template(:modifier) }
it { expect(flash.alert).to eq(['Le champ l doit être rempli.', 'pj']) }
context 'and the user saves a draft' do
let(:payload) { submit_payload.merge(submit_action: 'draft') }
it { expect(response).to render_template(:modifier) }
it { expect(flash.notice).to eq('Votre brouillon a bien été sauvegardé.') }
it { expect(dossier.reload.state).to eq('brouillon') }
end
end
end
end

View file

@ -110,5 +110,33 @@ FactoryBot.define do
procedure.archived_at = Time.now
end
end
trait :with_all_champs_mandatory do
after(:build) do |procedure, _evaluator|
tdcs = []
tdcs << create(:type_de_champ, type_champ: 'text', mandatory: true, libelle: 'text')
tdcs << create(:type_de_champ, type_champ: 'textarea', mandatory: true, libelle: 'textarea')
tdcs << create(:type_de_champ, type_champ: 'date', mandatory: true, libelle: 'date')
tdcs << create(:type_de_champ, type_champ: 'datetime', mandatory: true, libelle: 'datetime')
tdcs << create(:type_de_champ, type_champ: 'number', mandatory: true, libelle: 'number')
tdcs << create(:type_de_champ, type_champ: 'checkbox', mandatory: true, libelle: 'checkbox')
tdcs << create(:type_de_champ, type_champ: 'civilite', mandatory: true, libelle: 'civilite')
tdcs << create(:type_de_champ, type_champ: 'email', mandatory: true, libelle: 'email')
tdcs << create(:type_de_champ, type_champ: 'phone', mandatory: true, libelle: 'phone')
tdcs << create(:type_de_champ, type_champ: 'yes_no', mandatory: true, libelle: 'yes_no')
tdcs << create(:type_de_champ, :type_drop_down_list, mandatory: true, libelle: 'simple_drop_down_list')
tdcs << create(:type_de_champ, :type_drop_down_list, type_champ: 'multiple_drop_down_list', mandatory: true, libelle: 'multiple_drop_down_list')
tdcs << create(:type_de_champ, type_champ: 'pays', mandatory: true, libelle: 'pays')
tdcs << create(:type_de_champ, type_champ: 'regions', mandatory: true, libelle: 'regions')
tdcs << create(:type_de_champ, type_champ: 'departements', mandatory: true, libelle: 'departements')
tdcs << create(:type_de_champ, type_champ: 'engagement', mandatory: true, libelle: 'engagement')
tdcs << create(:type_de_champ, type_champ: 'header_section', mandatory: true, libelle: 'header_section')
tdcs << create(:type_de_champ, type_champ: 'explication', mandatory: true, libelle: 'explication')
tdcs << create(:type_de_champ, :type_dossier_link, mandatory: true, libelle: 'dossier_link')
tdcs << create(:type_de_champ, type_champ: 'piece_justificative', mandatory: true, libelle: 'piece_justificative')
procedure.types_de_champ = tdcs
end
end
end
end

View file

@ -0,0 +1,155 @@
require 'spec_helper'
feature 'The user' do
let(:password) { 'secret_password' }
let!(:user) { create(:user, password: password) }
let!(:procedure) { create(:procedure, :published, :for_individual, :with_all_champs_mandatory) }
let(:user_dossier) { user.dossiers.first }
# TODO: check
# the order
# there are no extraneous input
# attached file works
scenario 'fill a dossier', js: true do
allow(Champ).to receive(:regions).and_return(['region1', 'region2']).at_least(:once)
allow(Champ).to receive(:departements).and_return(['dep1', 'dep2']).at_least(:once)
log_in(user.email, password, procedure)
fill_individual
# fill data
fill_in('text', with: 'super texte')
fill_in('textarea', with: 'super textarea')
fill_in('date', with: '12/12/2012')
select_date_and_time(DateTime.parse('06/01/1985 7h05'), form_id_for('datetime'))
fill_in('number', with: '42')
check('checkbox')
choose('Madame')
fill_in('email', with: 'loulou@yopmail.com')
fill_in('phone', with: '1234567890')
choose('Non')
select('val2', from: form_id_for('simple_drop_down_list'))
select('val1', from: form_id_for('multiple_drop_down_list'))
select('val3', from: form_id_for('multiple_drop_down_list'))
select('AUSTRALIE', from: 'pays')
select('region2', from: 'regions')
select('dep2', from: 'departements')
check('engagement')
fill_in('dossier_link', with: '123')
# do not know how to make it work...
# find('form input[type="file"]').set(Rails.root.join('spec/fixtures/white.png'))
click_on 'Enregistrer le brouillon'
# check data on the dossier
expect(user_dossier.brouillon?).to be true
expect(champ_value_for('text')).to eq('super texte')
expect(champ_value_for('textarea')).to eq('super textarea')
expect(champ_value_for('date')).to eq('2012-12-12')
expect(champ_value_for('datetime')).to eq('06/01/1985 07:05')
expect(champ_value_for('number')).to eq('42')
expect(champ_value_for('checkbox')).to eq('on')
expect(champ_value_for('civilite')).to eq('Mme.')
expect(champ_value_for('email')).to eq('loulou@yopmail.com')
expect(champ_value_for('phone')).to eq('1234567890')
expect(champ_value_for('yes_no')).to eq('false')
expect(champ_value_for('simple_drop_down_list')).to eq('val2')
expect(JSON.parse(champ_value_for('multiple_drop_down_list'))).to match(['val1', 'val3'])
expect(champ_value_for('pays')).to eq('AUSTRALIE')
expect(champ_value_for('regions')).to eq('region2')
expect(champ_value_for('departements')).to eq('dep2')
expect(champ_value_for('engagement')).to eq('on')
expect(champ_value_for('dossier_link')).to eq('123')
## check data on the gui
expect(page).to have_field('text', with: 'super texte')
expect(page).to have_field('textarea', with: 'super textarea')
expect(page).to have_field('date', with: '2012-12-12')
check_date_and_time(DateTime.parse('06/01/1985 7:05'), form_id_for('datetime'))
expect(page).to have_field('number', with: '42')
expect(page).to have_checked_field('checkbox')
expect(page).to have_checked_field('Madame')
expect(page).to have_field('email', with: 'loulou@yopmail.com')
expect(page).to have_field('phone', with: '1234567890')
expect(page).to have_checked_field('Non')
expect(page).to have_select('simple_drop_down_list', selected: 'val2')
expect(page).to have_select('multiple_drop_down_list', selected: ['val1', 'val3'])
expect(page).to have_select('pays', selected: 'AUSTRALIE')
expect(page).to have_select('regions', selected: 'region2')
expect(page).to have_select('departement', selected: 'dep2')
expect(page).to have_checked_field('engagement')
expect(page).to have_field('dossier_link', with: '123')
end
let(:simple_procedure) do
tdcs = [create(:type_de_champ, type_champ: 'text', mandatory: true, libelle: 'text')]
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
end
scenario 'save an incomplete dossier as draft but cannot not submit it', js: true do
log_in(user.email, password, simple_procedure)
fill_individual
click_on 'Enregistrer le brouillon'
expect(page).to have_content('Votre brouillon a bien été sauvegardé')
expect(page).to have_current_path(dossier_path(user_dossier))
click_on 'Soumettre le dossier'
expect(user_dossier.reload.brouillon?).to be(true)
expect(page).to have_current_path(dossier_path(user_dossier))
fill_in('text', with: 'super texte')
click_on 'Soumettre le dossier'
expect(user_dossier.reload.en_construction?).to be(true)
expect(champ_value_for('text')).to eq('super texte')
expect(page).to have_current_path(users_dossier_recapitulatif_path(user_dossier))
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 form_id_for(libelle)
find(:xpath, ".//label[contains(text()[normalize-space()], '#{libelle}')]")[:for]
end
def champ_value_for(libelle)
champs = user_dossier.champs
champs.find { |c| c.libelle == libelle }.value
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 select_date_and_time(date, field)
select date.strftime('%Y'), from: "#{field}_1i" # year
select I18n.l(date, format: '%B'), from: "#{field}_2i" # month
select date.strftime('%-d'), from: "#{field}_3i" # day
select date.strftime('%H'), from: "#{field}_4i" # hour
select date.strftime('%M'), from: "#{field}_5i" # minute
end
def check_date_and_time(date, field)
expect(page).to have_select("#{field}_1i", selected: date.strftime('%Y'))
expect(page).to have_select("#{field}_2i", selected: I18n.l(date, format: '%B'))
expect(page).to have_select("#{field}_3i", selected: date.strftime('%-d'))
expect(page).to have_select("#{field}_4i", selected: date.strftime('%H'))
expect(page).to have_select("#{field}_5i", selected: date.strftime('%M'))
end
end

View file

@ -1,49 +0,0 @@
require 'spec_helper'
feature 'As a User I want to edit a dossier I own' do
let(:user) { create(:user) }
let(:procedure_for_individual) { create(:procedure, :published, :for_individual, :with_type_de_champ, :with_two_type_de_piece_justificative, :with_dossier_link) }
let!(:dossier) { create(:dossier, :with_entreprise, :for_individual, :with_dossier_link, procedure: procedure_for_individual, user: user, autorisation_donnees: true, state: 'en_construction') }
before "Create dossier and visit root path" do
login_as user, scope: :user
visit root_path
end
context 'After sign_in, I can navigate through dossiers indexes and edit a dossier' do
scenario 'After sign_in, I can see dossiers "à traiter" (default), and other indexes' do
expect(page.find('#a_traiter')['class']).to eq('active procedure-list-element')
page.find_by_id('brouillon').click
page.find_by_id('a_traiter').click
page.find_by_id('en_instruction').click
page.find_by_id('termine').click
page.find_by_id('invite').click
end
scenario 'Getting a dossier, I want to create a new message on', js: true do
page.find_by_id("tr_dossier_#{dossier.id.to_s}").click
expect(page).to have_current_path(users_dossier_recapitulatif_path(Dossier.first.id.to_s))
page.find_by_id('open-message').click
page.execute_script("$('#texte_commentaire').data('wysihtml5').editor.setValue('Contenu du nouveau message')")
page.find_by_id('save-message').click
expect(page.find('.last-commentaire .content').text).to eq('Contenu du nouveau message')
end
scenario 'On the same dossier, I want to edit informations', js: true do
page.find_by_id("tr_dossier_#{dossier.id.to_s}").click
expect(page).to have_current_path(users_dossier_recapitulatif_path(dossier.id.to_s))
# Linked Dossier
linked_dossier_id = dossier.champs.find { |c| c.type_de_champ.type_champ == 'dossier_link' }.value
expect(page).to have_link("Dossier #{linked_dossier_id}")
page.find_by_id('edit-dossier').click
expect(page).to have_current_path(users_dossier_description_path(dossier.id.to_s))
champ_id = dossier.champs.find { |t| t.type_champ == "text" }.id
fill_in "champs_#{champ_id.to_s}", with: 'Contenu du champ 1'
page.find_by_id('modification_terminee').click
expect(page).to have_current_path(users_dossier_recapitulatif_path(dossier.id.to_s))
expect(page.find("#champ-#{champ_id}-value").text).to eq('Contenu du champ 1')
end
end
end