From 273b3f2faf5e91289c51ace170ad515c4d85e035 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 18 Jan 2018 11:03:31 +0100 Subject: [PATCH 01/16] Remove `data_provide` and `data_date_format` attributes --- Gemfile | 2 -- Gemfile.lock | 3 --- app/assets/javascripts/application.js | 2 -- app/assets/stylesheets/application.scss | 1 - app/assets/stylesheets/description.scss | 1 - app/decorators/champ_decorator.rb | 10 +++++++++ app/models/champ.rb | 9 -------- .../users/description/champs/_date.html.haml | 2 +- .../description/champs/_datetime.html.haml | 13 ++++------- spec/models/champ_shared_example.rb | 22 ------------------- .../users/description/show.html.haml_spec.rb | 2 +- 11 files changed, 16 insertions(+), 51 deletions(-) diff --git a/Gemfile b/Gemfile index 591ce5f76..8666492d0 100644 --- a/Gemfile +++ b/Gemfile @@ -64,8 +64,6 @@ gem 'leaflet-rails' gem 'leaflet-markercluster-rails', '~> 0.7.0' gem 'leaflet-draw-rails' -gem 'bootstrap-datepicker-rails' - gem 'chartkick' gem 'logstasher' diff --git a/Gemfile.lock b/Gemfile.lock index df20eaa38..0b10d1a88 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,8 +92,6 @@ GEM rubyzip (~> 1.0.0) bcrypt (3.1.11) bindata (2.3.4) - bootstrap-datepicker-rails (1.6.4.1) - railties (>= 3.0) bootstrap-sass (3.3.7) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) @@ -731,7 +729,6 @@ DEPENDENCIES active_model_serializers administrate apipie-rails - bootstrap-datepicker-rails bootstrap-sass (~> 3.3.5) bootstrap-wysihtml5-rails (~> 0.3.3.8) brakeman diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index d5ce61307..62f9b7d15 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -17,8 +17,6 @@ //= require chartkick //= require_tree ./old_design //= require bootstrap-sprockets -//= require bootstrap-datepicker/core -//= require bootstrap-datepicker/locales/bootstrap-datepicker.fr.js //= require leaflet.js //= require d3.min diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 99cf3f12a..ed0ae2bf5 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -46,7 +46,6 @@ // = require attestation_recapitulatif // = require_self -// = require bootstrap-datepicker3 // = require leaflet // = require font-awesome // = require franceconnect diff --git a/app/assets/stylesheets/description.scss b/app/assets/stylesheets/description.scss index 3efa4d01b..3c97d8476 100644 --- a/app/assets/stylesheets/description.scss +++ b/app/assets/stylesheets/description.scss @@ -1,5 +1,4 @@ @import "bootstrap"; -@import "bootstrap-datepicker3"; #description-page #liste-champs { diff --git a/app/decorators/champ_decorator.rb b/app/decorators/champ_decorator.rb index 4652d5d08..ee6e524e3 100644 --- a/app/decorators/champ_decorator.rb +++ b/app/decorators/champ_decorator.rb @@ -19,6 +19,16 @@ class ChampDecorator < Draper::Decorator end end + def date_for_input + if object.value.present? + if type_champ == "date" + object.value + elsif type_champ == "datetime" && object.value != ' 00:00' + DateTime.parse(object.value, "%d/%m/%Y %H:%M").strftime("%d/%m/%Y") + end + end + end + def description_with_links description.gsub(URI.regexp, '\0') if description end diff --git a/app/models/champ.rb b/app/models/champ.rb index ca6a59c8c..abba4569b 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -17,15 +17,6 @@ class Champ < ActiveRecord::Base mandatory end - def data_provide - return 'datepicker' if (type_champ == 'datetime') && !(BROWSER.value.chrome? || BROWSER.value.edge?) - return 'typeahead' if type_champ == 'address' - end - - def data_date_format - ('dd/mm/yyyy' if type_champ == 'datetime') - end - def same_hour? num same_date? num, '%H' end diff --git a/app/views/users/description/champs/_date.html.haml b/app/views/users/description/champs/_date.html.haml index 74088b67c..7e0d94563 100644 --- a/app/views/users/description/champs/_date.html.haml +++ b/app/views/users/description/champs/_date.html.haml @@ -1,5 +1,5 @@ %input.form-control{ name: "champs['#{champ.id}']", placeholder: "JJ/MM/AAAA", id: "champs_#{champ.id}", - value: champ.value ? champ.object.value : champ.value, + value: champ.date_for_input, type: "date" } diff --git a/app/views/users/description/champs/_datetime.html.haml b/app/views/users/description/champs/_datetime.html.haml index 061ed821d..298d66860 100644 --- a/app/views/users/description/champs/_datetime.html.haml +++ b/app/views/users/description/champs/_datetime.html.haml @@ -1,18 +1,13 @@ -%input.form-control{ name: "champs['#{champ.id}']", - placeholder: champ.libelle, - id: "champs_#{champ.id}", - value: (champ.value.split(/[ ][0-9]*:[0-9]*/).first if champ.value.present?), - type: champ.type_champ, - 'data-provide' => champ.data_provide, - 'data-date-format' => champ.data_date_format } += render partial: 'users/description/champs/date', locals: { champ: champ } -%select.form-control{ name: "time_hour['#{champ.id}']", style: 'margin-left: 5px;', id: "time_hour_#{champ.id}" } +%br +%select.form-control{ name: "time_hour['#{champ.id}']", style: 'width:50px;display:inline;', id: "time_hour_#{champ.id}" } - (0..23).each do |num| - num = "%.2i" %num %option{ value: num, selected: (:selected if champ.same_hour?(num)) } = num h -%select.form-control{ name: "time_minute['#{champ.id}']", id: "time_minute_#{champ.id}" } +%select.form-control{ name: "time_minute['#{champ.id}']", style: 'width:50px;display:inline;', id: "time_minute_#{champ.id}" } - (0..59).each do |num| - num = "%.2i" %num - if num.to_i%5 == 0 diff --git a/spec/models/champ_shared_example.rb b/spec/models/champ_shared_example.rb index c8bb4360e..60dc38e9a 100644 --- a/spec/models/champ_shared_example.rb +++ b/spec/models/champ_shared_example.rb @@ -26,28 +26,6 @@ shared_examples 'champ_spec' do end end - describe 'data_provide' do - let(:champ) { create :champ } - - subject { champ.data_provide } - - context 'when type_champ is datetime' do - before do - champ.type_de_champ = create :type_de_champ_public, type_champ: 'datetime' - end - - it { is_expected.to eq 'datepicker' } - end - - context 'when type_champ is address' do - before do - champ.type_de_champ = create :type_de_champ_public, type_champ: 'address' - end - - it { is_expected.to eq 'typeahead' } - end - end - describe '.departement', vcr: { cassette_name: 'call_geo_api_departements' } do subject { Champ.departements } diff --git a/spec/views/users/description/show.html.haml_spec.rb b/spec/views/users/description/show.html.haml_spec.rb index 4f7bf3d62..c4d78f860 100644 --- a/spec/views/users/description/show.html.haml_spec.rb +++ b/spec/views/users/description/show.html.haml_spec.rb @@ -82,7 +82,7 @@ describe 'users/description/show.html.haml', type: :view do end describe 'datetime value is correctly setup when is not nil' do - it { expect(rendered).to have_css("input[type='datetime'][id='champs_#{champ_datetime.id}'][value='22/06/2016']") } + it { expect(rendered).to have_css("input[type='date'][id='champs_#{champ_datetime.id}'][value='22/06/2016']") } it { expect(rendered).to have_css("option[value='12'][selected='selected']") } it { expect(rendered).to have_css("option[value='05'][selected='selected']") } end From e42626c58848314cac52fcb6760173c9fb18cb85 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 17 Jan 2018 14:40:31 +0100 Subject: [PATCH 02/16] Add user info to LogStasher --- app/controllers/application_controller.rb | 48 +++++++++++++++---- config/initializers/logstasher.rb | 14 ++++++ .../application_controller_spec.rb | 34 +++++++++++-- 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5a534f4ce..b8a02d2a5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -51,22 +51,54 @@ class ApplicationController < ActionController::Base private - def set_raven_context - context = { ip_address: request.ip } - - logged_models = [ + def logged_users + @logged_users ||= [ current_user, current_gestionnaire, current_administrateur, current_administration ].compact + end - context[:email] = logged_models.first&.email - context[:id] = logged_models.first&.id + def logged_user_roles + roles = logged_users.map { |logged_user| logged_user.class.name } + roles.any? ? roles.join(', ') : 'Guest' + end - class_names = logged_models.map { |model| model.class.name } - context[:classes] = class_names.any? ? class_names.join(', ') : 'Guest' + def logged_user_info + logged_user = logged_users.first + + if logged_user + { + id: logged_user.id, + email: logged_user.email + } + end + end + + def set_raven_context + context = { + ip_address: request.ip, + roles: logged_user_roles + } + context.merge!(logged_user_info || {}) Raven.user_context(context) end + + def append_info_to_payload(payload) + payload.merge!({ + user_agent: request.user_agent, + current_user: logged_user_info, + current_user_roles: logged_user_roles + }.compact) + + if browser.known? + payload.merge!({ + browser: browser.name, + browser_version: browser.version.to_s, + platform: browser.platform.name, + }) + end + end end diff --git a/config/initializers/logstasher.rb b/config/initializers/logstasher.rb index 7d1be9caf..d85bb8d6a 100644 --- a/config/initializers/logstasher.rb +++ b/config/initializers/logstasher.rb @@ -2,4 +2,18 @@ if LogStasher.enabled LogStasher.add_custom_fields do |fields| fields[:type] = "tps" end + + LogStasher.watch('process_action.action_controller') do |name, start, finish, id, payload, store| + store[:user_agent] = payload[:user_agent] + store[:browser] = payload[:browser] + store[:browser_version] = payload[:browser_version] + store[:platform] = payload[:platform] + + store[:current_user_roles] = payload[:current_user_roles] + + if payload[:current_user].present? + store[:current_user_id] = payload[:current_user][:id] + store[:current_user_email] = payload[:current_user][:email] + end + end end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 21f6dfd56..89d601eaa 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -12,11 +12,12 @@ describe ApplicationController, type: :controller do end end - describe 'set_raven_context' do + describe 'set_raven_context and append_info_to_payload' do let(:current_user) { nil } let(:current_gestionnaire) { nil } let(:current_administrateur) { nil } let(:current_administration) { nil } + let(:payload) { {} } before do expect(@controller).to receive(:current_user).and_return(current_user) @@ -26,13 +27,16 @@ describe ApplicationController, type: :controller do allow(Raven).to receive(:user_context) @controller.send(:set_raven_context) + @controller.send(:append_info_to_payload, payload) end context 'when no one is logged in' do it do expect(Raven).to have_received(:user_context) - .with({ ip_address: '0.0.0.0', email: nil, id: nil, classes: 'Guest' }) + .with({ ip_address: '0.0.0.0', roles: 'Guest' }) end + + it { expect(payload).to eq({ user_agent: 'Rails Testing', current_user_roles: 'Guest' }) } end context 'when a user is logged in' do @@ -40,7 +44,18 @@ describe ApplicationController, type: :controller do it do expect(Raven).to have_received(:user_context) - .with({ ip_address: '0.0.0.0', email: current_user.email, id: current_user.id, classes: 'User' }) + .with({ ip_address: '0.0.0.0', email: current_user.email, id: current_user.id, roles: 'User' }) + end + + it do + expect(payload).to eq({ + user_agent: 'Rails Testing', + current_user: { + id: current_user.id, + email: current_user.email + }, + current_user_roles: 'User' + }) end end @@ -52,7 +67,18 @@ describe ApplicationController, type: :controller do it do expect(Raven).to have_received(:user_context) - .with({ ip_address: '0.0.0.0', email: current_user.email, id: current_user.id, classes: 'User, Gestionnaire, Administrateur, Administration' }) + .with({ ip_address: '0.0.0.0', email: current_user.email, id: current_user.id, roles: 'User, Gestionnaire, Administrateur, Administration' }) + end + + it do + expect(payload).to eq({ + user_agent: 'Rails Testing', + current_user: { + id: current_user.id, + email: current_user.email + }, + current_user_roles: 'User, Gestionnaire, Administrateur, Administration' + }) end end end From d945001e0a797187761b9a2d08e90a935470b01d Mon Sep 17 00:00:00 2001 From: Mathieu Magnin Date: Thu, 18 Jan 2018 17:24:21 +0100 Subject: [PATCH 03/16] [Fix #1302] sanitize_url can deal with nil values --- app/helpers/application_helper.rb | 4 +++- spec/helpers/application_helper_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 spec/helpers/application_helper_spec.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fa1bb5063..cfe77e7d8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -2,7 +2,9 @@ module ApplicationHelper include SanitizeUrl def sanitize_url(url) - super(url, schemes: ['http', 'https'], replace_evil_with: root_url) + if !url.nil? + super(url, schemes: ['http', 'https'], replace_evil_with: root_url) + end end def flash_class(level) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb new file mode 100644 index 000000000..30a7becef --- /dev/null +++ b/spec/helpers/application_helper_spec.rb @@ -0,0 +1,20 @@ +describe ApplicationHelper do + describe "#sanitize_url" do + subject { sanitize_url(url) } + + describe 'does nothing on clean url' do + let(:url) { "https://tps.fr/toto" } + it { is_expected.to eq(url) } + end + + describe 'clean a dangerous url' do + let(:url) { "javascript:alert('coucou jtai hacké')" } + it { is_expected.to eq(root_url) } + end + + describe 'can deal with a nil url' do + let(:url) { nil } + it { is_expected.to be_nil } + end + end +end From 5723c4c894beb462d39a4e83924884c39bc90711 Mon Sep 17 00:00:00 2001 From: gregoirenovel Date: Thu, 18 Jan 2018 14:03:18 +0100 Subject: [PATCH 04/16] Remove any reference to the SGMAP --- app/views/layouts/_footer.html.haml | 2 +- config/initializers/apipie.rb | 1 - doc/apipie_examples.json | 6 +++--- spec/factories/procedure.rb | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/views/layouts/_footer.html.haml b/app/views/layouts/_footer.html.haml index 8834f8fcc..71379539c 100644 --- a/app/views/layouts/_footer.html.haml +++ b/app/views/layouts/_footer.html.haml @@ -1,6 +1,6 @@ #footer %p{ class: "copyright col-md-push-#{12-main_container_size} col-md-#{main_container_size} col-lg-push-#{12-main_container_size} col-lg-#{main_container_size} text-muted small" } - = link_to 'SGMAP', "http://etatplateforme.modernisation.gouv.fr" + = link_to 'DINSIC', "http://etatplateforme.modernisation.gouv.fr" = Time.now.year \- = link_to 'Nouveautés', 'https://github.com/betagouv/tps/releases', target: '_blank' diff --git a/config/initializers/apipie.rb b/config/initializers/apipie.rb index 362020851..4609310f2 100644 --- a/config/initializers/apipie.rb +++ b/config/initializers/apipie.rb @@ -6,7 +6,6 @@ Apipie.configure do |config| config.markup = Apipie::Markup::Markdown.new config.default_version = '1.0' config.validate = false - config.copyright = "© SGMAP" config.namespaced_resources = true config.show_all_examples = true diff --git a/doc/apipie_examples.json b/doc/apipie_examples.json index 229529ce4..e62a7031f 100644 --- a/doc/apipie_examples.json +++ b/doc/apipie_examples.json @@ -101,7 +101,7 @@ "order_place": 1, "description": "description de votre projet" } - }, + }, { "value": { "type": "MultiPolygon", @@ -257,8 +257,8 @@ "link": "http://localhost", "id": 3, "description": "Demande de subvention à l'intention des associations", - "organisation": "Orga SGMAP", - "direction": "direction SGMAP", + "organisation": "Orga DINSIC", + "direction": "direction DINSIC", "archived_at": null, "geographic_information": { "use_api_carto": true, diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb index 7b8172458..46477a14b 100644 --- a/spec/factories/procedure.rb +++ b/spec/factories/procedure.rb @@ -4,8 +4,8 @@ FactoryGirl.define do lien_demarche 'http://localhost' sequence(:libelle) { |n| "Procedure #{n}" } description "Demande de subvention à l'intention des associations" - organisation "Orga SGMAP" - direction "direction SGMAP" + organisation "Orga DINSIC" + direction "direction DINSIC" published_at nil cerfa_flag false administrateur { create(:administrateur) } From 26b1de5883a0ad9c3ec276e2893274dc6ab2f999 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 16 Jan 2018 16:31:47 +0100 Subject: [PATCH 05/16] [Closes #1170] Put tps-dev (staging) environement behind a BasicAuth --- app/controllers/application_controller.rb | 8 ++++++++ app/services/staging_auth_service.rb | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 app/services/staging_auth_service.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b8a02d2a5..559acd6df 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,6 +7,14 @@ class ApplicationController < ActionController::Base before_action :set_raven_context before_action :authorize_request_for_profiler + before_action :staging_authenticate + + def staging_authenticate + if StagingAuthService.enabled? && !authenticate_with_http_basic { |username, password| StagingAuthService.authenticate(username, password) } + request_http_basic_authentication + end + end + def authorize_request_for_profiler if administration_signed_in? Rack::MiniProfiler.authorize_request diff --git a/app/services/staging_auth_service.rb b/app/services/staging_auth_service.rb new file mode 100644 index 000000000..79d94310c --- /dev/null +++ b/app/services/staging_auth_service.rb @@ -0,0 +1,23 @@ +class StagingAuthService + CONFIG_PATH = Rails.root.join("/config/basic_auth.yml") + + def self.authenticate(username, password) + if enabled? + username == config[:username] && password == config[:password] + else + true + end + end + + def self.enabled? + !!config[:enabled] + end + + def self.config + if File.exists?(CONFIG_PATH) + YAML.safe_load(File.read(CONFIG_PATH)).symbolize_keys + else + {} + end + end +end From 1282fc21131a5a44551a337e3dc9a67856761676 Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Tue, 9 Jan 2018 18:08:05 +0100 Subject: [PATCH 06/16] [#1203] Use factory girl to instanciate model in spec --- spec/models/concern/mail_template_concern_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/concern/mail_template_concern_spec.rb b/spec/models/concern/mail_template_concern_spec.rb index 13a2cbf34..fc4ba473c 100644 --- a/spec/models/concern/mail_template_concern_spec.rb +++ b/spec/models/concern/mail_template_concern_spec.rb @@ -4,7 +4,7 @@ describe MailTemplateConcern do let(:procedure) { create(:procedure) } let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier2) { create(:dossier, procedure: procedure) } - let(:initiated_mail) { Mails::InitiatedMail.default_for_procedure(procedure) } + let(:initiated_mail) { create(:initiated_mail, procedure: procedure) } shared_examples "can replace tokens in template" do describe 'with no token to replace' do From 9f7ffddb31c8146929552ee55d09f333d6b8378a Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Wed, 10 Jan 2018 15:03:56 +0100 Subject: [PATCH 07/16] [#1203] Allow filtering tags based on an arbitray status --- app/models/attestation_template.rb | 1 + app/models/concerns/mail_template_concern.rb | 4 --- .../concerns/tags_substitution_concern.rb | 30 ++++++++++++------- app/models/mails/closed_mail.rb | 2 +- app/models/mails/initiated_mail.rb | 2 +- app/models/mails/received_mail.rb | 2 +- app/models/mails/refused_mail.rb | 2 +- app/models/mails/without_continuation_mail.rb | 2 +- .../concern/tags_substitution_concern_spec.rb | 30 +++++++++---------- 9 files changed, 40 insertions(+), 35 deletions(-) diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb index a391e9272..446b2096e 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -11,6 +11,7 @@ class AttestationTemplate < ApplicationRecord validates :footer, length: { maximum: 190 } FILE_MAX_SIZE_IN_MB = 0.5 + DOSSIER_STATE = 'accepte' def attestation_for(dossier) Attestation.new(title: replace_tags(title, dossier), pdf: build_pdf(dossier)) diff --git a/app/models/concerns/mail_template_concern.rb b/app/models/concerns/mail_template_concern.rb index 17fdd5246..1e4f9ad98 100644 --- a/app/models/concerns/mail_template_concern.rb +++ b/app/models/concerns/mail_template_concern.rb @@ -13,10 +13,6 @@ module MailTemplateConcern replace_tags(body, dossier) end - def tags(is_dossier_termine: self.class.const_get(:IS_DOSSIER_TERMINE)) - super - end - module ClassMethods def default_for_procedure(procedure) body = ActionController::Base.new.render_to_string(template: self.const_get(:TEMPLATE_NAME)) diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index 6dcfc25ce..bfc6b6539 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -1,25 +1,33 @@ module TagsSubstitutionConcern extend ActiveSupport::Concern - def tags(is_dossier_termine: true) + def tags if procedure.for_individual? identity_tags = individual_tags else identity_tags = entreprise_tags + etablissement_tags end - tags = identity_tags + dossier_tags + procedure_type_de_champ_public_private_tags - filter_tags(tags, is_dossier_termine) + filter_tags(identity_tags + dossier_tags + procedure_type_de_champ_public_private_tags) end private - def filter_tags(tags, is_dossier_termine) - if !is_dossier_termine - tags.reject { |tag| tag[:dossier_termine_only] } - else - tags + def filter_tags(tags) + # Implementation note: emails and attestation generations are generally + # triggerred by changes to the dossier’s state. The email or attestation + # is generated right after the dossier has reached its new state. + # + # DOSSIER_STATE should be equal to this new state. + # + # For instance, for an email that gets generated for the brouillon->en_construction + # transition, DOSSIER_STATE should equal 'en_construction'. + + if !defined?(self.class::DOSSIER_STATE) + raise NameError.new("The class #{self.class.name} includes TagsSubstitutionConcern, it should define the DOSSIER_STATE constant but it does not", :DOSSIER_STATE) end + + tags.select { |tag| (tag[:available_for_states] || Dossier::SOUMIS).include?(self.class::DOSSIER_STATE) } end def procedure_type_de_champ_public_private_tags @@ -33,7 +41,7 @@ module TagsSubstitutionConcern libelle: 'motivation', description: 'Motivation facultative associée à la décision finale d’acceptation, refus ou classement sans suite', target: :motivation, - dossier_termine_only: true + available_for_states: Dossier::TERMINE }, { libelle: 'date de dépôt', @@ -49,7 +57,7 @@ module TagsSubstitutionConcern libelle: 'date de décision', description: 'Date de la décision d’acceptation, refus, ou classement sans suite', lambda: -> (d) { format_date(d.processed_at) }, - dossier_termine_only: true + available_for_states: Dossier::TERMINE }, { libelle: 'libellé procédure', description: '', lambda: -> (d) { d.procedure.libelle } }, { libelle: 'numéro du dossier', description: '', target: :id } @@ -101,7 +109,7 @@ module TagsSubstitutionConcern ] tags_and_datas - .map { |(tags, data)| [filter_tags(tags, dossier.termine?), data] } + .map { |(tags, data)| [filter_tags(tags), data] } .inject(text) { |acc, (tags, data)| replace_tags_with_values_from_data(acc, tags, data) } end diff --git a/app/models/mails/closed_mail.rb b/app/models/mails/closed_mail.rb index cf7231836..97f0f1985 100644 --- a/app/models/mails/closed_mail.rb +++ b/app/models/mails/closed_mail.rb @@ -8,6 +8,6 @@ module Mails TEMPLATE_NAME = "mails/closed_mail" DISPLAYED_NAME = "Accusé d'acceptation" DEFAULT_SUBJECT = 'Votre dossier TPS nº --numéro du dossier-- a été accepté' - IS_DOSSIER_TERMINE = true + DOSSIER_STATE = 'accepte' end end diff --git a/app/models/mails/initiated_mail.rb b/app/models/mails/initiated_mail.rb index c9ca384ac..d4a2cbe0f 100644 --- a/app/models/mails/initiated_mail.rb +++ b/app/models/mails/initiated_mail.rb @@ -8,6 +8,6 @@ module Mails TEMPLATE_NAME = "mails/initiated_mail" DISPLAYED_NAME = 'Accusé de réception' DEFAULT_SUBJECT = 'Votre dossier TPS nº --numéro du dossier-- a bien été reçu' - IS_DOSSIER_TERMINE = false + DOSSIER_STATE = 'en_construction' end end diff --git a/app/models/mails/received_mail.rb b/app/models/mails/received_mail.rb index 30f58de9a..ef903b388 100644 --- a/app/models/mails/received_mail.rb +++ b/app/models/mails/received_mail.rb @@ -8,6 +8,6 @@ module Mails TEMPLATE_NAME = "mails/received_mail" DISPLAYED_NAME = 'Accusé de passage en instruction' DEFAULT_SUBJECT = 'Votre dossier TPS nº --numéro du dossier-- va être instruit' - IS_DOSSIER_TERMINE = false + DOSSIER_STATE = 'en_instruction' end end diff --git a/app/models/mails/refused_mail.rb b/app/models/mails/refused_mail.rb index 4e0d818c6..a133e6534 100644 --- a/app/models/mails/refused_mail.rb +++ b/app/models/mails/refused_mail.rb @@ -8,6 +8,6 @@ module Mails TEMPLATE_NAME = "mails/refused_mail" DISPLAYED_NAME = 'Accusé de rejet du dossier' DEFAULT_SUBJECT = 'Votre dossier TPS nº --numéro du dossier-- a été refusé' - IS_DOSSIER_TERMINE = true + DOSSIER_STATE = 'refuse' end end diff --git a/app/models/mails/without_continuation_mail.rb b/app/models/mails/without_continuation_mail.rb index 2c1e2f8b2..142db0982 100644 --- a/app/models/mails/without_continuation_mail.rb +++ b/app/models/mails/without_continuation_mail.rb @@ -8,6 +8,6 @@ module Mails TEMPLATE_NAME = "mails/without_continuation_mail" DISPLAYED_NAME = 'Accusé de classement sans suite' DEFAULT_SUBJECT = 'Votre dossier TPS nº --numéro du dossier-- a été classé sans suite' - IS_DOSSIER_TERMINE = true + DOSSIER_STATE = 'sans_suite' end end diff --git a/spec/models/concern/tags_substitution_concern_spec.rb b/spec/models/concern/tags_substitution_concern_spec.rb index 911fa29de..a05b30590 100644 --- a/spec/models/concern/tags_substitution_concern_spec.rb +++ b/spec/models/concern/tags_substitution_concern_spec.rb @@ -2,6 +2,7 @@ describe TagsSubstitutionConcern, type: :model do let(:types_de_champ) { [] } let(:types_de_champ_private) { [] } let(:for_individual) { false } + let(:state) { 'accepte' } let(:procedure) do create(:procedure, @@ -13,17 +14,18 @@ describe TagsSubstitutionConcern, type: :model do let(:template_concern) do (Class.new do - include TagsSubstitutionConcern - public :replace_tags + include TagsSubstitutionConcern + public :replace_tags - def initialize(p) - @procedure = p - end + def initialize(p, s) + @procedure = p + self.class.const_set(:DOSSIER_STATE, s) + end - def procedure - @procedure - end - end).new(procedure) + def procedure + @procedure + end + end).new(procedure, state) end describe 'replace_tags' do @@ -119,8 +121,6 @@ describe TagsSubstitutionConcern, type: :model do context 'when the dossier has a motivation' do let(:dossier) { create(:dossier, motivation: 'motivation') } - before { dossier.accepte! } - context 'and the template has some dossier tags' do let(:template) { '--motivation-- --numéro du dossier--' } @@ -173,7 +173,6 @@ describe TagsSubstitutionConcern, type: :model do context "when using a date tag" do before do - dossier.accepte! dossier.en_construction_at = DateTime.new(2001, 2, 3) dossier.en_instruction_at = DateTime.new(2004, 5, 6) dossier.processed_at = DateTime.new(2007, 8, 9) @@ -237,6 +236,7 @@ describe TagsSubstitutionConcern, type: :model do context 'when generating a document for a dossier that is not termine' do let(:dossier) { create(:dossier) } let(:template) { '--motivation-- --date de décision--' } + let(:state) { 'en_instruction' } subject { template_concern.replace_tags(template, dossier) } @@ -247,15 +247,15 @@ describe TagsSubstitutionConcern, type: :model do end describe 'tags' do - context 'when generating a document for a dossier terminé' do - subject { template_concern.tags } + subject { template_concern.tags } + context 'when generating a document for a dossier terminé' do it { is_expected.to include(include({ libelle: 'motivation' })) } it { is_expected.to include(include({ libelle: 'date de décision' })) } end context 'when generating a document for a dossier that is not terminé' do - subject { template_concern.tags(is_dossier_termine: false) } + let(:state) { 'en_instruction' } it { is_expected.not_to include(include({ libelle: 'motivation' })) } it { is_expected.not_to include(include({ libelle: 'date de décision' })) } From eebab149542f7d8b3ba806aa487e5502c5b54bfe Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Thu, 11 Jan 2018 11:28:41 +0100 Subject: [PATCH 08/16] =?UTF-8?q?[Fix=20#1203]=20No=20d=C3=A9but=20d?= =?UTF-8?q?=E2=80=99instruction=20tag=20in=20accus=C3=A9=20de=20r=C3=A9cep?= =?UTF-8?q?tion=20mails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doesn’t make sense functionnally --- app/models/concerns/tags_substitution_concern.rb | 3 ++- app/models/dossier.rb | 1 + .../models/concern/mail_template_concern_spec.rb | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index bfc6b6539..929175c3d 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -51,7 +51,8 @@ module TagsSubstitutionConcern { libelle: 'date de passage en instruction', description: '', - lambda: -> (d) { format_date(d.en_instruction_at) } + lambda: -> (d) { format_date(d.en_instruction_at) }, + available_for_states: Dossier::INSTRUCTION_COMMENCEE }, { libelle: 'date de décision', diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 25b022407..a0451e769 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -10,6 +10,7 @@ class Dossier < ActiveRecord::Base EN_CONSTRUCTION_OU_INSTRUCTION = %w(en_construction en_instruction) TERMINE = %w(accepte refuse sans_suite) + INSTRUCTION_COMMENCEE = TERMINE + %w(en_instruction) has_one :etablissement, dependent: :destroy has_one :entreprise, dependent: :destroy diff --git a/spec/models/concern/mail_template_concern_spec.rb b/spec/models/concern/mail_template_concern_spec.rb index fc4ba473c..136f58b8c 100644 --- a/spec/models/concern/mail_template_concern_spec.rb +++ b/spec/models/concern/mail_template_concern_spec.rb @@ -47,6 +47,22 @@ describe MailTemplateConcern do it_behaves_like "can replace tokens in template" end + describe 'tags' do + describe 'in initiated mail' do + it "does not treat date de passage en instruction as a tag" do + expect(initiated_mail.tags).not_to include(include({ libelle: 'date de passage en instruction' })) + end + end + + describe 'in received mail' do + let(:received_mail) { create(:received_mail, procedure: procedure) } + + it "treats date de passage en instruction as a tag" do + expect(received_mail.tags).to include(include({ libelle: 'date de passage en instruction' })) + end + end + end + describe '.replace_tags' do before { initiated_mail.body = "n --numéro du dossier--" } it "avoids side effects" do From 943fef3160e98527c48ec5bb3dd55e11f5a8976b Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Thu, 18 Jan 2018 11:28:31 +0100 Subject: [PATCH 09/16] [#1203] Move all tag definitions to mail template concern Even the one that is mail-specific, because it is too easy to overlook it when refactoring otherwise --- app/models/concerns/mail_template_concern.rb | 11 +---------- app/models/concerns/tags_substitution_concern.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/models/concerns/mail_template_concern.rb b/app/models/concerns/mail_template_concern.rb index 1e4f9ad98..39e45487b 100644 --- a/app/models/concerns/mail_template_concern.rb +++ b/app/models/concerns/mail_template_concern.rb @@ -1,8 +1,6 @@ module MailTemplateConcern extend ActiveSupport::Concern - include Rails.application.routes.url_helpers - include ActionView::Helpers::UrlHelper include TagsSubstitutionConcern def subject_for_dossier(dossier) @@ -20,14 +18,7 @@ module MailTemplateConcern end end - private - def dossier_tags - super + [{ libelle: 'lien dossier', description: '', lambda: -> (d) { users_dossier_recapitulatif_link(d) } }] - end - - def users_dossier_recapitulatif_link(dossier) - url = users_dossier_recapitulatif_url(dossier) - link_to(url, url, target: '_blank') + super + dossier_tags_for_mail end end diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index 929175c3d..da7bd910d 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -1,6 +1,9 @@ module TagsSubstitutionConcern extend ActiveSupport::Concern + include Rails.application.routes.url_helpers + include ActionView::Helpers::UrlHelper + def tags if procedure.for_individual? identity_tags = individual_tags @@ -73,6 +76,15 @@ module TagsSubstitutionConcern end end + def dossier_tags_for_mail + [{ libelle: 'lien dossier', description: '', lambda: -> (d) { users_dossier_recapitulatif_link(d) } }] + end + + def users_dossier_recapitulatif_link(dossier) + url = users_dossier_recapitulatif_url(dossier) + link_to(url, url, target: '_blank') + end + def individual_tags [ { libelle: 'civilité', description: 'M., Mme', target: :gender }, From 21ad13cf74264e75cd15de9d7137d4ecd6436b5e Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Wed, 17 Jan 2018 13:55:34 +0100 Subject: [PATCH 10/16] [#1203] Make all tags multi-line --- .../concerns/tags_substitution_concern.rb | 70 ++++++++++++++++--- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index da7bd910d..5bd2bdc71 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -63,8 +63,16 @@ module TagsSubstitutionConcern lambda: -> (d) { format_date(d.processed_at) }, available_for_states: Dossier::TERMINE }, - { libelle: 'libellé procédure', description: '', lambda: -> (d) { d.procedure.libelle } }, - { libelle: 'numéro du dossier', description: '', target: :id } + { + libelle: 'libellé procédure', + description: '', + lambda: -> (d) { d.procedure.libelle } + }, + { + libelle: 'numéro du dossier', + description: '', + target: :id + } ] end @@ -77,7 +85,13 @@ module TagsSubstitutionConcern end def dossier_tags_for_mail - [{ libelle: 'lien dossier', description: '', lambda: -> (d) { users_dossier_recapitulatif_link(d) } }] + [ + { + libelle: 'lien dossier', + description: '', + lambda: -> (d) { users_dossier_recapitulatif_link(d) } + } + ] end def users_dossier_recapitulatif_link(dossier) @@ -87,23 +101,57 @@ module TagsSubstitutionConcern 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 } + { + 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 } + { + 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 }] + [ + { + libelle: 'adresse', + description: '', + target: :inline_adresse + } + ] end def replace_tags(text, dossier) From db5bf39e32fc82c89dc419f7de045260e57b5f3e Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Thu, 18 Jan 2018 11:39:05 +0100 Subject: [PATCH 11/16] [#1203] No implicit available_for_states, make it explicit --- .../concerns/tags_substitution_concern.rb | 44 ++++++++++++------- app/models/dossier.rb | 1 + 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index 5bd2bdc71..8bfaa4256 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -11,7 +11,7 @@ module TagsSubstitutionConcern identity_tags = entreprise_tags + etablissement_tags end - filter_tags(identity_tags + dossier_tags + procedure_type_de_champ_public_private_tags) + filter_tags(identity_tags + dossier_tags) + procedure_type_de_champ_public_private_tags end private @@ -30,7 +30,7 @@ module TagsSubstitutionConcern raise NameError.new("The class #{self.class.name} includes TagsSubstitutionConcern, it should define the DOSSIER_STATE constant but it does not", :DOSSIER_STATE) end - tags.select { |tag| (tag[:available_for_states] || Dossier::SOUMIS).include?(self.class::DOSSIER_STATE) } + tags.select { |tag| tag[:available_for_states].include?(self.class::DOSSIER_STATE) } end def procedure_type_de_champ_public_private_tags @@ -49,7 +49,8 @@ module TagsSubstitutionConcern { libelle: 'date de dépôt', description: 'Date du passage en construction du dossier par l’usager', - lambda: -> (d) { format_date(d.en_construction_at) } + lambda: -> (d) { format_date(d.en_construction_at) }, + available_for_states: Dossier::SOUMIS }, { libelle: 'date de passage en instruction', @@ -66,12 +67,14 @@ module TagsSubstitutionConcern { libelle: 'libellé procédure', description: '', - lambda: -> (d) { d.procedure.libelle } + lambda: -> (d) { d.procedure.libelle }, + available_for_states: Dossier::SOUMIS }, { libelle: 'numéro du dossier', description: '', - target: :id + target: :id, + available_for_states: Dossier::SOUMIS } ] end @@ -89,7 +92,8 @@ module TagsSubstitutionConcern { libelle: 'lien dossier', description: '', - lambda: -> (d) { users_dossier_recapitulatif_link(d) } + lambda: -> (d) { users_dossier_recapitulatif_link(d) }, + available_for_states: Dossier::SOUMIS } ] end @@ -104,17 +108,20 @@ module TagsSubstitutionConcern { libelle: 'civilité', description: 'M., Mme', - target: :gender + target: :gender, + available_for_states: Dossier::SOUMIS }, { libelle: 'nom', description: "nom de l'usager", - target: :nom + target: :nom, + available_for_states: Dossier::SOUMIS }, { libelle: 'prénom', description: "prénom de l'usager", - target: :prenom + target: :prenom, + available_for_states: Dossier::SOUMIS } ] end @@ -124,22 +131,26 @@ module TagsSubstitutionConcern { libelle: 'SIREN', description: '', - target: :siren + target: :siren, + available_for_states: Dossier::SOUMIS }, { libelle: 'numéro de TVA intracommunautaire', description: '', - target: :numero_tva_intracommunautaire + target: :numero_tva_intracommunautaire, + available_for_states: Dossier::SOUMIS }, { libelle: 'SIRET du siège social', description: '', - target: :siret_siege_social + target: :siret_siege_social, + available_for_states: Dossier::SOUMIS }, { libelle: 'raison sociale', description: '', - target: :raison_sociale + target: :raison_sociale, + available_for_states: Dossier::SOUMIS } ] end @@ -147,9 +158,10 @@ module TagsSubstitutionConcern def etablissement_tags [ { - libelle: 'adresse', - description: '', - target: :inline_adresse + libelle: 'adresse', + description: '', + target: :inline_adresse, + available_for_states: Dossier::SOUMIS } ] end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index a0451e769..5843337d2 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -11,6 +11,7 @@ class Dossier < ActiveRecord::Base EN_CONSTRUCTION_OU_INSTRUCTION = %w(en_construction en_instruction) TERMINE = %w(accepte refuse sans_suite) INSTRUCTION_COMMENCEE = TERMINE + %w(en_instruction) + SOUMIS = EN_CONSTRUCTION_OU_INSTRUCTION + TERMINE has_one :etablissement, dependent: :destroy has_one :entreprise, dependent: :destroy From a21dee680dfb5e8c906843d8f6bb298f507f24d8 Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Thu, 18 Jan 2018 11:52:48 +0100 Subject: [PATCH 12/16] [#1203] Define tags in constants rather than methods --- app/models/concerns/mail_template_concern.rb | 2 +- .../concerns/tags_substitution_concern.rb | 263 +++++++++--------- 2 files changed, 130 insertions(+), 135 deletions(-) diff --git a/app/models/concerns/mail_template_concern.rb b/app/models/concerns/mail_template_concern.rb index 39e45487b..387b515e2 100644 --- a/app/models/concerns/mail_template_concern.rb +++ b/app/models/concerns/mail_template_concern.rb @@ -19,6 +19,6 @@ module MailTemplateConcern end def dossier_tags - super + dossier_tags_for_mail + TagsSubstitutionConcern::DOSSIER_TAGS + TagsSubstitutionConcern::DOSSIER_TAGS_FOR_MAIL end end diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index 8bfaa4256..163bf3f04 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -4,11 +4,116 @@ module TagsSubstitutionConcern include Rails.application.routes.url_helpers include ActionView::Helpers::UrlHelper + DOSSIER_TAGS = [ + { + libelle: 'motivation', + description: 'Motivation facultative associée à la décision finale d’acceptation, refus ou classement sans suite', + target: :motivation, + available_for_states: Dossier::TERMINE + }, + { + libelle: 'date de dépôt', + description: 'Date du passage en construction du dossier par l’usager', + lambda: -> (d) { format_date(d.en_construction_at) }, + available_for_states: Dossier::SOUMIS + }, + { + libelle: 'date de passage en instruction', + description: '', + lambda: -> (d) { format_date(d.en_instruction_at) }, + available_for_states: Dossier::INSTRUCTION_COMMENCEE + }, + { + libelle: 'date de décision', + description: 'Date de la décision d’acceptation, refus, ou classement sans suite', + lambda: -> (d) { format_date(d.processed_at) }, + available_for_states: Dossier::TERMINE + }, + { + libelle: 'libellé procédure', + description: '', + lambda: -> (d) { d.procedure.libelle }, + available_for_states: Dossier::SOUMIS + }, + { + libelle: 'numéro du dossier', + description: '', + target: :id, + available_for_states: Dossier::SOUMIS + } + ] + + DOSSIER_TAGS_FOR_MAIL = [ + { + libelle: 'lien dossier', + description: '', + lambda: -> (d) { users_dossier_recapitulatif_link(d) }, + available_for_states: Dossier::SOUMIS + } + ] + + INDIVIDUAL_TAGS = [ + { + libelle: 'civilité', + description: 'M., Mme', + target: :gender, + available_for_states: Dossier::SOUMIS + }, + { + libelle: 'nom', + description: "nom de l'usager", + target: :nom, + available_for_states: Dossier::SOUMIS + }, + { + libelle: 'prénom', + description: "prénom de l'usager", + target: :prenom, + available_for_states: Dossier::SOUMIS + } + ] + + ENTREPRISE_TAGS = [ + { + libelle: 'SIREN', + description: '', + target: :siren, + available_for_states: Dossier::SOUMIS + }, + { + libelle: 'numéro de TVA intracommunautaire', + description: '', + target: :numero_tva_intracommunautaire, + available_for_states: Dossier::SOUMIS + }, + { + libelle: 'SIRET du siège social', + description: '', + target: :siret_siege_social, + available_for_states: Dossier::SOUMIS + }, + { + libelle: 'raison sociale', + description: '', + target: :raison_sociale, + available_for_states: Dossier::SOUMIS + } + ] + + ETABLISSEMENT_TAGS = [ + { + libelle: 'adresse', + description: '', + target: :inline_adresse, + available_for_states: Dossier::SOUMIS + } + ] + def tags if procedure.for_individual? - identity_tags = individual_tags + identity_tags = INDIVIDUAL_TAGS else - identity_tags = entreprise_tags + etablissement_tags + identity_tags = ENTREPRISE_TAGS + ETABLISSEMENT_TAGS end filter_tags(identity_tags + dossier_tags) + procedure_type_de_champ_public_private_tags @@ -16,6 +121,24 @@ module TagsSubstitutionConcern private + def format_date(date) + if date.present? + date.localtime.strftime('%d/%m/%Y') + else + '' + end + end + + def users_dossier_recapitulatif_link(dossier) + url = users_dossier_recapitulatif_url(dossier) + link_to(url, url, target: '_blank') + end + + def dossier_tags + # Overridden by MailTemplateConcern + DOSSIER_TAGS + end + def filter_tags(tags) # Implementation note: emails and attestation generations are generally # triggerred by changes to the dossier’s state. The email or attestation @@ -38,134 +161,6 @@ module TagsSubstitutionConcern .map { |tdc| { libelle: tdc.libelle, description: tdc.description } } end - def dossier_tags - [ - { - libelle: 'motivation', - description: 'Motivation facultative associée à la décision finale d’acceptation, refus ou classement sans suite', - target: :motivation, - available_for_states: Dossier::TERMINE - }, - { - libelle: 'date de dépôt', - description: 'Date du passage en construction du dossier par l’usager', - lambda: -> (d) { format_date(d.en_construction_at) }, - available_for_states: Dossier::SOUMIS - }, - { - libelle: 'date de passage en instruction', - description: '', - lambda: -> (d) { format_date(d.en_instruction_at) }, - available_for_states: Dossier::INSTRUCTION_COMMENCEE - }, - { - libelle: 'date de décision', - description: 'Date de la décision d’acceptation, refus, ou classement sans suite', - lambda: -> (d) { format_date(d.processed_at) }, - available_for_states: Dossier::TERMINE - }, - { - libelle: 'libellé procédure', - description: '', - lambda: -> (d) { d.procedure.libelle }, - available_for_states: Dossier::SOUMIS - }, - { - libelle: 'numéro du dossier', - description: '', - target: :id, - available_for_states: Dossier::SOUMIS - } - ] - end - - def format_date(date) - if date.present? - date.localtime.strftime('%d/%m/%Y') - else - '' - end - end - - def dossier_tags_for_mail - [ - { - libelle: 'lien dossier', - description: '', - lambda: -> (d) { users_dossier_recapitulatif_link(d) }, - available_for_states: Dossier::SOUMIS - } - ] - end - - def users_dossier_recapitulatif_link(dossier) - url = users_dossier_recapitulatif_url(dossier) - link_to(url, url, target: '_blank') - end - - def individual_tags - [ - { - libelle: 'civilité', - description: 'M., Mme', - target: :gender, - available_for_states: Dossier::SOUMIS - }, - { - libelle: 'nom', - description: "nom de l'usager", - target: :nom, - available_for_states: Dossier::SOUMIS - }, - { - libelle: 'prénom', - description: "prénom de l'usager", - target: :prenom, - available_for_states: Dossier::SOUMIS - } - ] - end - - def entreprise_tags - [ - { - libelle: 'SIREN', - description: '', - target: :siren, - available_for_states: Dossier::SOUMIS - }, - { - libelle: 'numéro de TVA intracommunautaire', - description: '', - target: :numero_tva_intracommunautaire, - available_for_states: Dossier::SOUMIS - }, - { - libelle: 'SIRET du siège social', - description: '', - target: :siret_siege_social, - available_for_states: Dossier::SOUMIS - }, - { - libelle: 'raison sociale', - description: '', - target: :raison_sociale, - available_for_states: Dossier::SOUMIS - } - ] - end - - def etablissement_tags - [ - { - libelle: 'adresse', - description: '', - target: :inline_adresse, - available_for_states: Dossier::SOUMIS - } - ] - end - def replace_tags(text, dossier) if text.nil? return '' @@ -176,9 +171,9 @@ module TagsSubstitutionConcern tags_and_datas = [ [dossier_tags, dossier], - [individual_tags, dossier.individual], - [entreprise_tags, dossier.entreprise], - [etablissement_tags, dossier.entreprise&.etablissement] + [INDIVIDUAL_TAGS, dossier.individual], + [ENTREPRISE_TAGS, dossier.entreprise], + [ETABLISSEMENT_TAGS, dossier.entreprise&.etablissement] ] tags_and_datas @@ -202,7 +197,7 @@ module TagsSubstitutionConcern if tag.key?(:target) value = data.send(tag[:target]) else - value = tag[:lambda].(data) + value = instance_exec(data, &tag[:lambda]) end replace_tag(acc, tag, value) end From 8f41ab89cfee756594490b9cb61d96bd488c9602 Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Thu, 18 Jan 2018 18:06:40 +0100 Subject: [PATCH 13/16] =?UTF-8?q?[#1203]=20Do=20not=20include=20champs=20p?= =?UTF-8?q?riv=C3=A9s=20in=20accus=C3=A9=20de=20r=C3=A9ception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../concerns/tags_substitution_concern.rb | 25 +++++++--- .../concern/tags_substitution_concern_spec.rb | 46 +++++++++++++++++-- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index 163bf3f04..385fe951c 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -116,7 +116,7 @@ module TagsSubstitutionConcern identity_tags = ENTREPRISE_TAGS + ETABLISSEMENT_TAGS end - filter_tags(identity_tags + dossier_tags) + procedure_type_de_champ_public_private_tags + filter_tags(identity_tags + dossier_tags + champ_public_tags + champ_private_tags) end private @@ -156,9 +156,22 @@ module TagsSubstitutionConcern tags.select { |tag| tag[:available_for_states].include?(self.class::DOSSIER_STATE) } 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 } } + def champ_public_tags + types_de_champ_tags(procedure.types_de_champ, Dossier::SOUMIS) + end + + def champ_private_tags + types_de_champ_tags(procedure.types_de_champ_private, Dossier::INSTRUCTION_COMMENCEE) + end + + def types_de_champ_tags(types_de_champ, available_for_states) + types_de_champ.map { |tdc| + { + libelle: tdc.libelle, + description: tdc.description, + available_for_states: available_for_states + } + } end def replace_tags(text, dossier) @@ -166,8 +179,8 @@ module TagsSubstitutionConcern 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) + 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 = [ [dossier_tags, dossier], diff --git a/spec/models/concern/tags_substitution_concern_spec.rb b/spec/models/concern/tags_substitution_concern_spec.rb index a05b30590..22188d0d6 100644 --- a/spec/models/concern/tags_substitution_concern_spec.rb +++ b/spec/models/concern/tags_substitution_concern_spec.rb @@ -131,10 +131,10 @@ describe TagsSubstitutionConcern, type: :model do 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 + context 'and it is used in the template' do let(:template) { '--libelleA--' } - context 'and its value in the dossier are not nil' do + context 'and its value in the dossier is not nil' do before { dossier.champs_private.first.update_attributes(value: 'libelle1') } it { is_expected.to eq('libelle1') } @@ -142,6 +142,28 @@ describe TagsSubstitutionConcern, type: :model do end end + context 'when the dossier is en construction' do + let(:state) { 'en_construction' } + let(:template) { '--libelleA--' } + + context 'champs privés are not valid tags' do + # The dossier just transitionned from brouillon to en construction, + # so champs private are not valid tags yet + + let(:types_de_champ_private) { [create(:type_de_champ_private, libelle: 'libelleA')] } + + it { is_expected.to eq('--libelleA--') } + end + + context 'champs publics are valid tags' do + let(:types_de_champ) { [create(:type_de_champ_public, libelle: 'libelleA')] } + + before { dossier.champs.first.update_attributes(value: 'libelle1') } + + it { is_expected.to eq('libelle1') } + end + end + context 'when the procedure has 2 types de champ date and datetime' do let(:types_de_champ) do [ @@ -249,16 +271,34 @@ describe TagsSubstitutionConcern, type: :model do describe 'tags' do subject { template_concern.tags } + let(:types_de_champ) { [create(:type_de_champ_public, libelle: 'public')] } + let(:types_de_champ_private) { [create(:type_de_champ_private, libelle: 'privé')] } + context 'when generating a document for a dossier terminé' do it { is_expected.to include(include({ libelle: 'motivation' })) } it { is_expected.to include(include({ libelle: 'date de décision' })) } + it { is_expected.to include(include({ libelle: 'public' })) } + it { is_expected.to include(include({ libelle: 'privé' })) } end - context 'when generating a document for a dossier that is not terminé' do + context 'when generating a document for a dossier en instruction' do let(:state) { 'en_instruction' } it { is_expected.not_to include(include({ libelle: 'motivation' })) } it { is_expected.not_to include(include({ libelle: 'date de décision' })) } + + it { is_expected.to include(include({ libelle: 'public' })) } + it { is_expected.to include(include({ libelle: 'privé' })) } + end + + context 'when generating a document for a dossier en construction' do + let(:state) { 'en_construction' } + + it { is_expected.not_to include(include({ libelle: 'motivation' })) } + it { is_expected.not_to include(include({ libelle: 'date de décision' })) } + it { is_expected.not_to include(include({ libelle: 'privé' })) } + + it { is_expected.to include(include({ libelle: 'public' })) } end end end From 9b4ce1517ce5495adfb5ca2431623a417ee692d0 Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Wed, 17 Jan 2018 16:54:30 +0100 Subject: [PATCH 14/16] Use lambda instead of send for etablissement tag --- app/models/concerns/tags_substitution_concern.rb | 12 ++++-------- .../models/concern/tags_substitution_concern_spec.rb | 10 ++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index 385fe951c..b6ce914d0 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -97,14 +97,11 @@ module TagsSubstitutionConcern description: '', target: :raison_sociale, available_for_states: Dossier::SOUMIS - } - ] - - ETABLISSEMENT_TAGS = [ + }, { libelle: 'adresse', description: '', - target: :inline_adresse, + lambda: -> (e) { e&.etablissement&.inline_adresse }, available_for_states: Dossier::SOUMIS } ] @@ -113,7 +110,7 @@ module TagsSubstitutionConcern if procedure.for_individual? identity_tags = INDIVIDUAL_TAGS else - identity_tags = ENTREPRISE_TAGS + ETABLISSEMENT_TAGS + identity_tags = ENTREPRISE_TAGS end filter_tags(identity_tags + dossier_tags + champ_public_tags + champ_private_tags) @@ -185,8 +182,7 @@ module TagsSubstitutionConcern tags_and_datas = [ [dossier_tags, dossier], [INDIVIDUAL_TAGS, dossier.individual], - [ENTREPRISE_TAGS, dossier.entreprise], - [ETABLISSEMENT_TAGS, dossier.entreprise&.etablissement] + [ENTREPRISE_TAGS, dossier.entreprise] ] tags_and_datas diff --git a/spec/models/concern/tags_substitution_concern_spec.rb b/spec/models/concern/tags_substitution_concern_spec.rb index 22188d0d6..906e20ee3 100644 --- a/spec/models/concern/tags_substitution_concern_spec.rb +++ b/spec/models/concern/tags_substitution_concern_spec.rb @@ -58,19 +58,13 @@ describe TagsSubstitutionConcern, type: :model do let(:template) do '--SIREN-- --numéro de TVA intracommunautaire-- --SIRET du siège social-- --raison sociale-- --adresse--' end + let(:etablissement) { create(:etablissement) } let(:expected_text) do - "#{entreprise.siren} #{entreprise.numero_tva_intracommunautaire} #{entreprise.siret_siege_social} #{entreprise.raison_sociale} --adresse--" + "#{entreprise.siren} #{entreprise.numero_tva_intracommunautaire} #{entreprise.siret_siege_social} #{entreprise.raison_sociale} #{etablissement.inline_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 From e9ec9e410fd11e19b6456acc3a90ba2aa25240f3 Mon Sep 17 00:00:00 2001 From: Mathieu Magnin Date: Thu, 18 Jan 2018 17:45:33 +0100 Subject: [PATCH 15/16] [Fix #1304] If procedure has no path do not crash when hiding it --- app/controllers/admin/procedures_controller.rb | 6 +++++- spec/controllers/admin/procedures_controller_spec.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin/procedures_controller.rb b/app/controllers/admin/procedures_controller.rb index 034df45e0..e451af182 100644 --- a/app/controllers/admin/procedures_controller.rb +++ b/app/controllers/admin/procedures_controller.rb @@ -45,7 +45,11 @@ class Admin::ProceduresController < AdminController def hide procedure = current_administrateur.procedures.find(params[:id]) procedure.hide! - procedure.procedure_path.destroy + # procedure should no longer be reachable so we delete its procedure_path + # that way it is also available for another procedure + # however, sometimes the path has already been deleted (ex: stolen by another procedure), + # so we're not certain the procedure has a procedure_path anymore + procedure.procedure_path.try(:destroy) flash.notice = "Procédure supprimée, en cas d'erreur contactez nous : contact@tps.apientreprise.fr" redirect_to admin_procedures_draft_path diff --git a/spec/controllers/admin/procedures_controller_spec.rb b/spec/controllers/admin/procedures_controller_spec.rb index 2564a49c6..829b3a7cb 100644 --- a/spec/controllers/admin/procedures_controller_spec.rb +++ b/spec/controllers/admin/procedures_controller_spec.rb @@ -559,5 +559,15 @@ describe Admin::ProceduresController, type: :controller do it { expect(procedure.hidden_at).not_to be_nil } it { expect(procedure.procedure_path).to be_nil } end + + context "when procedure has no path" do + let!(:procedure) { create :procedure, administrateur: admin } + + it { expect{ subject }.not_to raise_error } + it do + subject + expect(procedure.reload.hidden_at).not_to be_nil + end + end end end From 469aca999bf79ac62a63cf9e4f1882d4caba3142 Mon Sep 17 00:00:00 2001 From: Mathieu Magnin Date: Fri, 19 Jan 2018 13:20:53 +0100 Subject: [PATCH 16/16] Revert "Remove `data_provide` and `data_date_format` attributes" This reverts commit 273b3f2faf5e91289c51ace170ad515c4d85e035. --- Gemfile | 2 ++ Gemfile.lock | 3 +++ app/assets/javascripts/application.js | 2 ++ app/assets/stylesheets/application.scss | 1 + app/assets/stylesheets/description.scss | 1 + app/decorators/champ_decorator.rb | 10 --------- app/models/champ.rb | 9 ++++++++ .../users/description/champs/_date.html.haml | 2 +- .../description/champs/_datetime.html.haml | 13 +++++++---- spec/models/champ_shared_example.rb | 22 +++++++++++++++++++ .../users/description/show.html.haml_spec.rb | 2 +- 11 files changed, 51 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 8666492d0..591ce5f76 100644 --- a/Gemfile +++ b/Gemfile @@ -64,6 +64,8 @@ gem 'leaflet-rails' gem 'leaflet-markercluster-rails', '~> 0.7.0' gem 'leaflet-draw-rails' +gem 'bootstrap-datepicker-rails' + gem 'chartkick' gem 'logstasher' diff --git a/Gemfile.lock b/Gemfile.lock index 0b10d1a88..df20eaa38 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,6 +92,8 @@ GEM rubyzip (~> 1.0.0) bcrypt (3.1.11) bindata (2.3.4) + bootstrap-datepicker-rails (1.6.4.1) + railties (>= 3.0) bootstrap-sass (3.3.7) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) @@ -729,6 +731,7 @@ DEPENDENCIES active_model_serializers administrate apipie-rails + bootstrap-datepicker-rails bootstrap-sass (~> 3.3.5) bootstrap-wysihtml5-rails (~> 0.3.3.8) brakeman diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 62f9b7d15..d5ce61307 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -17,6 +17,8 @@ //= require chartkick //= require_tree ./old_design //= require bootstrap-sprockets +//= require bootstrap-datepicker/core +//= require bootstrap-datepicker/locales/bootstrap-datepicker.fr.js //= require leaflet.js //= require d3.min diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index ed0ae2bf5..99cf3f12a 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -46,6 +46,7 @@ // = require attestation_recapitulatif // = require_self +// = require bootstrap-datepicker3 // = require leaflet // = require font-awesome // = require franceconnect diff --git a/app/assets/stylesheets/description.scss b/app/assets/stylesheets/description.scss index 3c97d8476..3efa4d01b 100644 --- a/app/assets/stylesheets/description.scss +++ b/app/assets/stylesheets/description.scss @@ -1,4 +1,5 @@ @import "bootstrap"; +@import "bootstrap-datepicker3"; #description-page #liste-champs { diff --git a/app/decorators/champ_decorator.rb b/app/decorators/champ_decorator.rb index ee6e524e3..4652d5d08 100644 --- a/app/decorators/champ_decorator.rb +++ b/app/decorators/champ_decorator.rb @@ -19,16 +19,6 @@ class ChampDecorator < Draper::Decorator end end - def date_for_input - if object.value.present? - if type_champ == "date" - object.value - elsif type_champ == "datetime" && object.value != ' 00:00' - DateTime.parse(object.value, "%d/%m/%Y %H:%M").strftime("%d/%m/%Y") - end - end - end - def description_with_links description.gsub(URI.regexp, '\0') if description end diff --git a/app/models/champ.rb b/app/models/champ.rb index abba4569b..ca6a59c8c 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -17,6 +17,15 @@ class Champ < ActiveRecord::Base mandatory end + def data_provide + return 'datepicker' if (type_champ == 'datetime') && !(BROWSER.value.chrome? || BROWSER.value.edge?) + return 'typeahead' if type_champ == 'address' + end + + def data_date_format + ('dd/mm/yyyy' if type_champ == 'datetime') + end + def same_hour? num same_date? num, '%H' end diff --git a/app/views/users/description/champs/_date.html.haml b/app/views/users/description/champs/_date.html.haml index 7e0d94563..74088b67c 100644 --- a/app/views/users/description/champs/_date.html.haml +++ b/app/views/users/description/champs/_date.html.haml @@ -1,5 +1,5 @@ %input.form-control{ name: "champs['#{champ.id}']", placeholder: "JJ/MM/AAAA", id: "champs_#{champ.id}", - value: champ.date_for_input, + value: champ.value ? champ.object.value : champ.value, type: "date" } diff --git a/app/views/users/description/champs/_datetime.html.haml b/app/views/users/description/champs/_datetime.html.haml index 298d66860..061ed821d 100644 --- a/app/views/users/description/champs/_datetime.html.haml +++ b/app/views/users/description/champs/_datetime.html.haml @@ -1,13 +1,18 @@ -= render partial: 'users/description/champs/date', locals: { champ: champ } +%input.form-control{ name: "champs['#{champ.id}']", + placeholder: champ.libelle, + id: "champs_#{champ.id}", + value: (champ.value.split(/[ ][0-9]*:[0-9]*/).first if champ.value.present?), + type: champ.type_champ, + 'data-provide' => champ.data_provide, + 'data-date-format' => champ.data_date_format } -%br -%select.form-control{ name: "time_hour['#{champ.id}']", style: 'width:50px;display:inline;', id: "time_hour_#{champ.id}" } +%select.form-control{ name: "time_hour['#{champ.id}']", style: 'margin-left: 5px;', id: "time_hour_#{champ.id}" } - (0..23).each do |num| - num = "%.2i" %num %option{ value: num, selected: (:selected if champ.same_hour?(num)) } = num h -%select.form-control{ name: "time_minute['#{champ.id}']", style: 'width:50px;display:inline;', id: "time_minute_#{champ.id}" } +%select.form-control{ name: "time_minute['#{champ.id}']", id: "time_minute_#{champ.id}" } - (0..59).each do |num| - num = "%.2i" %num - if num.to_i%5 == 0 diff --git a/spec/models/champ_shared_example.rb b/spec/models/champ_shared_example.rb index 60dc38e9a..c8bb4360e 100644 --- a/spec/models/champ_shared_example.rb +++ b/spec/models/champ_shared_example.rb @@ -26,6 +26,28 @@ shared_examples 'champ_spec' do end end + describe 'data_provide' do + let(:champ) { create :champ } + + subject { champ.data_provide } + + context 'when type_champ is datetime' do + before do + champ.type_de_champ = create :type_de_champ_public, type_champ: 'datetime' + end + + it { is_expected.to eq 'datepicker' } + end + + context 'when type_champ is address' do + before do + champ.type_de_champ = create :type_de_champ_public, type_champ: 'address' + end + + it { is_expected.to eq 'typeahead' } + end + end + describe '.departement', vcr: { cassette_name: 'call_geo_api_departements' } do subject { Champ.departements } diff --git a/spec/views/users/description/show.html.haml_spec.rb b/spec/views/users/description/show.html.haml_spec.rb index c4d78f860..4f7bf3d62 100644 --- a/spec/views/users/description/show.html.haml_spec.rb +++ b/spec/views/users/description/show.html.haml_spec.rb @@ -82,7 +82,7 @@ describe 'users/description/show.html.haml', type: :view do end describe 'datetime value is correctly setup when is not nil' do - it { expect(rendered).to have_css("input[type='date'][id='champs_#{champ_datetime.id}'][value='22/06/2016']") } + it { expect(rendered).to have_css("input[type='datetime'][id='champs_#{champ_datetime.id}'][value='22/06/2016']") } it { expect(rendered).to have_css("option[value='12'][selected='selected']") } it { expect(rendered).to have_css("option[value='05'][selected='selected']") } end