diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb index efcd36dcd..a391e9272 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -1,5 +1,6 @@ class AttestationTemplate < ApplicationRecord include ActionView::Helpers::NumberHelper + include TagsSubstitutionConcern belongs_to :procedure @@ -11,16 +12,6 @@ class AttestationTemplate < ApplicationRecord FILE_MAX_SIZE_IN_MB = 0.5 - def tags - if procedure.for_individual? - identity_tags = individual_tags - else - identity_tags = entreprise_tags + etablissement_tags - end - - identity_tags + dossier_tags + procedure_type_de_champ_public_private_tags - end - def attestation_for(dossier) Attestation.new(title: replace_tags(title, dossier), pdf: build_pdf(dossier)) end @@ -53,33 +44,6 @@ class AttestationTemplate < ApplicationRecord end end - def procedure_type_de_champ_public_private_tags - (procedure.types_de_champ + procedure.types_de_champ_private) - .map { |tdc| { libelle: tdc.libelle, description: tdc.description } } - end - - def dossier_tags - [{ libelle: 'motivation', description: '', target: :motivation }, - { libelle: 'numéro du dossier', description: '', target: :id }] - end - - def individual_tags - [{ libelle: 'civilité', description: 'M., Mme', target: :gender }, - { libelle: 'nom', description: "nom de l'usager", target: :nom }, - { libelle: 'prénom', description: "prénom de l'usager", target: :prenom }] - end - - def entreprise_tags - [{ libelle: 'SIREN', description: '', target: :siren }, - { libelle: 'numéro de TVA intracommunautaire', description: '', target: :numero_tva_intracommunautaire }, - { libelle: 'SIRET du siège social', description: '', target: :siret_siege_social }, - { libelle: 'raison sociale', description: '', target: :raison_sociale }] - end - - def etablissement_tags - [{ libelle: 'adresse', description: '', target: :inline_adresse }] - end - def build_pdf(dossier) action_view = ActionView::Base.new(ActionController::Base.view_paths, logo: logo, @@ -104,51 +68,4 @@ class AttestationTemplate < ApplicationRecord pdf end - - def replace_tags(text, dossier) - if text.nil? - return '' - end - - text = replace_type_de_champ_tags(text, procedure.types_de_champ, dossier.champs) - text = replace_type_de_champ_tags(text, procedure.types_de_champ_private, dossier.champs_private) - - tags_and_datas = [ - [dossier_tags, dossier], - [individual_tags, dossier.individual], - [entreprise_tags, dossier.entreprise], - [etablissement_tags, dossier.entreprise&.etablissement]] - - tags_and_datas.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| - replace_tag(acc, tag, data.send(tag[:target])) - end - else - text - end - end - - def replace_tag(text, tag, value) - 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:]]") - - text.gsub(/--#{libelle}--/, value.to_s) - end end diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb new file mode 100644 index 000000000..6beb0efb2 --- /dev/null +++ b/app/models/concerns/tags_substitution_concern.rb @@ -0,0 +1,89 @@ +module TagsSubstitutionConcern + extend ActiveSupport::Concern + + def tags + if procedure.for_individual? + identity_tags = individual_tags + else + identity_tags = entreprise_tags + etablissement_tags + end + + identity_tags + dossier_tags + procedure_type_de_champ_public_private_tags + end + + private + + def procedure_type_de_champ_public_private_tags + (procedure.types_de_champ + procedure.types_de_champ_private) + .map { |tdc| { libelle: tdc.libelle, description: tdc.description } } + end + + def dossier_tags + [{ libelle: 'motivation', description: '', target: :motivation }, + { libelle: 'numéro du dossier', description: '', target: :id }] + end + + def individual_tags + [{ libelle: 'civilité', description: 'M., Mme', target: :gender }, + { libelle: 'nom', description: "nom de l'usager", target: :nom }, + { libelle: 'prénom', description: "prénom de l'usager", target: :prenom }] + end + + def entreprise_tags + [{ libelle: 'SIREN', description: '', target: :siren }, + { libelle: 'numéro de TVA intracommunautaire', description: '', target: :numero_tva_intracommunautaire }, + { libelle: 'SIRET du siège social', description: '', target: :siret_siege_social }, + { libelle: 'raison sociale', description: '', target: :raison_sociale }] + end + + def etablissement_tags + [{ libelle: 'adresse', description: '', target: :inline_adresse }] + end + + def replace_tags(text, dossier) + if text.nil? + return '' + end + + text = replace_type_de_champ_tags(text, procedure.types_de_champ, dossier.champs) + text = replace_type_de_champ_tags(text, procedure.types_de_champ_private, dossier.champs_private) + + tags_and_datas = [ + [dossier_tags, dossier], + [individual_tags, dossier.individual], + [entreprise_tags, dossier.entreprise], + [etablissement_tags, dossier.entreprise&.etablissement]] + + tags_and_datas.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| + replace_tag(acc, tag, data.send(tag[:target])) + end + else + text + end + end + + def replace_tag(text, tag, value) + 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:]]") + + text.gsub(/--#{libelle}--/, value.to_s) + end +end diff --git a/spec/models/attestation_template_spec.rb b/spec/models/attestation_template_spec.rb index 23aac7166..a02b29b52 100644 --- a/spec/models/attestation_template_spec.rb +++ b/spec/models/attestation_template_spec.rb @@ -146,68 +146,16 @@ describe AttestationTemplate, type: :model do expect(attestation.pdf.filename).to start_with('attestation') end - context 'when the dossier and the procedure has an individual' do - let(:for_individual) { true } - let(:individual) { Individual.create(nom: 'nom', prenom: 'prenom', gender: 'Mme') } - - context 'and the template title use the individual tags' do - let(:template_title) { '--civilité-- --nom-- --prénom--' } - - it { expect(view_args[:title]).to eq('Mme nom prenom') } - end - end - - context 'when the dossier and the procedure has an entreprise' do - let(:for_individual) { false } - - context 'and the template title use the entreprise tags' do - let(:template_title) do - '--SIREN-- --numéro de TVA intracommunautaire-- --SIRET du siège social-- --raison sociale-- --adresse--' - end - - let(:expected_title) do - "#{entreprise.siren} #{entreprise.numero_tva_intracommunautaire} #{entreprise.siret_siege_social} #{entreprise.raison_sociale} --adresse--" - end - - it { expect(view_args[:title]).to eq(expected_title) } - - context 'and the entreprise has a etablissement with an adresse' do - let(:etablissement) { create(:etablissement, adresse: 'adresse') } - let(:template_title) { '--adresse--' } - - it { expect(view_args[:title]).to eq(etablissement.inline_adresse) } - end - end - end - context 'when the procedure has a type de champ named libelleA et libelleB' do let(:types_de_champ) do [create(:type_de_champ_public, libelle: 'libelleA'), create(:type_de_champ_public, libelle: 'libelleB')] end - context 'and the template title is nil' do - let(:template_title) { nil } - - it { expect(view_args[:title]).to eq('') } - end - - context 'and it is not used in the template title nor body' do - it { expect(view_args[:title]).to eq('title') } - it { expect(view_args[:body]).to eq('body') } - it { expect(view_args[:created_at]).to eq(Time.now) } - it { expect(view_args[:logo]).to eq(attestation_template.logo) } - it { expect(view_args[:signature]).to eq(attestation_template.signature) } - end - context 'and the are used in the template title and body' do let(:template_title) { 'title --libelleA--' } let(:template_body) { 'body --libelleB--' } - context 'and their value in the dossier are nil' do - it { expect(view_args[:title]).to eq('title ') } - end - context 'and their value in the dossier are not nil' do before do dossier.champs @@ -227,86 +175,5 @@ describe AttestationTemplate, type: :model do end end end - - context 'when the dossier has a motivation' do - let(:dossier) { create(:dossier, motivation: 'motivation') } - - context 'and the title has some dossier tags' do - let(:template_title) { 'title --motivation-- --numéro du dossier--' } - - it { expect(view_args[:title]).to eq("title motivation #{dossier.id}") } - end - end - - context 'when the procedure has a type de champ prive named libelleA' do - let(:types_de_champ_private) { [create(:type_de_champ_private, libelle: 'libelleA')] } - - context 'and the are used in the template title' do - let(:template_title) { 'title --libelleA--' } - - context 'and its value in the dossier are not nil' do - before { dossier.champs_private.first.update_attributes(value: 'libelle1') } - - it { expect(view_args[:title]).to eq('title libelle1') } - end - end - end - - context 'when the procedure has 2 types de champ date and datetime' do - let(:types_de_champ) do - [create(:type_de_champ_public, libelle: 'date', type_champ: 'date'), - create(:type_de_champ_public, libelle: 'datetime', type_champ: 'datetime')] - end - - context 'and the are used in the template title' do - let(:template_title) { 'title --date-- --datetime--' } - - context 'and its value in the dossier are not nil' do - before do - dossier.champs - .select { |champ| champ.type_champ == 'date' } - .first - .update_attributes(value: '2017-04-15') - - dossier.champs - .select { |champ| champ.type_champ == 'datetime' } - .first - .update_attributes(value: '13/09/2017 09:00') - end - - it { expect(view_args[:title]).to eq('title 15/04/2017 13/09/2017 09:00') } - end - end - end - - context "match breaking and non breaking spaces" do - before { dossier.champs.first.update_attributes(value: 'valeur') } - - shared_examples "treat all kinds of space as equivalent" do - context 'and the champ has a non breaking space' do - let(:types_de_champ) { [create(:type_de_champ_public, libelle: 'mon tag')] } - - it { expect(view_args[:body]).to eq('body valeur') } - end - - context 'and the champ has an ordinary space' do - let(:types_de_champ) { [create(:type_de_champ_public, libelle: 'mon tag')] } - - it { expect(view_args[:body]).to eq('body valeur') } - end - end - - context "when the tag has a non breaking space" do - let(:template_body) { 'body --mon tag--' } - - it_behaves_like "treat all kinds of space as equivalent" - end - - context "when the tag has an ordinary space" do - let(:template_body) { 'body --mon tag--' } - - it_behaves_like "treat all kinds of space as equivalent" - end - end end end diff --git a/spec/models/concern/tags_substitution_concern_spec.rb b/spec/models/concern/tags_substitution_concern_spec.rb new file mode 100644 index 000000000..031f261cc --- /dev/null +++ b/spec/models/concern/tags_substitution_concern_spec.rb @@ -0,0 +1,197 @@ +describe TagsSubstitutionConcern, type: :model do + let(:types_de_champ) { [] } + let(:types_de_champ_private) { [] } + let(:for_individual) { false } + + let(:procedure) do + create(:procedure, + types_de_champ: types_de_champ, + types_de_champ_private: types_de_champ_private, + for_individual: for_individual) + end + + let(:template_concern) do + (Class.new do + include TagsSubstitutionConcern + public :replace_tags + + def initialize(p) + @procedure = p + end + + def procedure + @procedure + end + end).new(procedure) + end + + describe 'replace_tags' do + let(:individual) { nil } + let(:etablissement) { nil } + let(:entreprise) { create(:entreprise, etablissement: etablissement) } + let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, entreprise: entreprise) } + + before { Timecop.freeze(Time.now) } + + subject { template_concern.replace_tags(template, dossier) } + + after { Timecop.return } + + context 'when the dossier and the procedure has an individual' do + let(:for_individual) { true } + let(:individual) { Individual.create(nom: 'nom', prenom: 'prenom', gender: 'Mme') } + + context 'and the template use the individual tags' do + let(:template) { '--civilité-- --nom-- --prénom--' } + + it { is_expected.to eq('Mme nom prenom') } + end + end + + context 'when the dossier and the procedure has an entreprise' do + let(:for_individual) { false } + + context 'and the template use the entreprise tags' do + let(:template) do + '--SIREN-- --numéro de TVA intracommunautaire-- --SIRET du siège social-- --raison sociale-- --adresse--' + end + + let(:expected_text) do + "#{entreprise.siren} #{entreprise.numero_tva_intracommunautaire} #{entreprise.siret_siege_social} #{entreprise.raison_sociale} --adresse--" + end + + it { is_expected.to eq(expected_text) } + + context 'and the entreprise has a etablissement with an adresse' do + let(:etablissement) { create(:etablissement, adresse: 'adresse') } + let(:template) { '--adresse--' } + + it { is_expected.to eq(etablissement.inline_adresse) } + end + end + end + + context 'when the procedure has a type de champ named libelleA et libelleB' do + let(:types_de_champ) do + [create(:type_de_champ_public, libelle: 'libelleA'), + create(:type_de_champ_public, libelle: 'libelleB')] + end + + context 'and the template is nil' do + let(:template) { nil } + + it { is_expected.to eq('') } + end + + context 'and it is not used in the template' do + let(:template) { '' } + it { is_expected.to eq('') } + end + + context 'and they are used in the template' do + let(:template) { '--libelleA-- --libelleB--' } + + context 'and their value in the dossier are nil' do + it { is_expected.to eq(' ') } + end + + context 'and their value in the dossier are not nil' do + before do + dossier.champs + .select { |champ| champ.libelle == 'libelleA' } + .first + .update_attributes(value: 'libelle1') + + dossier.champs + .select { |champ| champ.libelle == 'libelleB' } + .first + .update_attributes(value: 'libelle2') + end + + it { is_expected.to eq('libelle1 libelle2') } + end + end + end + + context 'when the dossier has a motivation' do + let(:dossier) { create(:dossier, motivation: 'motivation') } + + context 'and the template has some dossier tags' do + let(:template) { '--motivation-- --numéro du dossier--' } + + it { is_expected.to eq("motivation #{dossier.id}") } + end + end + + context 'when the procedure has a type de champ prive named libelleA' do + let(:types_de_champ_private) { [create(:type_de_champ_private, libelle: 'libelleA')] } + + context 'and the are used in the template' do + let(:template) { '--libelleA--' } + + context 'and its value in the dossier are not nil' do + before { dossier.champs_private.first.update_attributes(value: 'libelle1') } + + it { is_expected.to eq('libelle1') } + end + end + end + + context 'when the procedure has 2 types de champ date and datetime' do + let(:types_de_champ) do + [create(:type_de_champ_public, libelle: 'date', type_champ: 'date'), + create(:type_de_champ_public, libelle: 'datetime', type_champ: 'datetime')] + end + + context 'and the are used in the template' do + let(:template) { '--date-- --datetime--' } + + context 'and its value in the dossier are not nil' do + before do + dossier.champs + .select { |champ| champ.type_champ == 'date' } + .first + .update_attributes(value: '2017-04-15') + + dossier.champs + .select { |champ| champ.type_champ == 'datetime' } + .first + .update_attributes(value: '13/09/2017 09:00') + end + + it { is_expected.to eq('15/04/2017 13/09/2017 09:00') } + end + end + end + + context "match breaking and non breaking spaces" do + before { dossier.champs.first.update_attributes(value: 'valeur') } + + shared_examples "treat all kinds of space as equivalent" do + context 'and the champ has a non breaking space' do + let(:types_de_champ) { [create(:type_de_champ_public, libelle: 'mon tag')] } + + it { is_expected.to eq('valeur') } + end + + context 'and the champ has an ordinary space' do + let(:types_de_champ) { [create(:type_de_champ_public, libelle: 'mon tag')] } + + it { is_expected.to eq('valeur') } + end + end + + context "when the tag has a non breaking space" do + let(:template) { '--mon tag--' } + + it_behaves_like "treat all kinds of space as equivalent" + end + + context "when the tag has an ordinary space" do + let(:template) { '--mon tag--' } + + it_behaves_like "treat all kinds of space as equivalent" + end + end + end +end