diff --git a/app/assets/stylesheets/new_design/message.scss b/app/assets/stylesheets/new_design/message.scss new file mode 100644 index 000000000..025a7783e --- /dev/null +++ b/app/assets/stylesheets/new_design/message.scss @@ -0,0 +1,37 @@ +@import "colors"; +@import "constants"; + +.message { + display: flex; + align-items: flex-start; + margin-bottom: $default-padding; + padding: $default-padding; + background: #FFFFFF; + border-radius: 3px; + + .person-icon { + margin-right: $default-spacer; + } + + h2 { + margin-bottom: $default-spacer; + } + + .mail { + font-weight: bold; + } + + .guest, + .date { + font-size: 12px; + color: $grey; + } + + .date { + float: right; + } + + .attachment-link { + margin-top: $default-spacer; + } +} diff --git a/app/assets/stylesheets/new_design/messagerie.scss b/app/assets/stylesheets/new_design/messagerie.scss index 74102f1c7..c8fe54379 100644 --- a/app/assets/stylesheets/new_design/messagerie.scss +++ b/app/assets/stylesheets/new_design/messagerie.scss @@ -1,58 +1,25 @@ @import "colors"; -@import "common"; @import "constants"; -.messagerie { - .messages-list { - max-height: 350px; - overflow-y: scroll; - border: 1px solid $border-grey; - background: $light-grey; - padding: 2 * $default-spacer; - margin-bottom: $default-spacer; - border-radius: 4px; +.messages-list { + max-height: 350px; + overflow-y: scroll; + border: 1px solid $border-grey; + background: $light-grey; + padding: 2 * $default-spacer; + margin-bottom: $default-spacer; + border-radius: 4px; - > li { - display: flex; - align-items: flex-start; - margin-bottom: $default-padding; - padding: $default-padding; - background: #FFFFFF; - width: 80%; - border-radius: 3px; + .message { + width: 80%; - &.from-me { - margin-left: auto; - } + &.from-me { + margin-left: auto; } } +} - .person-icon { - margin-right: $default-spacer; - } - - h2 { - margin-bottom: $default-spacer; - } - - .mail { - font-weight: bold; - } - - .guest, - .date { - font-size: 12px; - color: $grey; - } - - .date { - float: right; - } - - .attachment-link { - margin-top: $default-spacer; - } - +.messagerie { .message-textarea { margin-bottom: $default-spacer; } diff --git a/app/assets/stylesheets/new_design/print.scss b/app/assets/stylesheets/new_design/print.scss index 0a62ad243..2a6ebd178 100644 --- a/app/assets/stylesheets/new_design/print.scss +++ b/app/assets/stylesheets/new_design/print.scss @@ -44,10 +44,6 @@ th { padding-left: 0; max-height: none; - li { - margin-bottom: 40px; - } - h2 { font-size: 110%; } @@ -58,6 +54,10 @@ th { } } +.message { + margin-bottom: 40px; +} + .updated-at { display: none; } diff --git a/app/controllers/admin/procedures_controller.rb b/app/controllers/admin/procedures_controller.rb index 2d24d5c65..6532cc3a3 100644 --- a/app/controllers/admin/procedures_controller.rb +++ b/app/controllers/admin/procedures_controller.rb @@ -94,24 +94,12 @@ class Admin::ProceduresController < AdminController def publish procedure = current_administrateur.procedures.find(params[:procedure_id]) - new_procedure_path = ProcedurePath.new( - { - path: params[:procedure_path], - procedure: procedure, - administrateur: procedure.administrateur - } - ) - - if new_procedure_path.validate - new_procedure_path.delete - else + if !ProcedurePath.valid?(procedure, params[:procedure_path]) flash.alert = 'Lien de la démarche invalide' return redirect_to admin_procedures_path end - if procedure.may_publish?(params[:procedure_path]) - procedure.publish!(params[:procedure_path]) - + if procedure.publish_or_reopen!(params[:procedure_path]) flash.notice = "Démarche publiée" redirect_to admin_procedures_path else @@ -205,10 +193,7 @@ class Admin::ProceduresController < AdminController def path_list json_path_list = ProcedurePath - .joins(:procedure) - .where(procedures: { archived_at: nil }) - .where("path LIKE ?", "%#{params[:request]}%") - .order(:id) + .find_with_path(params[:request]) .pluck(:path, :administrateur_id) .map do |path, administrateur_id| { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a0b353e99..e1b052a25 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,7 +3,7 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. - protect_from_forgery with: :exception + protect_from_forgery with: :exception, if: -> { !Rails.env.test? } before_action :load_navbar_left_pannel_partial_url before_action :set_raven_context before_action :authorize_request_for_profiler @@ -41,6 +41,16 @@ class ApplicationController < ActionController::Base protected + def authenticate_logged_user! + if gestionnaire_signed_in? + authenticate_gestionnaire! + elsif administrateur_signed_in? + authenticate_administrateur! + else + authenticate_user! + end + end + def authenticate_gestionnaire! if gestionnaire_signed_in? super @@ -80,6 +90,8 @@ class ApplicationController < ActionController::Base logged_users.first end + helper_method :logged_user + def logged_user_roles roles = logged_users.map { |logged_user| logged_user.class.name } roles.any? ? roles.join(', ') : 'Guest' diff --git a/app/controllers/champs/dossier_link_controller.rb b/app/controllers/champs/dossier_link_controller.rb new file mode 100644 index 000000000..d4297c733 --- /dev/null +++ b/app/controllers/champs/dossier_link_controller.rb @@ -0,0 +1,11 @@ +class Champs::DossierLinkController < ApplicationController + before_action :authenticate_logged_user! + + def show + if params[:dossier].key?(:champs_attributes) + @dossier_id = params[:dossier][:champs_attributes][params[:position]][:value] + else + @dossier_id = params[:dossier][:champs_private_attributes][params[:position]][:value] + end + end +end diff --git a/app/controllers/new_user/feedbacks_controller.rb b/app/controllers/new_user/feedbacks_controller.rb index 3c7b391da..40ec33f80 100644 --- a/app/controllers/new_user/feedbacks_controller.rb +++ b/app/controllers/new_user/feedbacks_controller.rb @@ -1,6 +1,8 @@ -class NewUser::FeedbacksController < ApplicationController - def create - current_user.feedbacks.create!(rating: params[:rating]) - flash.notice = "Merci de votre retour, si vous souhaitez nous en dire plus, n'hésitez pas à #{view_context.contact_link('nous contacter', type: Helpscout::FormAdapter::TYPE_AMELIORATION)}." +module NewUser + class FeedbacksController < UserController + def create + current_user.feedbacks.create!(rating: params[:rating]) + flash.notice = "Merci de votre retour, si vous souhaitez nous en dire plus, n'hésitez pas à #{view_context.contact_link('nous contacter', type: Helpscout::FormAdapter::TYPE_AMELIORATION)}." + end end end diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 6a183433a..4d2c54ee5 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -129,19 +129,23 @@ class StatsController < ApplicationController Feedback.ratings.fetch(:neutral) => "Neutres", Feedback.ratings.fetch(:unhappy) => "Mécontents" } + interval = 6.weeks.ago.beginning_of_week..1.week.ago.beginning_of_week - totals = Feedback.where(created_at: 5.weeks.ago..Time.now).group_by_week(:created_at).count + totals = Feedback + .where(created_at: interval) + .group_by_week(:created_at) + .count Feedback.ratings.values.map do |rating| data = Feedback - .where(created_at: 5.weeks.ago..Time.now, rating: rating) + .where(created_at: interval, rating: rating) .group_by_week(:created_at) .count .map do |week, count| total = totals[week] if total > 0 - [week, (count.to_f / total).round(2)] + [week, (count.to_f / total * 100).round(2)] else 0 end diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 06d1ec4eb..9349d72d5 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -162,13 +162,6 @@ class Users::DossiersController < UsersController redirect_to url_for dossiers_path end - def text_summary - dossier = Dossier.find(params[:dossier_id]) - render json: { textSummary: dossier.text_summary } - rescue ActiveRecord::RecordNotFound - render json: {}, status: 404 - end - private def check_siret diff --git a/app/javascript/new_design/champs/dossier-link.js b/app/javascript/new_design/champs/dossier-link.js deleted file mode 100644 index 0a86d53c9..000000000 --- a/app/javascript/new_design/champs/dossier-link.js +++ /dev/null @@ -1,42 +0,0 @@ -import $ from 'jquery'; - -function showNotFound() { - $('.dossier-link .text-info').hide(); - $('.dossier-link .text-warning').show(); -} - -function showData(data) { - $('.dossier-link .dossier-text-summary').text(data.textSummary); - $('.dossier-link .text-info').show(); - $('.dossier-link .text-warning').hide(); -} - -function hideEverything() { - $('.dossier-link .text-info').hide(); - $('.dossier-link .text-warning').hide(); -} - -function fetchProcedureLibelle(e) { - const dossierId = $(e.target).val(); - if (dossierId) { - $.get(`/users/dossiers/${dossierId}/text_summary`) - .done(showData) - .fail(showNotFound); - } else { - hideEverything(); - } -} - -let timeOut; -function debounceFetchProcedureLibelle(e) { - if (timeOut) { - clearTimeout(timeOut); - } - timeOut = setTimeout(() => fetchProcedureLibelle(e), 300); -} - -$(document).on( - 'input', - '[data-type=dossier-link]', - debounceFetchProcedureLibelle -); diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 64c5c01c7..971840e3a 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -21,7 +21,6 @@ import '../new_design/form-validation'; import '../new_design/carto'; import '../new_design/select2'; -import '../new_design/champs/dossier-link'; import '../new_design/champs/linked-drop-down-list'; import '../new_design/champs/siret'; diff --git a/app/models/administrateur.rb b/app/models/administrateur.rb index 37242cb4b..f45bb1447 100644 --- a/app/models/administrateur.rb +++ b/app/models/administrateur.rb @@ -10,6 +10,7 @@ class Administrateur < ApplicationRecord has_many :administrateurs_procedures has_many :admin_procedures, through: :administrateurs_procedures, source: :procedure has_many :services + has_many :dossiers, -> { state_not_brouillon }, through: :procedures before_validation -> { sanitize_email(:email) } before_save :ensure_api_token diff --git a/app/models/procedure.rb b/app/models/procedure.rb index d508e64fa..79f432ee6 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -70,6 +70,9 @@ class Procedure < ApplicationRecord event :publish, after: :after_publish, guard: :can_publish? do transitions from: :brouillon, to: :publiee + end + + event :reopen, after: :after_reopen, guard: :can_publish? do transitions from: :archivee, to: :publiee end @@ -84,33 +87,27 @@ class Procedure < ApplicationRecord end end - def after_publish(path) - now = Time.now - update( - test_started_at: now, - archived_at: nil, - published_at: now - ) - procedure_path = ProcedurePath.find_by(path: path) + def publish_or_reopen!(path) + if archivee? && may_reopen?(path) + reopen!(path) + elsif may_publish?(path) + reset! + publish!(path) + end + end + + def publish_with_path!(path) + procedure_path = ProcedurePath + .where(administrateur: administrateur) + .find_by(path: path) if procedure_path.present? procedure_path.publish!(self) else - ProcedurePath.create(procedure: self, administrateur: administrateur, path: path) + create_procedure_path!(administrateur: administrateur, path: path) end end - def after_archive - update(archived_at: Time.now) - end - - def after_hide - now = Time.now - update(hidden_at: now) - procedure_path&.hide! - dossiers.update_all(hidden_at: now) - end - def reset! if locked? raise "Can not reset a locked procedure." @@ -132,15 +129,6 @@ class Procedure < ApplicationRecord publiee? || archivee? end - def can_publish?(path) - procedure_path = ProcedurePath.find_by(path: path) - if procedure_path.present? - administrateur.owns?(procedure_path) - else - true - end - end - # Warning: dossier after_save build_default_champs must be removed # to save a dossier created from this method def new_dossier @@ -372,6 +360,38 @@ class Procedure < ApplicationRecord private + def can_publish?(path) + procedure_path = ProcedurePath.find_by(path: path) + if procedure_path.present? + administrateur.owns?(procedure_path) + else + true + end + end + + def after_publish(path) + update!(published_at: Time.now) + + publish_with_path!(path) + end + + def after_archive + update!(archived_at: Time.now) + end + + def after_hide + now = Time.now + update!(hidden_at: now) + procedure_path&.hide! + dossiers.update_all(hidden_at: now) + end + + def after_reopen(path) + update!(published_at: Time.now, archived_at: nil) + + publish_with_path!(path) + end + def update_juridique_required self.juridique_required ||= (cadre_juridique.present? || deliberation.attached?) true diff --git a/app/models/procedure_path.rb b/app/models/procedure_path.rb index 197162224..7bab10680 100644 --- a/app/models/procedure_path.rb +++ b/app/models/procedure_path.rb @@ -1,17 +1,29 @@ class ProcedurePath < ApplicationRecord - validates :path, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, presence: true, allow_blank: false, allow_nil: false + validates :path, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: true, presence: true, allow_blank: false, allow_nil: false validates :administrateur_id, presence: true, allow_blank: false, allow_nil: false validates :procedure_id, presence: true, allow_blank: false, allow_nil: false belongs_to :procedure belongs_to :administrateur + def self.valid?(procedure, path) + create_with(procedure: procedure, administrateur: procedure.administrateur) + .find_or_initialize_by(path: path).validate + end + + def self.find_with_path(path) + joins(:procedure) + .where.not(procedures: { aasm_state: :archivee }) + .where("path LIKE ?", "%#{path}%") + .order(:id) + end + def hide! destroy! end def publish!(new_procedure) - if procedure&.publiee? + if procedure&.publiee? && procedure != new_procedure procedure.archive! end update!(procedure: new_procedure) diff --git a/app/views/champs/dossier_link/show.js.erb b/app/views/champs/dossier_link/show.js.erb new file mode 100644 index 000000000..d561ee26a --- /dev/null +++ b/app/views/champs/dossier_link/show.js.erb @@ -0,0 +1,3 @@ +<%= render_to_element('.dossier-link .help-block', + partial: 'shared/champs/dossier_link/help_block', + locals: { id: @dossier_id }) %> diff --git a/app/views/shared/champs/dossier_link/_help_block.html.haml b/app/views/shared/champs/dossier_link/_help_block.html.haml new file mode 100644 index 000000000..624dc21c8 --- /dev/null +++ b/app/views/shared/champs/dossier_link/_help_block.html.haml @@ -0,0 +1,8 @@ +- if id.present? + - dossier = logged_user.dossiers.find_by(id: id) + - if dossier.blank? + %p.text-warning + Ce dossier est inconnu + - else + %p.text-info + %span.dossier-text-summary= sanitize(dossier.text_summary) diff --git a/app/views/shared/dossiers/_messagerie.html.haml b/app/views/shared/dossiers/_messagerie.html.haml index be8414010..ee380ee09 100644 --- a/app/views/shared/dossiers/_messagerie.html.haml +++ b/app/views/shared/dossiers/_messagerie.html.haml @@ -1,7 +1,7 @@ .messagerie.container %ul.messages-list - dossier.commentaires.each do |commentaire| - %li{ class: commentaire_is_from_me_class(commentaire, user_email) } + %li.message{ class: commentaire_is_from_me_class(commentaire, user_email) } = render partial: "shared/dossiers/messages/message", locals: { commentaire: commentaire, user_email: user_email, messagerie_seen_at: messagerie_seen_at } = render partial: "shared/dossiers/messages/form", locals: { commentaire: new_commentaire, form_url: form_url } diff --git a/app/views/shared/dossiers/editable_champs/_dossier_link.html.haml b/app/views/shared/dossiers/editable_champs/_dossier_link.html.haml index 72ce494ec..40d830ad8 100644 --- a/app/views/shared/dossiers/editable_champs/_dossier_link.html.haml +++ b/app/views/shared/dossiers/editable_champs/_dossier_link.html.haml @@ -1,18 +1,9 @@ -- dossier = Dossier.find_by(id: champ.value) -- show_text_summary = dossier.present? -- show_warning = !show_text_summary && champ.value.present? -- text_summary = sanitize(dossier&.text_summary) - .dossier-link = form.number_field :value, placeholder: "Numéro de dossier", autocomplete: 'off', - 'data-type': 'dossier-link', - required: champ.mandatory? + required: champ.mandatory?, + data: { remote: true, url: champs_dossier_link_path(form.index) } .help-block - %p.text-info{ style: show_text_summary ? nil : 'display: none;' } - %span.dossier-text-summary= text_summary - - %p.text-warning{ style: show_warning ? nil : 'display: none;' } - Ce dossier est inconnu + = render partial: 'shared/champs/dossier_link/help_block', locals: { id: champ.value } diff --git a/config/routes.rb b/config/routes.rb index cb8855c75..78a27debe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -111,6 +111,7 @@ Rails.application.routes.draw do namespace :champs do get ':champ_id/siret' => 'siret#index', as: 'siret' + get ':position/dossier_link', to: 'dossier_link#show', as: :dossier_link end namespace :commencer do @@ -158,8 +159,6 @@ Rails.application.routes.draw do post '/siret_informations' => 'dossiers#siret_informations' put '/change_siret' => 'dossiers#change_siret' - - get 'text_summary' => 'dossiers#text_summary' end resource 'dossiers' diff --git a/spec/controllers/champs/dossier_link_controller_spec.rb b/spec/controllers/champs/dossier_link_controller_spec.rb new file mode 100644 index 000000000..b01b91c41 --- /dev/null +++ b/spec/controllers/champs/dossier_link_controller_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Champs::DossierLinkController, type: :controller do + let(:user) { create(:user) } + let(:procedure) { create(:procedure, :published) } + + describe '#show' do + let(:dossier) { create(:dossier, user: user, procedure: procedure) } + + context 'when user is connected' do + render_views + before { sign_in user } + + let(:params) do + { + dossier: { + champs_attributes: { + '1' => { value: "#{dossier_id}" } + } + }, + position: '1' + } + end + let(:dossier_id) { dossier.id } + + context 'when the dossier exist' do + before { + get :show, params: params, format: 'js' + } + + it 'returns the procedure name' do + expect(response.body).to include('Dossier en brouillon') + expect(response.body).to include(procedure.libelle) + expect(response.body).to include(procedure.organisation) + end + end + + context 'when the dossier does not exist' do + let(:dossier_id) { '13' } + before { + get :show, params: params, format: 'js' + } + + it { expect(response.body).to include('Ce dossier est inconnu') } + end + end + + context 'when user is not connected' do + before { + get :show, params: { position: '1' }, format: 'js' + } + + it { expect(response.code).to eq('401') } + end + end +end diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb index 8a518d099..5161cb779 100644 --- a/spec/controllers/users/dossiers_controller_spec.rb +++ b/spec/controllers/users/dossiers_controller_spec.rb @@ -446,29 +446,4 @@ describe Users::DossiersController, type: :controller do subject end end - - describe 'Get #text_summary' do - let!(:dossier) { create(:dossier, procedure: procedure) } - - context 'when user is connected' do - before { sign_in user } - - context 'when the dossier exist' do - before { get :text_summary, params: { dossier_id: dossier.id } } - it 'returns the procedure name' do - expect(JSON.parse(response.body)).to eq("textSummary" => "Dossier en brouillon répondant à la démarche #{procedure.libelle} gérée par l'organisme #{procedure.organisation}") - end - end - - context 'when the dossier does not exist' do - before { get :text_summary, params: { dossier_id: 666 } } - it { expect(response.code).to eq('404') } - end - end - - context 'when user is not connected' do - before { get :text_summary, params: { dossier_id: dossier.id } } - it { expect(response.code).to eq('302') } - end - end end