commit
d19bccb935
19 changed files with 180 additions and 107 deletions
|
@ -89,6 +89,9 @@ module ProcedureHelper
|
|||
.merge(include: TYPES_DE_CHAMP_INCLUDE.merge(types_de_champ: TYPES_DE_CHAMP_BASE))
|
||||
|
||||
def types_de_champ_as_json(types_de_champ)
|
||||
types_de_champ.as_json(TYPES_DE_CHAMP)
|
||||
types_de_champ.includes(:drop_down_list,
|
||||
piece_justificative_template_attachment: :blob,
|
||||
types_de_champ: [:drop_down_list, piece_justificative_template_attachment: :blob])
|
||||
.as_json(TYPES_DE_CHAMP)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -130,9 +130,7 @@ export default {
|
|||
addChamp() {
|
||||
this.typesDeChamp.push({
|
||||
type_champ: 'text',
|
||||
drop_down_list: {},
|
||||
types_de_champ: [],
|
||||
options: {}
|
||||
types_de_champ: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,14 @@ function initEditor(el) {
|
|||
|
||||
this.update = update;
|
||||
this.updateAll = updateAll;
|
||||
|
||||
// We add an initial type de champ here if form is empty
|
||||
if (this.state.typesDeChamp.length === 0) {
|
||||
this.state.typesDeChamp.push({
|
||||
type_champ: 'text',
|
||||
types_de_champ: []
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,29 +1,28 @@
|
|||
addEventListener('turbolinks:load', () => {
|
||||
const primaries = document.querySelectorAll('select[data-secondary-options]');
|
||||
import { delegate } from '@utils';
|
||||
|
||||
for (let primary of primaries) {
|
||||
let secondary = document.querySelector(
|
||||
`select[data-secondary-id="${primary.dataset.primaryId}"]`
|
||||
);
|
||||
let secondaryOptions = JSON.parse(primary.dataset.secondaryOptions);
|
||||
const PRIMARY_SELECTOR = 'select[data-secondary-options]';
|
||||
const SECONDARY_SELECTOR = 'select[data-secondary]';
|
||||
const CHAMP_SELECTOR = '.editable-champ';
|
||||
|
||||
primary.addEventListener('change', e => {
|
||||
let option, options, element;
|
||||
delegate('change', PRIMARY_SELECTOR, evt => {
|
||||
const primary = evt.target;
|
||||
const secondary = primary
|
||||
.closest(CHAMP_SELECTOR)
|
||||
.querySelector(SECONDARY_SELECTOR);
|
||||
const options = JSON.parse(primary.dataset.secondaryOptions);
|
||||
|
||||
while ((option = secondary.firstChild)) {
|
||||
secondary.removeChild(option);
|
||||
}
|
||||
selectOptions(secondary, options[primary.value]);
|
||||
});
|
||||
|
||||
options = secondaryOptions[e.target.value];
|
||||
function selectOptions(selectElement, options) {
|
||||
selectElement.innerHTML = '';
|
||||
|
||||
for (let option of options) {
|
||||
element = document.createElement('option');
|
||||
let element = document.createElement('option');
|
||||
element.textContent = option;
|
||||
element.value = option;
|
||||
secondary.appendChild(element);
|
||||
selectElement.appendChild(element);
|
||||
}
|
||||
|
||||
secondary.selectedIndex = 0;
|
||||
});
|
||||
selectElement.selectedIndex = 0;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,7 +4,6 @@ const BUTTON_SELECTOR = '.button.remove-row';
|
|||
const DESTROY_INPUT_SELECTOR = 'input[type=hidden][name*=_destroy]';
|
||||
const CHAMP_SELECTOR = '.editable-champ';
|
||||
|
||||
addEventListener('turbolinks:load', () => {
|
||||
delegate('click', BUTTON_SELECTOR, evt => {
|
||||
evt.preventDefault();
|
||||
|
||||
|
@ -21,4 +20,3 @@ addEventListener('turbolinks:load', () => {
|
|||
evt.target.remove();
|
||||
row.classList.remove('row');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@ class Champs::LinkedDropDownListChamp < Champ
|
|||
end
|
||||
|
||||
def to_s
|
||||
value.present? ? [primary_value, secondary_value].compact.join(' / ') : ""
|
||||
value.present? ? [primary_value, secondary_value].select(&:present?).join(' / ') : ""
|
||||
end
|
||||
|
||||
def for_export
|
||||
|
|
|
@ -167,13 +167,11 @@ module TagsSubstitutionConcern
|
|||
end
|
||||
|
||||
def types_de_champ_tags(types_de_champ, available_for_states)
|
||||
types_de_champ.map do |tdc|
|
||||
{
|
||||
libelle: tdc.libelle,
|
||||
description: tdc.description,
|
||||
available_for_states: available_for_states
|
||||
}
|
||||
tags = types_de_champ.flat_map(&:tags_for_template)
|
||||
tags.each do |tag|
|
||||
tag[:available_for_states] = available_for_states
|
||||
end
|
||||
tags
|
||||
end
|
||||
|
||||
def replace_tags(text, dossier)
|
||||
|
@ -181,10 +179,9 @@ module TagsSubstitutionConcern
|
|||
return ''
|
||||
end
|
||||
|
||||
text = replace_type_de_champ_tags(text, filter_tags(champ_public_tags), dossier.champs)
|
||||
text = replace_type_de_champ_tags(text, filter_tags(champ_private_tags), dossier.champs_private)
|
||||
|
||||
tags_and_datas = [
|
||||
[champ_public_tags, dossier.champs],
|
||||
[champ_private_tags, dossier.champs_private],
|
||||
[dossier_tags, dossier],
|
||||
[INDIVIDUAL_TAGS, dossier.individual],
|
||||
[ENTREPRISE_TAGS, dossier.etablissement&.entreprise]
|
||||
|
@ -195,38 +192,29 @@ module TagsSubstitutionConcern
|
|||
.inject(text) { |acc, (tags, data)| replace_tags_with_values_from_data(acc, tags, data) }
|
||||
end
|
||||
|
||||
def replace_type_de_champ_tags(text, types_de_champ, dossier_champs)
|
||||
types_de_champ.inject(text) do |acc, tag|
|
||||
champ = dossier_champs
|
||||
.select { |dossier_champ| dossier_champ.libelle == tag[:libelle] }
|
||||
.first
|
||||
|
||||
replace_tag(acc, tag, champ)
|
||||
end
|
||||
end
|
||||
|
||||
def replace_tags_with_values_from_data(text, tags, data)
|
||||
if data.present?
|
||||
tags.inject(text) do |acc, tag|
|
||||
if tag.key?(:target)
|
||||
value = data.send(tag[:target])
|
||||
else
|
||||
value = instance_exec(data, &tag[:lambda])
|
||||
end
|
||||
replace_tag(acc, tag, value)
|
||||
replace_tag(acc, tag, data)
|
||||
end
|
||||
else
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
def replace_tag(text, tag, value)
|
||||
def replace_tag(text, tag, data)
|
||||
libelle = Regexp.quote(tag[:libelle])
|
||||
|
||||
# allow any kind of space (non-breaking or other) in the tag’s libellé to match any kind of space in the template
|
||||
# (the '\\ |' is there because plain ASCII spaces were escaped by preceding Regexp.quote)
|
||||
libelle.gsub!(/\\ |[[:blank:]]/, "[[:blank:]]")
|
||||
|
||||
if tag.key?(:target)
|
||||
value = data.send(tag[:target])
|
||||
else
|
||||
value = instance_exec(data, &tag[:lambda])
|
||||
end
|
||||
|
||||
text.gsub(/--#{libelle}--/, value.to_s)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -186,7 +186,7 @@ class Dossier < ApplicationRecord
|
|||
"Dossier en brouillon répondant à la démarche ",
|
||||
procedure.libelle,
|
||||
" gérée par l'organisme ",
|
||||
procedure.organisation
|
||||
procedure.organisation_name
|
||||
]
|
||||
else
|
||||
parts = [
|
||||
|
@ -195,7 +195,7 @@ class Dossier < ApplicationRecord
|
|||
" sur la démarche ",
|
||||
procedure.libelle,
|
||||
" gérée par l'organisme ",
|
||||
procedure.organisation
|
||||
procedure.organisation_name
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ class TypeDeChamp < ApplicationRecord
|
|||
has_many :types_de_champ, -> { ordered }, foreign_key: :parent_id, class_name: 'TypeDeChamp', dependent: :destroy
|
||||
|
||||
store_accessor :options, :cadastres, :quartiers_prioritaires, :parcelles_agricoles, :old_pj
|
||||
delegate :tags_for_template, to: :dynamic_type
|
||||
|
||||
# TODO simplify after migrating `options` column to (non YAML encoded) JSON
|
||||
class MaybeYaml
|
||||
|
|
|
@ -21,6 +21,34 @@ class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBas
|
|||
secondary_options
|
||||
end
|
||||
|
||||
def tags_for_template
|
||||
tags = super
|
||||
l = libelle
|
||||
tags.push(
|
||||
{
|
||||
libelle: "#{l}/primaire",
|
||||
description: "#{description} (menu primaire)",
|
||||
lambda: -> (champs) {
|
||||
champs
|
||||
.detect { |champ| champ.libelle == l }
|
||||
&.primary_value
|
||||
}
|
||||
}
|
||||
)
|
||||
tags.push(
|
||||
{
|
||||
libelle: "#{l}/secondaire",
|
||||
description: "#{description} (menu secondaire)",
|
||||
lambda: -> (champs) {
|
||||
champs
|
||||
.detect { |champ| champ.libelle == l }
|
||||
&.secondary_value
|
||||
}
|
||||
}
|
||||
)
|
||||
tags
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_presence_of_primary_options
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
class TypesDeChamp::TypeDeChampBase
|
||||
include ActiveModel::Validations
|
||||
|
||||
delegate :libelle, to: :@type_de_champ
|
||||
delegate :description, :libelle, to: :@type_de_champ
|
||||
|
||||
def initialize(type_de_champ)
|
||||
@type_de_champ = type_de_champ
|
||||
end
|
||||
|
||||
def tags_for_template
|
||||
l = libelle
|
||||
[
|
||||
{
|
||||
libelle: l,
|
||||
description: description,
|
||||
lambda: -> (champs) {
|
||||
champs.detect { |champ| champ.libelle == l }
|
||||
}
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
class ClamavService
|
||||
def self.safe_file?(file_path)
|
||||
if Rails.env.development?
|
||||
Rails.logger.info("Rails.env = development => fake scan") # FIXME : remove me
|
||||
return true
|
||||
end
|
||||
|
||||
FileUtils.chmod(0666, file_path)
|
||||
|
||||
client = ClamAV::Client.new
|
||||
response = client.execute(ClamAV::Commands::ScanCommand.new(file_path))
|
||||
Rails.logger.info("ClamAV response for #{file_path} : #{response.first.class.name}") # FIXME : remove me
|
||||
response.first.class != ClamAV::VirusResponse
|
||||
response = client.execute(ClamAV::Commands::ScanCommand.new(file_path)).first
|
||||
if response.class == ClamAV::SuccessResponse
|
||||
true
|
||||
elsif response.class == ClamAV::VirusResponse
|
||||
false
|
||||
elsif response.class == ClamAV::ErrorResponse
|
||||
raise "ClamAV ErrorResponse : #{response.error_str}"
|
||||
else
|
||||
raise "ClamAV unkown response #{response.class.name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
- 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 } }
|
||||
{ data: { secondary_options: champ.secondary_options } }
|
||||
= form.select :secondary_value,
|
||||
champ.secondary_options[champ.primary_value],
|
||||
{ required: champ.mandatory? },
|
||||
{ data: { "secondary-id" => champ_id } }
|
||||
{ data: { secondary: true } }
|
||||
|
|
|
@ -100,9 +100,6 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
|
|||
page.refresh
|
||||
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
|
||||
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
|
||||
expect(page).to have_content('Formulaire mis à jour')
|
||||
|
@ -131,9 +128,6 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
|
|||
scenario 'After adding champ and file, make publication' do
|
||||
page.refresh
|
||||
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
|
||||
expect(page).to have_content('Formulaire mis à jour')
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ feature 'As an administrateur I edit procedure', js: true do
|
|||
end
|
||||
|
||||
it "Add a new champ" do
|
||||
click_on 'Supprimer'
|
||||
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
|
@ -31,7 +33,6 @@ feature 'As an administrateur I edit procedure', js: true do
|
|||
click_on 'Ajouter un champ'
|
||||
click_on 'Ajouter un champ'
|
||||
click_on 'Ajouter un champ'
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
expect(page).not_to have_content('Le libellé doit être rempli.')
|
||||
expect(page).not_to have_content('Modifications non sauvegardées.')
|
||||
|
@ -66,9 +67,6 @@ feature 'As an administrateur I edit procedure', js: true do
|
|||
end
|
||||
|
||||
it "Remove champs" do
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
||||
expect(page).to have_content('Formulaire mis à jour')
|
||||
page.refresh
|
||||
|
@ -78,13 +76,10 @@ feature 'As an administrateur I edit procedure', js: true do
|
|||
expect(page).not_to have_content('Supprimer')
|
||||
page.refresh
|
||||
|
||||
expect(page).not_to have_content('Supprimer')
|
||||
expect(page).to have_content('Supprimer', count: 1)
|
||||
end
|
||||
|
||||
it "Only add valid champs" do
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_description')
|
||||
fill_in 'procedure_types_de_champ_attributes_0_description', with: 'déscription du champ'
|
||||
expect(page).to have_content('Le libellé doit être rempli.')
|
||||
|
@ -95,9 +90,6 @@ feature 'As an administrateur I edit procedure', js: true do
|
|||
end
|
||||
|
||||
it "Add repetition champ" do
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
||||
select('Bloc répétable', from: 'procedure_types_de_champ_attributes_0_type_champ')
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
||||
|
|
|
@ -74,7 +74,8 @@ feature 'linked dropdown lists' do
|
|||
|
||||
def secondary_id_for(libelle)
|
||||
primary_id = primary_id_for(libelle)
|
||||
link = find("\##{primary_id}")['data-primary-id']
|
||||
find("[data-secondary-id=\"#{link}\"]")['id']
|
||||
find("\##{primary_id}")
|
||||
.ancestor('.editable-champ')
|
||||
.find("[data-secondary]")['id']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -110,6 +110,40 @@ describe TagsSubstitutionConcern, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the procedure has a linked drop down menus type de champ' do
|
||||
let(:types_de_champ) do
|
||||
[
|
||||
create(:type_de_champ_linked_drop_down_list, libelle: 'libelle')
|
||||
]
|
||||
end
|
||||
|
||||
let(:template) { 'tout : --libelle--, primaire : --libelle/primaire--, secondaire : --libelle/secondaire--' }
|
||||
|
||||
context 'and the champ has no value' do
|
||||
it { is_expected.to eq('tout : , primaire : , secondaire : ') }
|
||||
end
|
||||
|
||||
context 'and the champ has a primary value' do
|
||||
before do
|
||||
c = dossier.champs.detect { |champ| champ.libelle == 'libelle' }
|
||||
c.primary_value = 'primo'
|
||||
c.save
|
||||
end
|
||||
it { is_expected.to eq('tout : primo, primaire : primo, secondaire : ') }
|
||||
end
|
||||
|
||||
context 'and the champ has a primary and secondary value' do
|
||||
before do
|
||||
c = dossier.champs.detect { |champ| champ.libelle == 'libelle' }
|
||||
c.primary_value = 'primo'
|
||||
c.secondary_value = 'secundo'
|
||||
c.save
|
||||
end
|
||||
|
||||
it { is_expected.to eq('tout : primo / secundo, primaire : primo, secondaire : secundo') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the dossier has a motivation' do
|
||||
let(:dossier) { create(:dossier, motivation: 'motivation') }
|
||||
|
||||
|
|
|
@ -236,14 +236,15 @@ describe Dossier do
|
|||
end
|
||||
|
||||
describe "#text_summary" do
|
||||
let(:procedure) { create(:procedure, libelle: "Démarche", organisation: "Organisme") }
|
||||
let(:service) { create(:service, nom: 'nom du service') }
|
||||
let(:procedure) { create(:procedure, libelle: "Démarche", organisation: "Organisme", service: service) }
|
||||
|
||||
context 'when the dossier has been en_construction' do
|
||||
let(:dossier) { create :dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction), en_construction_at: "31/12/2010".to_date }
|
||||
|
||||
subject { dossier.text_summary }
|
||||
|
||||
it { is_expected.to eq("Dossier déposé le 31/12/2010 sur la démarche Démarche gérée par l'organisme Organisme") }
|
||||
it { is_expected.to eq("Dossier déposé le 31/12/2010 sur la démarche Démarche gérée par l'organisme nom du service") }
|
||||
end
|
||||
|
||||
context 'when the dossier has not been en_construction' do
|
||||
|
@ -251,7 +252,7 @@ describe Dossier do
|
|||
|
||||
subject { dossier.text_summary }
|
||||
|
||||
it { is_expected.to eq("Dossier en brouillon répondant à la démarche Démarche gérée par l'organisme Organisme") }
|
||||
it { is_expected.to eq("Dossier en brouillon répondant à la démarche Démarche gérée par l'organisme nom du service") }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,17 +4,27 @@ describe ClamavService do
|
|||
describe '.safe_file?' do
|
||||
let(:path_file) { '/tmp/plop.txt' }
|
||||
|
||||
subject { ClamavService.safe_file? path_file }
|
||||
subject { ClamavService.safe_file?(path_file) }
|
||||
|
||||
before do
|
||||
client = instance_double("ClamAV::Client", :execute => [ClamAV::SuccessResponse])
|
||||
client = double("ClamAV::Client", execute: [response])
|
||||
allow(ClamAV::Client).to receive(:new).and_return(client)
|
||||
end
|
||||
|
||||
it 'change permission of file path' do
|
||||
allow(FileUtils).to receive(:chmod).with(0666, path_file).and_return(true)
|
||||
end
|
||||
|
||||
subject
|
||||
context 'When response type is ClamAV::SuccessResponse' do
|
||||
let(:response) { ClamAV::SuccessResponse.new("OK") }
|
||||
it { expect(subject).to eq(true) }
|
||||
end
|
||||
|
||||
context 'When response type is ClamAV::VirusResponse' do
|
||||
let(:response) { ClamAV::VirusResponse.new("KO", "VirusN4ame") }
|
||||
it { expect(subject).to eq(false) }
|
||||
end
|
||||
|
||||
context 'When response type is ClamAV::ErrorResponse' do
|
||||
let(:response) { ClamAV::ErrorResponse.new("File not found") }
|
||||
it { expect { subject }.to raise_error("ClamAV ErrorResponse : File not found") }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue