Merge branch 'develop' into staging
This commit is contained in:
commit
e05e40f431
71 changed files with 810 additions and 86 deletions
5
Gemfile
5
Gemfile
|
@ -3,6 +3,9 @@ source 'https://rubygems.org'
|
|||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||
gem 'rails', '5.0.0.1'
|
||||
|
||||
gem 'actioncable', '5.0.0.1'
|
||||
gem 'redis'
|
||||
|
||||
# Use SCSS for stylesheets
|
||||
gem 'sass-rails', '~> 5.0'
|
||||
# Use Uglifier as compressor for JavaScript assets
|
||||
|
@ -116,6 +119,8 @@ group :development do
|
|||
# Access an IRB console on exception pages or by using <%= console %> in views
|
||||
|
||||
gem 'web-console'
|
||||
|
||||
gem 'rack-handlers'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
|
|
|
@ -417,6 +417,8 @@ GEM
|
|||
pry (~> 0.10)
|
||||
public_suffix (2.0.4)
|
||||
rack (2.0.1)
|
||||
rack-handlers (0.7.3)
|
||||
rack
|
||||
rack-oauth2 (1.4.0)
|
||||
activesupport (>= 2.3)
|
||||
attr_required (>= 0.0.5)
|
||||
|
@ -465,6 +467,7 @@ GEM
|
|||
nokogiri (~> 1.5)
|
||||
trollop (~> 2.1)
|
||||
rdoc (4.3.0)
|
||||
redis (3.3.0)
|
||||
ref (2.0.0)
|
||||
request_store (1.3.1)
|
||||
responders (2.3.0)
|
||||
|
@ -624,6 +627,7 @@ PLATFORMS
|
|||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
actioncable (= 5.0.0.1)
|
||||
active_model_serializers
|
||||
apipie-rails
|
||||
as_csv
|
||||
|
@ -665,9 +669,11 @@ DEPENDENCIES
|
|||
pg
|
||||
poltergeist
|
||||
pry-byebug
|
||||
rack-handlers
|
||||
railroady
|
||||
rails (= 5.0.0.1)
|
||||
rails-controller-testing
|
||||
redis
|
||||
rest-client
|
||||
rgeo-geojson
|
||||
rspec-rails (~> 3.0)
|
||||
|
|
13
app/assets/javascripts/cable.js
Normal file
13
app/assets/javascripts/cable.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Action Cable provides the framework to deal with WebSockets in Rails.
|
||||
// You can generate new channels where WebSocket features live using the rails generate channel command.
|
||||
//
|
||||
//= require action_cable
|
||||
//= require_self
|
||||
//= require_tree ./channels
|
||||
|
||||
(function() {
|
||||
this.App || (this.App = {});
|
||||
|
||||
App.cable = ActionCable.createConsumer();
|
||||
|
||||
}).call(this);
|
23
app/assets/javascripts/channels/notifications.js
Normal file
23
app/assets/javascripts/channels/notifications.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
App.messages = App.cable.subscriptions.create('NotificationsChannel', {
|
||||
received: function (data) {
|
||||
if (window.location.href.indexOf('backoffice') !== -1) {
|
||||
$("#notification_alert").html(data['message']);
|
||||
|
||||
slideIn_notification_alert();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function slideIn_notification_alert (){
|
||||
$("#notification_alert").animate({
|
||||
right: '20px'
|
||||
}, 250);
|
||||
|
||||
setTimeout(slideOut_notification_alert, 3500);
|
||||
}
|
||||
|
||||
function slideOut_notification_alert (){
|
||||
$("#notification_alert").animate({
|
||||
right: '-250px'
|
||||
}, 200);
|
||||
}
|
|
@ -1,5 +1,22 @@
|
|||
$(document).on('page:load', the_terms);
|
||||
$(document).ready(the_terms);
|
||||
$(document).on('page:load', pannel_switch);
|
||||
$(document).ready(pannel_switch);
|
||||
|
||||
function pannel_switch() {
|
||||
$('#switch-notifications').click(function () {
|
||||
$('#procedure_list').addClass('hidden');
|
||||
$('#notifications_list').removeClass('hidden');
|
||||
$(this).addClass('active');
|
||||
$('#switch-procedures').removeClass('active');
|
||||
})
|
||||
$('#switch-procedures').click(function () {
|
||||
$('#notifications_list').addClass('hidden');
|
||||
$('#procedure_list').removeClass('hidden');
|
||||
$(this).addClass('active');
|
||||
$('#switch-notifications').removeClass('active');
|
||||
})
|
||||
}
|
||||
|
||||
function the_terms() {
|
||||
var the_terms = $("#dossier_autorisation_donnees");
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
$(document).on('page:load', link_init);
|
||||
$(document).ready(link_init);
|
||||
|
||||
|
||||
function link_init() {
|
||||
$('#dossiers_list tr').on('click', function () {
|
||||
$(location).attr('href', $(this).data('dossier_url'))
|
||||
});
|
||||
}
|
||||
$('#dossiers_list tr').on('click', function () {
|
||||
$(location).attr('href', $(this).data('dossier_url'))
|
||||
});
|
||||
}
|
||||
|
|
|
@ -42,24 +42,22 @@ h5 span {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#procedure_list {
|
||||
#procedure_list, #notifications_list {
|
||||
margin-left: -10px;
|
||||
margin-top: 20px;
|
||||
a, a:hover {
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.procedure_list_element.active{
|
||||
.procedure_list_element.active, .notification.active {
|
||||
background-color: #668ABD;
|
||||
}
|
||||
|
||||
.procedure_list_element {
|
||||
.procedure_list_element, .notification {
|
||||
padding: 15px 40px 15px 20px;
|
||||
cursor: pointer;
|
||||
line-height: 1.8em;
|
||||
}
|
||||
.procedure_list_element:hover{
|
||||
.procedure_list_element:hover, .notification:hover {
|
||||
background-color: #668ABD;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -67,5 +65,5 @@ h5 span {
|
|||
|
||||
.split-hr-left {
|
||||
border-bottom: 1px solid #FFFFFF;
|
||||
margin: 20px 10px 0px 0;
|
||||
margin: 20px 10px 0px 10px;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#left-pannel {
|
||||
margin-top: 60px;
|
||||
padding: 0 0 0 10px;
|
||||
padding: 0;
|
||||
background-color: #003189;
|
||||
height: calc(100% - 60px);
|
||||
position: fixed;
|
||||
|
@ -51,6 +51,53 @@
|
|||
}
|
||||
}
|
||||
#menu-block {
|
||||
#switch-buttons {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
font-size: 16px;
|
||||
margin-top: 20px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 205px;
|
||||
border: 1px solid;
|
||||
padding: 0 0 0 10px;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
.active {
|
||||
background-color: #668ABD !important;
|
||||
cursor: default;
|
||||
}
|
||||
.separator {
|
||||
height: 26px;
|
||||
width: 1px;
|
||||
display: inline-block;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
#switch-procedures:hover, #switch-notifications:hover {
|
||||
background-color: #668AEA;
|
||||
}
|
||||
#switch-procedures {
|
||||
height: 28px;
|
||||
margin: 0 0 0 -10px;
|
||||
padding-left: 10px;
|
||||
width: 100px;
|
||||
display: inline-block;
|
||||
border-radius: 25px 0 0 25px;
|
||||
}
|
||||
#switch-notifications {
|
||||
width: 103px;
|
||||
display: inline-block;
|
||||
border-radius: 0 25px 25px 0;
|
||||
height: 28px;
|
||||
margin: 0 0 0 -5px;
|
||||
padding: 0 0 0 5px;
|
||||
}
|
||||
}
|
||||
.split-hr {
|
||||
border-bottom: 1px solid #FFFFFF;
|
||||
width: 200px;
|
||||
margin: 20px 0 20px 0;
|
||||
}
|
||||
}
|
||||
#infos-block {
|
||||
.split-hr {
|
||||
|
@ -63,18 +110,42 @@
|
|||
font-size: 25px;
|
||||
width: 200px;
|
||||
margin-top: 20px;
|
||||
width: 200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.tips {
|
||||
margin: 0 10px 0 5px;
|
||||
.fa {
|
||||
color: #FFFFFF;
|
||||
font-size: 40px;
|
||||
width: inherit;
|
||||
padding: 5px;
|
||||
#notifications_list {
|
||||
.notification {
|
||||
padding: 10px 10px 10px 20px;
|
||||
.dossier, .updated-at {
|
||||
display: inline-block;
|
||||
color: #CCCCCC;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.notice {
|
||||
font-size: 18px;
|
||||
display: initial;
|
||||
}
|
||||
.notifications {
|
||||
margin: 20px 10px 0 5px;
|
||||
.fa {
|
||||
font-size: 25px;
|
||||
width: 100%;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
.notification {
|
||||
margin: 10px 0 10px 10px;
|
||||
.type {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.updated-at {
|
||||
color: #CCCCCC;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.split-hr {
|
||||
width: 40px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
app/assets/stylesheets/notification_alert.scss
Normal file
12
app/assets/stylesheets/notification_alert.scss
Normal file
|
@ -0,0 +1,12 @@
|
|||
#notification_alert {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: -250px;
|
||||
|
||||
z-index: 1000;
|
||||
|
||||
width: 250px;
|
||||
height: 80px;
|
||||
|
||||
border: solid black 1px;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
#search-block{
|
||||
margin: 15px 10px 0 0;
|
||||
margin: 15px 10px 0 10px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
|
|
5
app/channels/application_cable/channel.rb
Normal file
5
app/channels/application_cable/channel.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
|
||||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
5
app/channels/application_cable/connection.rb
Normal file
5
app/channels/application_cable/connection.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
end
|
||||
end
|
5
app/channels/notifications_channel.rb
Normal file
5
app/channels/notifications_channel.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class NotificationsChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from 'notifications'
|
||||
end
|
||||
end
|
|
@ -19,6 +19,7 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
|
|||
|
||||
def show
|
||||
create_dossier_facade params[:id]
|
||||
|
||||
unless @facade.nil?
|
||||
@champs_private = @facade.champs_private
|
||||
|
||||
|
@ -27,6 +28,8 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
|
|||
acc
|
||||
end
|
||||
end
|
||||
|
||||
Notification.where(dossier_id: params[:id].to_i).update_all already_read: true
|
||||
end
|
||||
|
||||
def filter
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
class CommentairesController < ApplicationController
|
||||
def index
|
||||
@facade = DossierFacades.new(
|
||||
params[:dossier_id],
|
||||
(current_gestionnaire || current_user).email,
|
||||
params[:champs_id]
|
||||
params[:dossier_id],
|
||||
(current_gestionnaire || current_user).email,
|
||||
params[:champs_id]
|
||||
)
|
||||
render layout: false
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
|
@ -48,12 +48,15 @@ class CommentairesController < ApplicationController
|
|||
end
|
||||
|
||||
NotificationMailer.new_answer(@commentaire.dossier).deliver_now! if saved
|
||||
|
||||
redirect_to url_for(controller: 'backoffice/dossiers', action: :show, id: params['dossier_id'])
|
||||
elsif current_user.email != @commentaire.dossier.user.email
|
||||
invite = Invite.where(dossier: @commentaire.dossier, user: current_user).first
|
||||
redirect_to url_for(controller: 'users/dossiers/invites', action: :show, id: invite.id)
|
||||
else
|
||||
redirect_to url_for(controller: :recapitulatif, action: :show, dossier_id: params['dossier_id'])
|
||||
if current_user.email != @commentaire.dossier.user.email
|
||||
invite = Invite.where(dossier: @commentaire.dossier, user: current_user).first
|
||||
redirect_to url_for(controller: 'users/dossiers/invites', action: :show, id: invite.id)
|
||||
else
|
||||
redirect_to url_for(controller: :recapitulatif, action: :show, dossier_id: params['dossier_id'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
8
app/decorators/notification_decorator.rb
Normal file
8
app/decorators/notification_decorator.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
class NotificationDecorator < Draper::Decorator
|
||||
delegate_all
|
||||
|
||||
def index_display
|
||||
['champs', 'piece_justificative'].include?(type_notif) ? type = liste.join(" ") : type = liste.last
|
||||
{ dossier: "Dossier n°#{dossier.id}", date: updated_at.strftime('%d/%m %H:%M'), type: type }
|
||||
end
|
||||
end
|
|
@ -10,6 +10,10 @@ class DossierFacades
|
|||
@dossier.decorate
|
||||
end
|
||||
|
||||
def last_notifications
|
||||
@dossier.notifications.order("updated_at DESC").limit(5)
|
||||
end
|
||||
|
||||
def champs
|
||||
@dossier.ordered_champs
|
||||
end
|
||||
|
|
|
@ -26,8 +26,16 @@ class DossiersListFacades
|
|||
current_devise_profil.dossiers.where(state: :initiated, archived: false).count
|
||||
end
|
||||
|
||||
def new_dossier_number procedure_id
|
||||
current_devise_profil.dossiers.where(state: :initiated, archived: false, procedure_id: procedure_id).count
|
||||
end
|
||||
|
||||
def gestionnaire_procedures_name_and_id_list
|
||||
@current_devise_profil.procedures.order('libelle ASC').inject([]) { |acc, procedure| acc.push({id: procedure.id, libelle: procedure.libelle}) }
|
||||
@current_devise_profil.procedures.order('libelle ASC').inject([]) { |acc, procedure| acc.push({id: procedure.id, libelle: procedure.libelle, unread_notifications: @current_devise_profil.notifications_for(procedure)}) }
|
||||
end
|
||||
|
||||
def unread_notifications
|
||||
current_devise_profil.notifications
|
||||
end
|
||||
|
||||
def procedure_id
|
||||
|
@ -130,4 +138,4 @@ class DossiersListFacades
|
|||
@procedure.nil? ? backoffice_dossiers_path(liste: liste) : backoffice_dossiers_procedure_path(id: @procedure.id, liste: liste)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,6 @@ class InviteDossierFacades < DossierFacades
|
|||
|
||||
#TODO rechercher en fonction de la personne/email
|
||||
def initialize dossier_id, email
|
||||
@dossier = (Invite.where(email: email).find(dossier_id)).dossier
|
||||
@dossier = Invite.where(email: email, dossier_id: dossier_id).first!.dossier
|
||||
end
|
||||
end
|
2
app/jobs/application_job.rb
Normal file
2
app/jobs/application_job.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
class ApplicationJob < ActiveJob::Base
|
||||
end
|
3
app/models/application_record.rb
Normal file
3
app/models/application_record.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
end
|
|
@ -5,6 +5,8 @@ class Cerfa < ActiveRecord::Base
|
|||
mount_uploader :content, CerfaUploader
|
||||
validates :content, :file_size => {:maximum => 20.megabytes}
|
||||
|
||||
after_save :internal_notification, if: Proc.new { !dossier.nil? }
|
||||
|
||||
def empty?
|
||||
content.blank?
|
||||
end
|
||||
|
@ -18,4 +20,12 @@ class Cerfa < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def internal_notification
|
||||
unless dossier.state == 'draft'
|
||||
NotificationService.new('cerfa', self.dossier.id).notify
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,6 +5,8 @@ class Champ < ActiveRecord::Base
|
|||
|
||||
delegate :libelle, :type_champ, :order_place, :mandatory, :description, :drop_down_list, to: :type_de_champ
|
||||
|
||||
after_save :internal_notification, if: Proc.new { !dossier.nil? }
|
||||
|
||||
def mandatory?
|
||||
mandatory
|
||||
end
|
||||
|
@ -36,14 +38,22 @@ class Champ < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.regions
|
||||
JSON.parse(Carto::GeoAPI::Driver.regions).sort_by{|e| e['nom']}.inject([]){|acc, liste| acc.push(liste['nom']) }
|
||||
JSON.parse(Carto::GeoAPI::Driver.regions).sort_by { |e| e['nom'] }.inject([]) { |acc, liste| acc.push(liste['nom']) }
|
||||
end
|
||||
|
||||
def self.departements
|
||||
JSON.parse(Carto::GeoAPI::Driver.departements).inject([]){|acc, liste| acc.push(liste['code'] + ' - ' + liste['nom']) }.push('99 - Étranger')
|
||||
JSON.parse(Carto::GeoAPI::Driver.departements).inject([]) { |acc, liste| acc.push(liste['code'] + ' - ' + liste['nom']) }.push('99 - Étranger')
|
||||
end
|
||||
|
||||
def self.pays
|
||||
JSON.parse(Carto::GeoAPI::Driver.pays).inject([]){|acc, liste| acc.push(liste['nom']) }
|
||||
JSON.parse(Carto::GeoAPI::Driver.pays).inject([]) { |acc, liste| acc.push(liste['nom']) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def internal_notification
|
||||
unless dossier.state == 'draft'
|
||||
NotificationService.new('champs', self.dossier.id, self.libelle).notify
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,17 @@ class Commentaire < ActiveRecord::Base
|
|||
|
||||
belongs_to :piece_justificative
|
||||
|
||||
after_save :internal_notification
|
||||
|
||||
def header
|
||||
"#{email}, " + created_at.localtime.strftime('%d %b %Y %H:%M')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def internal_notification
|
||||
if email == dossier.user.email || dossier.invites_user.pluck(:email).to_a.include?(email)
|
||||
NotificationService.new('commentaire', self.dossier.id).notify
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,6 +27,7 @@ class Dossier < ActiveRecord::Base
|
|||
has_many :invites, dependent: :destroy
|
||||
has_many :invites_user, class_name: 'InviteUser', dependent: :destroy
|
||||
has_many :follows
|
||||
has_many :notifications, dependent: :destroy
|
||||
|
||||
belongs_to :procedure
|
||||
belongs_to :user
|
||||
|
@ -41,6 +42,7 @@ class Dossier < ActiveRecord::Base
|
|||
|
||||
after_save :build_default_champs, if: Proc.new { procedure_id_changed? }
|
||||
after_save :build_default_individual, if: Proc.new { procedure.for_individual? }
|
||||
after_save :internal_notification
|
||||
|
||||
validates :user, presence: true
|
||||
|
||||
|
@ -326,4 +328,12 @@ class Dossier < ActiveRecord::Base
|
|||
def invite_by_user? email
|
||||
(invites_user.pluck :email).include? email
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def internal_notification
|
||||
if state_changed? && state == 'submitted'
|
||||
NotificationService.new('submitted', self.id).notify
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,6 +63,22 @@ class Gestionnaire < ActiveRecord::Base
|
|||
PreferenceSmartListingPage.create(page: 1, procedure: nil, gestionnaire: self, liste: 'a_traiter')
|
||||
end
|
||||
|
||||
def notifications
|
||||
Notification.where(already_read: false, dossier_id: follows.pluck(:dossier_id) ).order("updated_at DESC")
|
||||
end
|
||||
|
||||
def notifications_for procedure
|
||||
procedure_ids = dossiers_follow.pluck(:procedure_id)
|
||||
|
||||
if procedure_ids.include?(procedure.id)
|
||||
return dossiers_follow.where(procedure_id: procedure.id)
|
||||
.inject(0) do |acc, dossier|
|
||||
acc += dossier.notifications.where(already_read: false).count
|
||||
end
|
||||
end
|
||||
0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_couple_table_attr? table, column
|
||||
|
|
19
app/models/notification.rb
Normal file
19
app/models/notification.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Notification < ActiveRecord::Base
|
||||
belongs_to :dossier
|
||||
|
||||
# after_save :broadcast_notification
|
||||
|
||||
enum type_notif: {
|
||||
commentaire: 'commentaire',
|
||||
cerfa: 'cerfa',
|
||||
piece_justificative: 'piece_justificative',
|
||||
champs: 'champs',
|
||||
submitted: 'submitted'
|
||||
}
|
||||
|
||||
# def broadcast_notification
|
||||
# ActionCable.server.broadcast 'notifications',
|
||||
# message: "Dossier n°#{self.dossier.id} : #{self.liste.last}",
|
||||
# dossier: {id: self.dossier.id}
|
||||
# end
|
||||
end
|
|
@ -13,6 +13,8 @@ class PieceJustificative < ActiveRecord::Base
|
|||
validates :content, :file_size => {:maximum => 20.megabytes}
|
||||
validates :content, presence: true, allow_blank: false, allow_nil: false
|
||||
|
||||
after_save :internal_notification, if: Proc.new { !dossier.nil? }
|
||||
|
||||
def empty?
|
||||
content.blank?
|
||||
end
|
||||
|
@ -43,4 +45,12 @@ class PieceJustificative < ActiveRecord::Base
|
|||
image/jpeg
|
||||
"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def internal_notification
|
||||
unless self.type_de_piece_justificative.nil? && dossier.state == 'draft'
|
||||
NotificationService.new('piece_justificative', self.dossier.id, self.libelle).notify
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,9 +19,9 @@ class ChampsService
|
|||
end
|
||||
end
|
||||
|
||||
champ.save
|
||||
champ.save if champ.changed?
|
||||
end
|
||||
|
||||
errors
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
42
app/services/notification_service.rb
Normal file
42
app/services/notification_service.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
class NotificationService
|
||||
|
||||
def initialize type_notif, dossier_id, attribut_change=''
|
||||
@type_notif = type_notif
|
||||
@dossier_id = dossier_id
|
||||
|
||||
notification.liste.push text_for_notif attribut_change
|
||||
notification.liste = notification.liste.uniq
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def notify
|
||||
notification.save
|
||||
end
|
||||
|
||||
def notification
|
||||
@notification ||=
|
||||
begin
|
||||
Notification.find_by! dossier_id: @dossier_id, already_read: false, type_notif: @type_notif
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
Notification.new dossier_id: @dossier_id, type_notif: @type_notif, liste: []
|
||||
end
|
||||
end
|
||||
|
||||
def text_for_notif attribut=''
|
||||
case @type_notif
|
||||
when 'commentaire'
|
||||
"#{notification.liste.size + 1} nouveau(x) commentaire(s) déposé(s)."
|
||||
when 'cerfa'
|
||||
"Un nouveau formulaire a été déposé."
|
||||
when 'piece_justificative'
|
||||
attribut
|
||||
when 'champs'
|
||||
attribut
|
||||
when 'submitted'
|
||||
"Le dossier n°#{@dossier_id} a été déposé."
|
||||
else
|
||||
'Notification par défaut'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -35,4 +35,4 @@ class SyncCredentialsService
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
%td
|
||||
= procedure.created_at_fr
|
||||
%td
|
||||
= link_to('Cloner', admin_procedure_clone_path(procedure.id), 'data-method' => :put, class: 'btn-sm btn-primary')
|
||||
= link_to('Cloner', admin_procedure_clone_path(procedure.id), 'data-method' => :put, class: 'btn-sm btn-primary clone-btn')
|
||||
- unless procedure.published? || procedure.archived?
|
||||
= link_to('X', url_for(controller: 'admin/procedures', action: :destroy, id: procedure.id), 'data-method' => :delete, class: 'btn-sm btn-danger')
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
%table#dossiers_list.table
|
||||
%thead
|
||||
- if smart_listing.name.to_s == 'follow_dossiers'
|
||||
%th
|
||||
%i.fa.fa-bell
|
||||
- @facade_data_view.preference_list_dossiers_filter.each do |preference|
|
||||
%th{class: "col-md-#{preference.bootstrap_lg} col-lg-#{preference.bootstrap_lg}"}
|
||||
- if preference.table.to_s.include? 'champs'
|
||||
|
@ -16,6 +19,15 @@
|
|||
- unless smart_listing.empty?
|
||||
- smart_listing.collection.each do |dossier|
|
||||
%tr.dossier-row{id: "tr_dossier_#{dossier.id}", 'data-dossier_url' => backoffice_dossier_url(id: dossier.id)}
|
||||
- if smart_listing.name.to_s == 'follow_dossiers'
|
||||
%td.center
|
||||
- total_notif = dossier.notifications.where(already_read: false).count
|
||||
- if total_notif == 0
|
||||
.badge.progress-bar-default
|
||||
= total_notif
|
||||
- else
|
||||
.badge.progress-bar-warning
|
||||
= total_notif
|
||||
- @facade_data_view.preference_list_dossiers_filter.each_with_index do |preference, index|
|
||||
%td
|
||||
- if preference.table.nil? || preference.table.empty?
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
Aucune personne invitée
|
||||
|
||||
.col-md-3.col-sm-3.col-xs-3.col-lg-3
|
||||
=form_tag invites_dossier_path(dossier_id: @facade.dossier.id), method: :post, class: 'form-inline' do
|
||||
=text_field_tag :email, '', class: 'form-control', placeholder: 'Envoyer une invitation'
|
||||
=submit_tag 'Ajouter', class: 'btn btn-success'
|
||||
= form_tag invites_dossier_path(dossier_id: @facade.dossier.id), method: :post, class: 'form-inline' do
|
||||
= text_field_tag :email, '', class: 'form-control', placeholder: 'Envoyer une invitation'
|
||||
= submit_tag 'Ajouter', class: 'btn btn-success', id: 'send-invitation'
|
||||
|
||||
|
|
1
app/views/layouts/_notifications_alert.html.haml
Normal file
1
app/views/layouts/_notifications_alert.html.haml
Normal file
|
@ -0,0 +1 @@
|
|||
#notification_alert.alert.alert-success
|
|
@ -9,6 +9,7 @@
|
|||
= javascript_include_tag 'application', 'data-turbolinks-track' => true
|
||||
|
||||
= csrf_meta_tags
|
||||
= action_cable_meta_tag
|
||||
%body
|
||||
= render partial: 'layouts/support_navigator_banner'
|
||||
#beta{class:(Rails.env == 'production' ? '' : 'beta_staging')}
|
||||
|
@ -49,6 +50,7 @@
|
|||
%i.fa.fa-times{style:'position: fixed; top: 10; right: 30; color: white;'}
|
||||
|
||||
= render partial: 'layouts/switch_devise_profile_module'
|
||||
= render partial: 'layouts/notifications_alert'
|
||||
|
||||
= render partial: 'layouts/footer', locals: {main_container_size: main_container_size}
|
||||
= render partial: 'layouts/google_analytics'
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
%div#action-block
|
||||
|
||||
%div#menu-block
|
||||
%div.split-hr-left
|
||||
#switch-buttons
|
||||
#switch-procedures.active Procédures
|
||||
#switch-notifications Notifications
|
||||
|
||||
%div#infos-block
|
||||
%div.split-hr-left
|
||||
|
@ -17,3 +21,17 @@
|
|||
= link_to backoffice_dossiers_procedure_path(procedure[:id]), {title: procedure[:libelle]} do
|
||||
%div.procedure_list_element{ class: ('active' if procedure[:id] == @facade_data_view.procedure.id rescue '') }
|
||||
= truncate(procedure[:libelle], length: 50)
|
||||
- total_new = @facade_data_view.new_dossier_number procedure[:id]
|
||||
- if total_new > 0
|
||||
.badge.progress-bar-success{title:'Nouveaux dossiers'}
|
||||
= total_new
|
||||
-if procedure[:unread_notifications] > 0
|
||||
.badge.progress-bar-warning{title: 'Notifications'}
|
||||
= procedure[:unread_notifications]
|
||||
#notifications_list.hidden
|
||||
- @facade_data_view.unread_notifications.each do |notification|
|
||||
= link_to backoffice_dossier_path(notification.dossier.id) do
|
||||
.notification
|
||||
.dossier= notification.decorate.index_display[:dossier]
|
||||
.updated-at= notification.decorate.index_display[:date]
|
||||
.type= notification.decorate.index_display[:type]
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
%div#first-block
|
||||
%div.infos
|
||||
%div.projet-name #{@facade.dossier.nom_projet.capitalize rescue nil}
|
||||
#dossier_id= t('dynamics.dossiers.numéro') + @facade.dossier.id.to_s
|
||||
|
||||
%div#action-block
|
||||
|
@ -30,6 +29,24 @@
|
|||
%div.split-hr-left
|
||||
%div.dossier-state= @facade.dossier.display_state
|
||||
%div.split-hr-left
|
||||
%div.tips.hidden
|
||||
%i.fa.fa-lightbulb-o
|
||||
%div.notice= "Ceci est un bloc destiné à contenir des informations sur ce que vous êtes censé pouvoir faire à ce stade de traitement du dossier."
|
||||
%div.notifications
|
||||
- if @facade.dossier.notifications.empty?
|
||||
= "Aucune notification pour le moment."
|
||||
- else
|
||||
%i.fa.fa-bell-o
|
||||
- @facade.last_notifications.each do |notification|
|
||||
.notification
|
||||
.updated-at= notification.updated_at.strftime('%d/%m/%Y %H:%M')
|
||||
- if ['champs'].include?(notification.type_notif)
|
||||
- if notification.liste.size > 1
|
||||
.type= "Plusieurs attributs ont été changés, dont: #{notification.liste.join(" ")}"
|
||||
- else
|
||||
.type= "Un attribut à été changé: #{notification.liste.last}"
|
||||
- elsif ['piece_justificative'].include?(notification.type_notif)
|
||||
- if notification.liste.size > 1
|
||||
.type= "Plusieurs pièces justificatives ont été changés, dont: #{notification.liste.join(" ")}"
|
||||
- else
|
||||
.type= "Une pièce justificative à été changée: #{notification.liste.last}"
|
||||
- else
|
||||
.type= notification.liste.last
|
||||
.split-hr
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
%div.en-cours
|
||||
%h2 Récapitulatif
|
||||
%div.infos
|
||||
%div #{@facade.dossier.nom_projet}
|
||||
%div= t('dynamics.dossiers.numéro') + @facade.dossier.id.to_s
|
||||
|
||||
%div#action-block
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
Suivre le dossier
|
||||
%div.row
|
||||
%div.col-lg-12.col-md-12.col-sm-12.col-xs-12
|
||||
%div.dropdown-toggle{ 'data-toggle' => 'dropdown', 'aria-haspopup' => true, 'aria-expanded' => false }
|
||||
%div#invitations.dropdown-toggle{ 'data-toggle' => 'dropdown', 'aria-haspopup' => true, 'aria-expanded' => false }
|
||||
%i.fa.fa-user
|
||||
= t('utils.involved')
|
||||
%div.dropdown-menu.dropdown-menu-right.dropdown-pannel
|
||||
|
@ -34,7 +34,6 @@
|
|||
= t('dynamics.dossiers.invites.empty')
|
||||
|
||||
%li
|
||||
=form_tag invites_dossier_path(dossier_id: @facade.dossier.id), method: :post, class: 'form-inline' do
|
||||
=text_field_tag :email, '', class: 'form-control', placeholder: 'Envoyer une invitation'
|
||||
=submit_tag 'Ajouter', class: 'btn btn-success'
|
||||
|
||||
= form_tag invites_dossier_path(dossier_id: @facade.dossier.id), method: :post, class: 'form-inline', id: 'send-invitation' do
|
||||
= text_field_tag :email, '', class: 'form-control', placeholder: 'Envoyer une invitation', id: 'invitation-email'
|
||||
= submit_tag 'Ajouter', class: 'btn btn-success'
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
%div.col-lg-3.col-md-3.col-sm-3.col-xs-3.options
|
||||
%div.row.centered-option
|
||||
%div.col-lg-12.col-md-12.col-sm-12.col-xs-12
|
||||
%div.dropdown-toggle{ 'data-toggle' => 'dropdown', 'aria-haspopup' => true, 'aria-expanded' => false }
|
||||
%div#invitations.dropdown-toggle{ 'data-toggle' => 'dropdown', 'aria-haspopup' => true, 'aria-expanded' => false }
|
||||
%i.fa.fa-user
|
||||
= t('utils.involved')
|
||||
%div.dropdown-menu.dropdown-menu-right.dropdown-pannel
|
||||
|
@ -24,6 +24,6 @@
|
|||
= t('dynamics.dossiers.invites.empty')
|
||||
|
||||
%li
|
||||
=form_tag invites_dossier_path(dossier_id: @facade.dossier.id), method: :post, class: 'form-inline' do
|
||||
=text_field_tag :email, '', class: 'form-control', placeholder: 'Envoyer une invitation'
|
||||
=submit_tag 'Ajouter', class: 'btn btn-success'
|
||||
= form_tag invites_dossier_path(dossier_id: @facade.dossier.id), method: :post, class: 'form-inline', id: 'send-invitation' do
|
||||
= text_field_tag :email, '', class: 'form-control', placeholder: 'Envoyer une invitation', id: 'invitation-email'
|
||||
= submit_tag 'Ajouter', class: 'btn btn-success'
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
# This file is used by Rack-based servers to start the application.
|
||||
|
||||
require ::File.expand_path('../config/environment', __FILE__)
|
||||
|
||||
# Action Cable requires that all classes are loaded in advance
|
||||
Rails.application.eager_load!
|
||||
|
||||
run Rails.application
|
||||
|
|
10
config/cable.yml
Normal file
10
config/cable.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
production:
|
||||
adapter: redis
|
||||
url: redis://localhost:6379
|
||||
|
||||
development:
|
||||
adapter: redis
|
||||
url: redis://localhost:6379
|
||||
|
||||
test:
|
||||
adapter: async
|
|
@ -45,5 +45,5 @@ Rails.application.configure do
|
|||
# Raises error for missing translations
|
||||
# config.action_view.raise_on_missing_translations = true
|
||||
|
||||
|
||||
config.action_cable.url = "ws://localhost:3000/cable"
|
||||
end
|
||||
|
|
|
@ -26,10 +26,6 @@ fr:
|
|||
mail_contact:
|
||||
blank: 'doit être rempli'
|
||||
invalid: 'est incorrect'
|
||||
nom_projet:
|
||||
blank: 'doit être rempli'
|
||||
description:
|
||||
blank: 'doit être remplie'
|
||||
montant_projet:
|
||||
blank: 'doit être rempli'
|
||||
montant_aide_demande:
|
||||
|
|
|
@ -202,4 +202,6 @@ Rails.application.routes.draw do
|
|||
end
|
||||
|
||||
apipie
|
||||
|
||||
mount ActionCable.server => '/cable'
|
||||
end
|
||||
|
|
109
config/unicorn.rb
Normal file
109
config/unicorn.rb
Normal file
|
@ -0,0 +1,109 @@
|
|||
# Sample verbose configuration file for Unicorn (not Rack)
|
||||
#
|
||||
# This configuration file documents many features of Unicorn
|
||||
# that may not be needed for some applications. See
|
||||
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
|
||||
# for a much simpler configuration file.
|
||||
#
|
||||
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
|
||||
# documentation.
|
||||
|
||||
|
||||
|
||||
# Use at least one worker per core if you're on a dedicated server,
|
||||
# more will usually help for _short_ waits on databases/caches.
|
||||
worker_processes 2
|
||||
|
||||
# Since Unicorn is never exposed to outside clients, it does not need to
|
||||
# run on the standard HTTP port (80), there is no reason to start Unicorn
|
||||
# as root unless it's from system init scripts.
|
||||
# If running the master process as root and the workers as an unprivileged
|
||||
# user, do this to switch euid/egid in the workers (also chowns logs):
|
||||
# user "unprivileged_user", "unprivileged_group"
|
||||
|
||||
# Help ensure your application will always spawn in the symlinked
|
||||
# "current" directory that Capistrano sets up.
|
||||
|
||||
# listen on both a Unix domain socket and a TCP port,
|
||||
# we use a shorter backlog for quicker failover when busy
|
||||
listen "127.0.0.1:3000", :tcp_nopush => true
|
||||
|
||||
# nuke workers after 30 seconds instead of 60 seconds (the default)
|
||||
timeout 30
|
||||
|
||||
# By default, the Unicorn logger will write to stderr.
|
||||
# Additionally, ome applications/frameworks log to stderr or stdout,
|
||||
# so prevent them from going to /dev/null when daemonized here:
|
||||
|
||||
# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
|
||||
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
|
||||
preload_app true
|
||||
GC.respond_to?(:copy_on_write_friendly=) and
|
||||
GC.copy_on_write_friendly = true
|
||||
|
||||
# Enable this flag to have unicorn test client connections by writing the
|
||||
# beginning of the HTTP headers before calling the application. This
|
||||
# prevents calling the application for connections that have disconnected
|
||||
# while queued. This is only guaranteed to detect clients on the same
|
||||
# host unicorn runs on, and unlikely to detect disconnects even on a
|
||||
# fast LAN.
|
||||
check_client_connection false
|
||||
|
||||
# local variable to guard against running a hook multiple times
|
||||
run_once = true
|
||||
|
||||
|
||||
before_fork do |server, worker|
|
||||
# the following is highly recomended for Rails + "preload_app true"
|
||||
# as there's no need for the master process to hold a connection
|
||||
defined?(ActiveRecord::Base) and
|
||||
ActiveRecord::Base.connection.disconnect!
|
||||
|
||||
# Occasionally, it may be necessary to run non-idempotent code in the
|
||||
# master before forking. Keep in mind the above disconnect! example
|
||||
# is idempotent and does not need a guard.
|
||||
if run_once
|
||||
# do_something_once_here ...
|
||||
run_once = false # prevent from firing again
|
||||
end
|
||||
|
||||
# The following is only recommended for memory/DB-constrained
|
||||
# installations. It is not needed if your system can house
|
||||
# twice as many worker_processes as you have configured.
|
||||
#
|
||||
# # This allows a new master process to incrementally
|
||||
# # phase out the old master process with SIGTTOU to avoid a
|
||||
# # thundering herd (especially in the "preload_app false" case)
|
||||
# # when doing a transparent upgrade. The last worker spawned
|
||||
# # will then kill off the old master process with a SIGQUIT.
|
||||
old_pid = "#{server.config[:pid]}.oldbin"
|
||||
if old_pid != server.pid
|
||||
begin
|
||||
sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
|
||||
Process.kill(sig, File.read(old_pid).to_i)
|
||||
rescue Errno::ENOENT, Errno::ESRCH
|
||||
end
|
||||
end
|
||||
#
|
||||
# Throttle the master from forking too quickly by sleeping. Due
|
||||
# to the implementation of standard Unix signal handlers, this
|
||||
# helps (but does not completely) prevent identical, repeated signals
|
||||
# from being lost when the receiving process is busy.
|
||||
sleep 1
|
||||
end
|
||||
|
||||
after_fork do |server, worker|
|
||||
# per-process listener ports for debugging/admin/migrations
|
||||
# addr = "127.0.0.1:#{9293 + worker.nr}"
|
||||
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
|
||||
|
||||
# the following is *required* for Rails + "preload_app true",
|
||||
defined?(ActiveRecord::Base) and
|
||||
ActiveRecord::Base.establish_connection
|
||||
|
||||
# if preload_app is true, then you may also want to check and
|
||||
# restart any other shared sockets/descriptors such as Memcached,
|
||||
# and Redis. TokyoCabinet file handles are safe to reuse
|
||||
# between any number of forked children (assuming your kernel
|
||||
# correctly implements pread()/pwrite() system calls)
|
||||
end
|
16
db/migrate/20161221153929_create_notification.rb
Normal file
16
db/migrate/20161221153929_create_notification.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class CreateNotification < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_table :notifications do |t|
|
||||
|
||||
t.boolean :already_read, default: false
|
||||
t.string :liste, array: true
|
||||
t.boolean :multiple, default: false
|
||||
t.string :type_notif
|
||||
t.datetime :created_at
|
||||
t.datetime :updated_at
|
||||
|
||||
end
|
||||
|
||||
add_belongs_to :notifications, :dossier
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
class DeleteOldAttrInDataBase < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
remove_column :dossiers, :nom_projet
|
||||
remove_column :procedures, :test
|
||||
remove_column :notifications, :multiple
|
||||
end
|
||||
end
|
14
db/schema.rb
14
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20161205110427) do
|
||||
ActiveRecord::Schema.define(version: 20161227103823) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -118,7 +118,6 @@ ActiveRecord::Schema.define(version: 20161205110427) do
|
|||
|
||||
create_table "dossiers", force: :cascade do |t|
|
||||
t.boolean "autorisation_donnees"
|
||||
t.string "nom_projet"
|
||||
t.integer "procedure_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
|
@ -249,6 +248,16 @@ ActiveRecord::Schema.define(version: 20161205110427) do
|
|||
t.index ["procedure_id"], name: "index_module_api_cartos_on_procedure_id", unique: true, using: :btree
|
||||
end
|
||||
|
||||
create_table "notifications", force: :cascade do |t|
|
||||
t.boolean "already_read", default: false
|
||||
t.string "liste", array: true
|
||||
t.string "type_notif"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "dossier_id"
|
||||
t.index ["dossier_id"], name: "index_notifications_on_dossier_id", using: :btree
|
||||
end
|
||||
|
||||
create_table "pieces_justificatives", force: :cascade do |t|
|
||||
t.string "content"
|
||||
t.integer "dossier_id"
|
||||
|
@ -302,7 +311,6 @@ ActiveRecord::Schema.define(version: 20161205110427) do
|
|||
t.string "lien_demarche"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "test"
|
||||
t.integer "administrateur_id"
|
||||
t.boolean "archived", default: false
|
||||
t.boolean "euro_flag", default: false
|
||||
|
|
|
@ -36,6 +36,10 @@ describe Backoffice::CommentairesController, type: :controller do
|
|||
expect { subject }.to change(Follow, :count).by(0)
|
||||
end
|
||||
end
|
||||
|
||||
it 'Internal notification is not create' do
|
||||
expect { subject }.to change(Notification, :count).by (0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when document is upload whith a commentaire', vcr: {cassette_name: 'controllers_backoffice_commentaires_controller_doc_upload_with_comment'} do
|
||||
|
@ -54,6 +58,10 @@ describe Backoffice::CommentairesController, type: :controller do
|
|||
subject
|
||||
end
|
||||
|
||||
it 'Internal notification is not create' do
|
||||
expect { subject }.to change(Notification, :count).by (0)
|
||||
end
|
||||
|
||||
describe 'piece justificative created' do
|
||||
let(:pj) { PieceJustificative.last }
|
||||
|
||||
|
|
|
@ -38,34 +38,40 @@ describe Backoffice::DossiersController, type: :controller do
|
|||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
subject { get :show, params: {id: dossier_id} }
|
||||
|
||||
context 'gestionnaire is connected' do
|
||||
before do
|
||||
sign_in gestionnaire
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get :show, params: {id: dossier_id}
|
||||
expect(response).to have_http_status(200)
|
||||
expect(subject).to have_http_status(200)
|
||||
end
|
||||
|
||||
describe 'all notifications unread are changed' do
|
||||
it do
|
||||
expect(Notification).to receive(:where).with(dossier_id: dossier_id).and_return(Notification::ActiveRecord_Relation)
|
||||
expect(Notification::ActiveRecord_Relation).to receive(:update_all).with(already_read: true).and_return(true)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context ' when dossier is archived' do
|
||||
before do
|
||||
get :show, params: {id: dossier_archived.id}
|
||||
end
|
||||
it { expect(response).to redirect_to('/backoffice') }
|
||||
let(:dossier_id) { dossier_archived }
|
||||
|
||||
it { expect(subject).to redirect_to('/backoffice') }
|
||||
end
|
||||
|
||||
context 'when dossier id does not exist' do
|
||||
before do
|
||||
get :show, params: {id: bad_dossier_id}
|
||||
end
|
||||
it { expect(response).to redirect_to('/backoffice') }
|
||||
let(:dossier_id) { bad_dossier_id }
|
||||
|
||||
it { expect(subject).to redirect_to('/backoffice') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'gestionnaire does not connected but dossier id is correct' do
|
||||
subject { get :show, params: {id: dossier_id} }
|
||||
|
||||
it { is_expected.to redirect_to('/gestionnaires/sign_in') }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,6 +28,10 @@ describe Users::CommentairesController, type: :controller do
|
|||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'Notification interne is create' do
|
||||
expect { subject }.to change(Notification, :count).by (1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when document is upload whith a commentaire', vcr: {cassette_name: 'controllers_sers_commentaires_controller_upload_doc'} do
|
||||
|
|
|
@ -145,6 +145,13 @@ shared_examples 'description_controller_spec' do
|
|||
end
|
||||
|
||||
context 'Quand la procédure accepte les CERFA' do
|
||||
subject { post :create, params: {dossier_id: dossier_id,
|
||||
cerfa_pdf: cerfa_pdf} }
|
||||
|
||||
it 'Notification interne is create' do
|
||||
expect { subject }.to change(Notification, :count).by (1)
|
||||
end
|
||||
|
||||
context 'Sauvegarde du CERFA PDF', vcr: {cassette_name: 'controllers_users_description_controller_save_cerfa'} do
|
||||
before do
|
||||
post :create, params: {dossier_id: dossier_id,
|
||||
|
@ -292,6 +299,10 @@ shared_examples 'description_controller_spec' do
|
|||
sign_in guest
|
||||
end
|
||||
|
||||
it 'Notification interne is create' do
|
||||
expect { subject }.to change(Notification, :count).by (1)
|
||||
end
|
||||
|
||||
context 'when PJ have no documents' do
|
||||
it { expect(dossier.pieces_justificatives.size).to eq 0 }
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ describe Users::DescriptionController, type: :controller, vcr: {cassette_name: '
|
|||
let(:invite_by_user) { create :user, email: 'invite@plop.com' }
|
||||
|
||||
let(:procedure) { create(:procedure, :with_two_type_de_piece_justificative, :with_type_de_champ, :with_datetime, cerfa_flag: true) }
|
||||
let(:dossier) { create(:dossier, procedure: procedure, user: owner_user) }
|
||||
let(:dossier) { create(:dossier, procedure: procedure, user: owner_user, state: 'initiated') }
|
||||
|
||||
let(:dossier_id) { dossier.id }
|
||||
let(:bad_dossier_id) { Dossier.count + 10000 }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
RSpec.describe Users::Dossiers::InvitesController, type: :controller do
|
||||
describe Users::Dossiers::InvitesController, type: :controller do
|
||||
describe '#authenticate_user!' do
|
||||
let(:user) { create :user }
|
||||
let(:invite) { create :invite }
|
||||
|
|
|
@ -82,6 +82,10 @@ describe Users::RecapitulatifController, type: :controller do
|
|||
dossier.validated!
|
||||
post :submit, params: {dossier_id: dossier.id}
|
||||
end
|
||||
|
||||
it 'Internal notification is created' do
|
||||
expect(Notification.where(dossier_id: dossier.id, type_notif: 'submitted').first).not_to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,10 +50,12 @@ describe DossiersListFacades do
|
|||
|
||||
it { expect(subject.first[:id]).to eq procedure.id }
|
||||
it { expect(subject.first[:libelle]).to eq procedure.libelle }
|
||||
it { expect(subject.first[:unread_notifications]).to eq 0 }
|
||||
|
||||
|
||||
it { expect(subject.last[:id]).to eq procedure_2.id }
|
||||
it { expect(subject.last[:libelle]).to eq procedure_2.libelle }
|
||||
|
||||
it { expect(subject.last[:unread_notifications]).to eq 0 }
|
||||
end
|
||||
|
||||
describe '#active_filter?' do
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
FactoryGirl.define do
|
||||
factory :commentaire do
|
||||
body 'plop'
|
||||
|
||||
before(:create) do |commentaire, _evaluator|
|
||||
unless commentaire.dossier
|
||||
commentaire.dossier = create :dossier
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
FactoryGirl.define do
|
||||
sequence(:gestionnaire_email) { |n| "plop#{n}@plop.com" }
|
||||
sequence(:gestionnaire_email) { |n| "gest#{n}@plop.com" }
|
||||
factory :gestionnaire do
|
||||
email { generate(:gestionnaire_email) }
|
||||
password 'password'
|
||||
|
|
12
spec/factories/notification.rb
Normal file
12
spec/factories/notification.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
FactoryGirl.define do
|
||||
factory :notification do
|
||||
type_notif 'commentaire'
|
||||
liste []
|
||||
|
||||
before(:create) do |notification, _evaluator|
|
||||
unless notification.dossier
|
||||
notification.dossier = create :dossier
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
29
spec/features/admin/procedure_cloning_spec.rb
Normal file
29
spec/features/admin/procedure_cloning_spec.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'As an administrateur I wanna clone a procedure', js: true do
|
||||
|
||||
let(:administrateur) { create(:administrateur) }
|
||||
|
||||
before do
|
||||
login_as administrateur, scope: :administrateur
|
||||
visit root_path
|
||||
end
|
||||
|
||||
context 'Cloning procedure' do
|
||||
|
||||
before 'Create procedure' do
|
||||
page.find_by_id('new-procedure').click
|
||||
fill_in 'procedure_libelle', with: 'libelle de la procedure'
|
||||
page.execute_script("$('#procedure_description').data('wysihtml5').editor.setValue('description de la procedure')")
|
||||
page.find_by_id('save-procedure').click
|
||||
end
|
||||
|
||||
scenario 'Cloning' do
|
||||
visit admin_procedures_draft_path
|
||||
expect(page.find_by_id('procedures')['data-item-count']).to eq('1')
|
||||
page.all('.clone-btn').first.click
|
||||
visit admin_procedures_draft_path
|
||||
expect(page.find_by_id('procedures')['data-item-count']).to eq('2')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'as an administrateur I wanna create a new procedure', js: true do
|
||||
feature 'As an administrateur I wanna create a new procedure', js: true do
|
||||
|
||||
let(:administrateur) { create(:administrateur) }
|
||||
|
||||
|
|
25
spec/features/backoffice/invitation_spec.rb
Normal file
25
spec/features/backoffice/invitation_spec.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'As an Accompagnateur I can send invitations from dossiers', js: true do
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:gestionnaire) { create(:gestionnaire) }
|
||||
let(:procedure_1) { create(:procedure, :with_type_de_champ, libelle: 'procedure 1') }
|
||||
|
||||
before 'Assign procedures to Accompagnateur and generating dossiers for each' do
|
||||
create :assign_to, gestionnaire: gestionnaire, procedure: procedure_1
|
||||
Dossier.create(procedure_id: procedure_1.id.to_s, user: user, state: 'initiated')
|
||||
login_as gestionnaire, scope: :gestionnaire
|
||||
visit backoffice_dossier_path(1)
|
||||
end
|
||||
|
||||
context 'On dossier show' do
|
||||
|
||||
scenario 'Sending invitation' do
|
||||
page.find('#invitations').click
|
||||
page.find('#invitation-email').set('toto@email.com')
|
||||
page.find('#send-invitation .btn-success').trigger('click')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
24
spec/features/users/invitation_spec.rb
Normal file
24
spec/features/users/invitation_spec.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'As a User I can send invitations from dossiers', js: true do
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:procedure_1) { create(:procedure, :with_type_de_champ, libelle: 'procedure 1') }
|
||||
|
||||
before 'Assign procedures to Accompagnateur and generating dossiers for each' do
|
||||
Dossier.create(procedure_id: procedure_1.id.to_s, user: user, state: 'initiated')
|
||||
login_as user, scope: :user
|
||||
visit users_dossier_recapitulatif_path(1)
|
||||
end
|
||||
|
||||
context 'On dossier show' do
|
||||
|
||||
scenario 'Sending invitation' do
|
||||
page.find('#invitations').click
|
||||
fill_in 'invitation-email', with: 'toto@email.com'
|
||||
page.find('#send-invitation .btn-success').trigger('click')
|
||||
save_and_open_page
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -5,7 +5,6 @@ describe Dossier do
|
|||
|
||||
describe 'database columns' do
|
||||
it { is_expected.to have_db_column(:autorisation_donnees) }
|
||||
it { is_expected.to have_db_column(:nom_projet) }
|
||||
it { is_expected.to have_db_column(:created_at) }
|
||||
it { is_expected.to have_db_column(:updated_at) }
|
||||
it { is_expected.to have_db_column(:state) }
|
||||
|
@ -27,6 +26,7 @@ describe Dossier do
|
|||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to have_many(:invites) }
|
||||
it { is_expected.to have_many(:follows) }
|
||||
it { is_expected.to have_many(:notifications) }
|
||||
end
|
||||
|
||||
describe 'delegation' do
|
||||
|
|
|
@ -208,4 +208,40 @@ describe Gestionnaire, type: :model do
|
|||
expect(admin.valid_password?('super secret')).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notifications_for' do
|
||||
subject { gestionnaire.notifications_for procedure }
|
||||
|
||||
context 'when gestionnaire follow any dossier' do
|
||||
it { is_expected.to eq 0 }
|
||||
it { expect(gestionnaire.follows.count).to eq 0 }
|
||||
it { expect_any_instance_of(Dossier::ActiveRecord_AssociationRelation).not_to receive(:inject)
|
||||
subject }
|
||||
end
|
||||
|
||||
context 'when gestionnaire follow any dossier into the procedure past in params' do
|
||||
before do
|
||||
create :follow, gestionnaire: gestionnaire, dossier: create(:dossier, procedure: procedure_2)
|
||||
end
|
||||
|
||||
it { is_expected.to eq 0 }
|
||||
it { expect(gestionnaire.follows.count).to eq 1 }
|
||||
it { expect_any_instance_of(Dossier::ActiveRecord_AssociationRelation).not_to receive(:inject)
|
||||
subject }
|
||||
end
|
||||
|
||||
context 'when gestionnaire follow a dossier with a notification into the procedure past in params' do
|
||||
let(:dossier) { create(:dossier, procedure: procedure, state: 'initiated') }
|
||||
|
||||
before do
|
||||
create :follow, gestionnaire: gestionnaire, dossier: dossier
|
||||
create :notification, dossier: dossier
|
||||
end
|
||||
|
||||
it { is_expected.to eq 1 }
|
||||
it { expect(gestionnaire.follows.count).to eq 1 }
|
||||
it { expect_any_instance_of(Dossier::ActiveRecord_AssociationRelation).to receive(:inject)
|
||||
subject }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
11
spec/models/notification_spec.rb
Normal file
11
spec/models/notification_spec.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Notification do
|
||||
it { is_expected.to have_db_column(:already_read) }
|
||||
it { is_expected.to have_db_column(:liste) }
|
||||
it { is_expected.to have_db_column(:type_notif) }
|
||||
it { is_expected.to have_db_column(:created_at) }
|
||||
it { is_expected.to have_db_column(:updated_at) }
|
||||
|
||||
it { is_expected.to belong_to(:dossier) }
|
||||
end
|
29
spec/services/notification_service_spec.rb
Normal file
29
spec/services/notification_service_spec.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe NotificationService do
|
||||
|
||||
describe '.notify' do
|
||||
let(:dossier) { create :dossier }
|
||||
let(:service) { described_class.new type_notif, dossier.id }
|
||||
|
||||
subject { service.notify }
|
||||
|
||||
context 'when is the first notification for dossier_id and type_notif and alread_read is false' do
|
||||
let(:type_notif) { 'commentaire' }
|
||||
|
||||
it { expect { subject }.to change(Notification, :count).by (1) }
|
||||
|
||||
context 'when is not the first notification' do
|
||||
before do
|
||||
create :notification, dossier: dossier, type_notif: type_notif
|
||||
end
|
||||
|
||||
it { expect { subject }.to change(Notification, :count).by (0) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'text_for_notif' do
|
||||
pending
|
||||
end
|
||||
end
|
|
@ -41,7 +41,7 @@ Capybara.register_driver :poltergeist do |app|
|
|||
Capybara::Poltergeist::Driver.new(app, js_errors: true, port: 44_678 + ENV['TEST_ENV_NUMBER'].to_i, phantomjs_options: ['--proxy-type=none'], timeout: 180)
|
||||
end
|
||||
|
||||
#ActiveSupport::Deprecation.silenced = true
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
Capybara.default_max_wait_time = 1
|
||||
|
||||
|
|
|
@ -29,6 +29,6 @@ describe 'admin/gestionnaires/index.html.haml', type: :view do
|
|||
array: true))
|
||||
render
|
||||
end
|
||||
it { expect(rendered).to match(/plop\d+@plop.com/) }
|
||||
it { expect(rendered).to match(/gest\d+@plop.com/) }
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue