Merge branch 'dev'

This commit is contained in:
gregoirenovel 2018-09-11 14:44:25 +02:00
commit 8ab5dc95db
21 changed files with 231 additions and 198 deletions

View file

@ -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;
}
}

View file

@ -1,58 +1,25 @@
@import "colors"; @import "colors";
@import "common";
@import "constants"; @import "constants";
.messagerie { .messages-list {
.messages-list { max-height: 350px;
max-height: 350px; overflow-y: scroll;
overflow-y: scroll; border: 1px solid $border-grey;
border: 1px solid $border-grey; background: $light-grey;
background: $light-grey; padding: 2 * $default-spacer;
padding: 2 * $default-spacer; margin-bottom: $default-spacer;
margin-bottom: $default-spacer; border-radius: 4px;
border-radius: 4px;
> li { .message {
display: flex; width: 80%;
align-items: flex-start;
margin-bottom: $default-padding;
padding: $default-padding;
background: #FFFFFF;
width: 80%;
border-radius: 3px;
&.from-me { &.from-me {
margin-left: auto; margin-left: auto;
}
} }
} }
}
.person-icon { .messagerie {
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;
}
.message-textarea { .message-textarea {
margin-bottom: $default-spacer; margin-bottom: $default-spacer;
} }

View file

@ -44,10 +44,6 @@ th {
padding-left: 0; padding-left: 0;
max-height: none; max-height: none;
li {
margin-bottom: 40px;
}
h2 { h2 {
font-size: 110%; font-size: 110%;
} }
@ -58,6 +54,10 @@ th {
} }
} }
.message {
margin-bottom: 40px;
}
.updated-at { .updated-at {
display: none; display: none;
} }

View file

@ -94,24 +94,12 @@ class Admin::ProceduresController < AdminController
def publish def publish
procedure = current_administrateur.procedures.find(params[:procedure_id]) procedure = current_administrateur.procedures.find(params[:procedure_id])
new_procedure_path = ProcedurePath.new( if !ProcedurePath.valid?(procedure, params[:procedure_path])
{
path: params[:procedure_path],
procedure: procedure,
administrateur: procedure.administrateur
}
)
if new_procedure_path.validate
new_procedure_path.delete
else
flash.alert = 'Lien de la démarche invalide' flash.alert = 'Lien de la démarche invalide'
return redirect_to admin_procedures_path return redirect_to admin_procedures_path
end end
if procedure.may_publish?(params[:procedure_path]) if procedure.publish_or_reopen!(params[:procedure_path])
procedure.publish!(params[:procedure_path])
flash.notice = "Démarche publiée" flash.notice = "Démarche publiée"
redirect_to admin_procedures_path redirect_to admin_procedures_path
else else
@ -205,10 +193,7 @@ class Admin::ProceduresController < AdminController
def path_list def path_list
json_path_list = ProcedurePath json_path_list = ProcedurePath
.joins(:procedure) .find_with_path(params[:request])
.where(procedures: { archived_at: nil })
.where("path LIKE ?", "%#{params[:request]}%")
.order(:id)
.pluck(:path, :administrateur_id) .pluck(:path, :administrateur_id)
.map do |path, administrateur_id| .map do |path, administrateur_id|
{ {

View file

@ -3,7 +3,7 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception. # Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead. # 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 :load_navbar_left_pannel_partial_url
before_action :set_raven_context before_action :set_raven_context
before_action :authorize_request_for_profiler before_action :authorize_request_for_profiler
@ -41,6 +41,16 @@ class ApplicationController < ActionController::Base
protected 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! def authenticate_gestionnaire!
if gestionnaire_signed_in? if gestionnaire_signed_in?
super super
@ -80,6 +90,8 @@ class ApplicationController < ActionController::Base
logged_users.first logged_users.first
end end
helper_method :logged_user
def logged_user_roles def logged_user_roles
roles = logged_users.map { |logged_user| logged_user.class.name } roles = logged_users.map { |logged_user| logged_user.class.name }
roles.any? ? roles.join(', ') : 'Guest' roles.any? ? roles.join(', ') : 'Guest'

View file

@ -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

View file

@ -1,6 +1,8 @@
class NewUser::FeedbacksController < ApplicationController module NewUser
def create class FeedbacksController < UserController
current_user.feedbacks.create!(rating: params[:rating]) def create
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)}." 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
end end

View file

@ -129,19 +129,23 @@ class StatsController < ApplicationController
Feedback.ratings.fetch(:neutral) => "Neutres", Feedback.ratings.fetch(:neutral) => "Neutres",
Feedback.ratings.fetch(:unhappy) => "Mécontents" 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| Feedback.ratings.values.map do |rating|
data = Feedback data = Feedback
.where(created_at: 5.weeks.ago..Time.now, rating: rating) .where(created_at: interval, rating: rating)
.group_by_week(:created_at) .group_by_week(:created_at)
.count .count
.map do |week, count| .map do |week, count|
total = totals[week] total = totals[week]
if total > 0 if total > 0
[week, (count.to_f / total).round(2)] [week, (count.to_f / total * 100).round(2)]
else else
0 0
end end

View file

@ -162,13 +162,6 @@ class Users::DossiersController < UsersController
redirect_to url_for dossiers_path redirect_to url_for dossiers_path
end 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 private
def check_siret def check_siret

View file

@ -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
);

View file

@ -21,7 +21,6 @@ import '../new_design/form-validation';
import '../new_design/carto'; import '../new_design/carto';
import '../new_design/select2'; import '../new_design/select2';
import '../new_design/champs/dossier-link';
import '../new_design/champs/linked-drop-down-list'; import '../new_design/champs/linked-drop-down-list';
import '../new_design/champs/siret'; import '../new_design/champs/siret';

View file

@ -10,6 +10,7 @@ class Administrateur < ApplicationRecord
has_many :administrateurs_procedures has_many :administrateurs_procedures
has_many :admin_procedures, through: :administrateurs_procedures, source: :procedure has_many :admin_procedures, through: :administrateurs_procedures, source: :procedure
has_many :services has_many :services
has_many :dossiers, -> { state_not_brouillon }, through: :procedures
before_validation -> { sanitize_email(:email) } before_validation -> { sanitize_email(:email) }
before_save :ensure_api_token before_save :ensure_api_token

View file

@ -70,6 +70,9 @@ class Procedure < ApplicationRecord
event :publish, after: :after_publish, guard: :can_publish? do event :publish, after: :after_publish, guard: :can_publish? do
transitions from: :brouillon, to: :publiee transitions from: :brouillon, to: :publiee
end
event :reopen, after: :after_reopen, guard: :can_publish? do
transitions from: :archivee, to: :publiee transitions from: :archivee, to: :publiee
end end
@ -84,33 +87,27 @@ class Procedure < ApplicationRecord
end end
end end
def after_publish(path) def publish_or_reopen!(path)
now = Time.now if archivee? && may_reopen?(path)
update( reopen!(path)
test_started_at: now, elsif may_publish?(path)
archived_at: nil, reset!
published_at: now publish!(path)
) end
procedure_path = ProcedurePath.find_by(path: path) end
def publish_with_path!(path)
procedure_path = ProcedurePath
.where(administrateur: administrateur)
.find_by(path: path)
if procedure_path.present? if procedure_path.present?
procedure_path.publish!(self) procedure_path.publish!(self)
else else
ProcedurePath.create(procedure: self, administrateur: administrateur, path: path) create_procedure_path!(administrateur: administrateur, path: path)
end end
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! def reset!
if locked? if locked?
raise "Can not reset a locked procedure." raise "Can not reset a locked procedure."
@ -132,15 +129,6 @@ class Procedure < ApplicationRecord
publiee? || archivee? publiee? || archivee?
end 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 # Warning: dossier after_save build_default_champs must be removed
# to save a dossier created from this method # to save a dossier created from this method
def new_dossier def new_dossier
@ -372,6 +360,38 @@ class Procedure < ApplicationRecord
private 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 def update_juridique_required
self.juridique_required ||= (cadre_juridique.present? || deliberation.attached?) self.juridique_required ||= (cadre_juridique.present? || deliberation.attached?)
true true

View file

@ -1,17 +1,29 @@
class ProcedurePath < ApplicationRecord 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 :administrateur_id, presence: true, allow_blank: false, allow_nil: false
validates :procedure_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 :procedure
belongs_to :administrateur 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! def hide!
destroy! destroy!
end end
def publish!(new_procedure) def publish!(new_procedure)
if procedure&.publiee? if procedure&.publiee? && procedure != new_procedure
procedure.archive! procedure.archive!
end end
update!(procedure: new_procedure) update!(procedure: new_procedure)

View file

@ -0,0 +1,3 @@
<%= render_to_element('.dossier-link .help-block',
partial: 'shared/champs/dossier_link/help_block',
locals: { id: @dossier_id }) %>

View file

@ -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)

View file

@ -1,7 +1,7 @@
.messagerie.container .messagerie.container
%ul.messages-list %ul.messages-list
- dossier.commentaires.each do |commentaire| - 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/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 } = render partial: "shared/dossiers/messages/form", locals: { commentaire: new_commentaire, form_url: form_url }

View file

@ -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 .dossier-link
= form.number_field :value, = form.number_field :value,
placeholder: "Numéro de dossier", placeholder: "Numéro de dossier",
autocomplete: 'off', autocomplete: 'off',
'data-type': 'dossier-link', required: champ.mandatory?,
required: champ.mandatory? data: { remote: true, url: champs_dossier_link_path(form.index) }
.help-block .help-block
%p.text-info{ style: show_text_summary ? nil : 'display: none;' } = render partial: 'shared/champs/dossier_link/help_block', locals: { id: champ.value }
%span.dossier-text-summary= text_summary
%p.text-warning{ style: show_warning ? nil : 'display: none;' }
Ce dossier est inconnu

View file

@ -111,6 +111,7 @@ Rails.application.routes.draw do
namespace :champs do namespace :champs do
get ':champ_id/siret' => 'siret#index', as: 'siret' get ':champ_id/siret' => 'siret#index', as: 'siret'
get ':position/dossier_link', to: 'dossier_link#show', as: :dossier_link
end end
namespace :commencer do namespace :commencer do
@ -158,8 +159,6 @@ Rails.application.routes.draw do
post '/siret_informations' => 'dossiers#siret_informations' post '/siret_informations' => 'dossiers#siret_informations'
put '/change_siret' => 'dossiers#change_siret' put '/change_siret' => 'dossiers#change_siret'
get 'text_summary' => 'dossiers#text_summary'
end end
resource 'dossiers' resource 'dossiers'

View file

@ -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

View file

@ -446,29 +446,4 @@ describe Users::DossiersController, type: :controller do
subject subject
end end
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 end