Gestionnaire can invite an email contact to participate at a dossier

This commit is contained in:
Xavier J 2016-02-08 18:16:18 +01:00
parent d7dbd28507
commit e2a896d0b0
33 changed files with 409 additions and 39 deletions

View file

@ -4,5 +4,4 @@ class Backoffice::CommentairesController < CommentairesController
def is_gestionnaire?
true
end
end

View file

@ -6,9 +6,9 @@ class CommentairesController < ApplicationController
if is_gestionnaire?
@commentaire.email = current_gestionnaire.email
@commentaire.dossier.next_step! 'gestionnaire', 'comment'
else #is_user
else
@commentaire.email = current_user.email
@commentaire.dossier.next_step! 'user', 'comment'
@commentaire.dossier.next_step! 'user', 'comment' if current_user.email == @commentaire.dossier.user.email
end
@commentaire.body = params['texte_commentaire']
@ -17,6 +17,9 @@ class CommentairesController < ApplicationController
if is_gestionnaire?
NotificationMailer.new_answer(@commentaire.dossier).deliver_now!
redirect_to url_for(controller: 'backoffice/dossiers', action: :show, id: params['dossier_id'])
elsif current_user.email != @commentaire.dossier.user.email
invite = Invite.find_by_email current_user.email
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

View file

@ -0,0 +1,23 @@
class InvitesController < ApplicationController
def create
email_sender = current_gestionnaire.email
user = User.find_by_email(params[:email])
invite = Invite.create(dossier_id: params[:dossier_id], user: user, email: params[:email], email_sender: email_sender)
if invite.valid?
InviteMailer.invite_user(invite).deliver_now! unless invite.user.nil?
InviteMailer.invite_guest(invite).deliver_now! if invite.user.nil?
flash.notice = "Invitation envoyée (#{invite.email})"
else
flash.alert = invite.errors.full_messages.join('<br />').html_safe
end
if gestionnaire_signed_in?
redirect_to url_for(controller: 'backoffice/dossiers', action: :show, id: params['dossier_id'])
# else
# redirect_to url_for(controller: :recapitulatif, action: :show, dossier_id: params['dossier_id'])
end
end
end

View file

@ -0,0 +1,3 @@
class Users::Dossiers::CommentairesController < CommentairesController
before_action :authenticate_user!
end

View file

@ -0,0 +1,10 @@
class Users::Dossiers::InvitesController < UsersController
def show
@facade = InviteDossierFacades.new params[:id], current_user.email
render 'users/recapitulatif/show'
rescue ActiveRecord::RecordNotFound
flash.alert = t('errors.messages.dossier_not_found')
redirect_to url_for users_dossiers_path
end
end

View file

@ -37,4 +37,8 @@ class DossierFacades
def procedure
@dossier.procedure
end
def invites
@dossier.invites
end
end

View file

@ -0,0 +1,9 @@
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
@email = email
end
end

View file

@ -0,0 +1,25 @@
class InviteMailer < ApplicationMailer
def invite_user invite
vars_mailer invite
send_mail invite.email, "TPS - Participez à l'élaboration d'un dossier" unless invite.user.nil?
end
def invite_guest invite
vars_mailer invite
send_mail invite.email, "Invitation - #{invite.email_sender} vous invite à consulter un dossier sur la plateforme TPS"
end
private
def vars_mailer invite
@invite = invite
end
def send_mail email, subject
mail(from: "tps@apientreprise.fr", to: email,
subject: subject)
end
end

View file

@ -16,6 +16,7 @@ class Dossier < ActiveRecord::Base
has_many :quartier_prioritaires, dependent: :destroy
has_many :cadastres, dependent: :destroy
has_many :commentaires, dependent: :destroy
has_many :invites, dependent: :destroy
belongs_to :procedure
belongs_to :user

10
app/models/invite.rb Normal file
View file

@ -0,0 +1,10 @@
class Invite < ActiveRecord::Base
belongs_to :dossier
belongs_to :user
validates_presence_of :email
validates_uniqueness_of :email, :scope => :dossier_id
validates :email, email_format: true
end

View file

@ -8,6 +8,7 @@ class User < ActiveRecord::Base
:recoverable, :rememberable, :trackable, :validatable
has_many :dossiers, dependent: :destroy
has_many :invites, dependent: :destroy
has_one :france_connect_information, dependent: :destroy
delegate :given_name, :family_name, :email_france_connect, :gender, :birthdate, :birthplace, :france_connect_particulier_id, to: :france_connect_information

View file

@ -0,0 +1,11 @@
class EmailFormatValidator < ActiveModel::Validator
def email_regex
/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
end
def validate(record)
return if record.email.blank?
record.errors[:base] << "Email invalide" unless email_regex.match(record.email)
end
end

View file

@ -2,20 +2,31 @@
%h1#dossier_id.text-info{ :style => 'text-align:right'}
= "Dossier n°#{@facade.dossier.id}"
%h3{:class => 'text-success', :style => 'text-align:right'}
= @facade.dossier.display_state
%div{:style => 'text-align:right'}
%h3{:class => 'text-success'}
= @facade.dossier.display_state
= render partial: '/dossiers/infos_entreprise'
%br
= render partial: '/dossiers/infos_dossier'
%br
%br
= render partial: '/users/recapitulatif/commentaires_flux'
%br
%br
%div
%ul{class: "nav nav-tabs", role: "tablist"}
%li{role: "presentation", class: "active"}
%a{href: "#commentaires", 'aria-controls' => "commentaires", role: "tab", 'data-toggle' => "tab"}
Commentaires
%li{role: "presentation"}
%a{href: "#invites", 'aria-controls' => "invites", role: "tab", 'data-toggle' => "tab"}
Invités
-#%script{type: 'text/javascript'}
-# ="url_carte = '#{@facade.dossier.id}/'"
-# ="ref_dossier = '#{@facade.dossier.ref_dossier_carto}'"
%div{class: "tab-content"}
%div{role: "tabpanel", class: "tab-pane fade in active", id:"commentaires"}
%h3 Flux de commentaires
%br
= render partial: '/users/recapitulatif/commentaires_flux'
%div{role: "tabpanel", class: "tab-pane fade", id:"invites"}
= render partial: '/dossiers/invites'
%br
%br

View file

@ -1,4 +1,3 @@
#infos_dossier
%div.row
.col-lg-6.col-md-6
@ -51,8 +50,8 @@
=render partial: '/dossiers/pieces_justificatives'
%br
%div.row{style: 'text-align:right'}
-unless gestionnaire_signed_in?
.row{style: 'text-align:right'}
-if user_signed_in? && (current_user.email == @facade.dossier.user.email)
-if !@facade.dossier.validated? && !@facade.dossier.submitted? && !@facade.dossier.closed?
-if @facade.dossier.procedure.module_api_carto.use_api_carto
%a#maj_carte.btn.btn-primary{href: "/users/dossiers/#{@facade.dossier.id}/carte"}
@ -60,7 +59,7 @@
%a#maj_infos.btn.btn-info{href: "/users/dossiers/#{@facade.dossier.id}/description"}
= 'Editer mon dossier'
-unless user_signed_in?
-if gestionnaire_signed_in?
-if !@facade.dossier.validated? && !@facade.dossier.submitted? && !@facade.dossier.closed?
= form_tag(url_for({controller: 'backoffice/dossiers', action: :valid, dossier_id: @facade.dossier.id}), class: 'form-inline', method: 'POST') do
%button#action_button.btn.btn-success

View file

@ -0,0 +1,18 @@
%h3 Personnes invitées à voir ce dossier
%br
.row
.col-md-4.col-lg-4
- if @facade.invites.size > 0
%ul
- @facade.invites.each do |invite|
%li
= invite.email
- else
Aucune personne invité
.col-md-3.col-lg-3
=form_tag backoffice_dossier_invites_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'

View file

@ -0,0 +1,12 @@
Bonjour <%= @invite.email %>
L'utilisateur <%= @invite.email_sender %> souhaite que vous participiez à l'élaboration d'un dossier sur la plateforme TPS.
Cette plateforme permet à ses utilisateurs d'établir des dossiers 100% en ligne et de dialoguer avec plusieurs interlocuteurs privilégiés avant d'instruire un dépot.
Afin de répondre à cette invitation, merci de vous inscrit avec l'adresse email <%= @invite.email %> sur <%= users_dossiers_invite_url(@invite.id) %>.
Bonne journée.
---
L'équide TPS - tps@apientreprise.fr

View file

@ -0,0 +1,11 @@
Bonjour <%= @invite.email %>
L'utilisateur <%= @invite.email_sender %> souhaite que vous participiez à l'élaboration d'un dossier sur la plateforme TPS.
Ce dossier se nomme : <%= @invite.dossier.nom_projet %>
Pour le consulter, merci de suivre ce lien : <%= users_dossiers_invite_url(@invite.id) %>
Bonne journée.
---
L'équide TPS - tps@apientreprise.fr

View file

@ -1,6 +1,6 @@
.content#commentaires_flux{style:'width:100%;'}
%div#commentaire_new{style: 'width:80%; margin-left:auto; margin-right:auto'}
= form_tag(url_for({ controller: :commentaires, action: :create, dossier_id: @facade.dossier.id }), class: 'form-inline', method: 'POST') do
= form_tag(url_for({ controller: 'commentaires', action: :create, dossier_id: @facade.dossier.id }), class: 'form-inline', method: 'POST') do
%textarea.form-control{id: 'texte_commentaire', name: 'texte_commentaire', style: 'width: 100%; margin-bottom:2%', rows: '5', maxlength: '255', placeholder:"Dialoguer avec votre interlocuteur privilégié en charge de votre dossier."}
%input.form-control.btn.btn-success{:type => 'submit', :value => 'Poster', style: 'float:right'}
%br

View file

@ -4,17 +4,18 @@
.col-md-6.col-lg-6
%h2 Récapitulatif
.col-md-6.col-lg-6
= form_tag "/users/dossiers/#{@facade.dossier.id}/archive", style:'margin-top:21px', action: :archive, method: :put do
%button#archive.btn.btn-sm.btn-default.text-info{type: :button}
%i.fa.fa-eraser
Archiver
#confirm
%button#cancel.btn.btn-sm.btn-danger{type: :button}
%i.fa.fa-remove
Annuler
%button#valid.btn.btn-sm.btn-success{type: :submit}
%i.fa.fa-check
Valider
- if current_user.email == @facade.dossier.user.email
= form_tag "/users/dossiers/#{@facade.dossier.id}/archive", style:'margin-top:21px', action: :archive, method: :put do
%button#archive.btn.btn-sm.btn-default.text-info{type: :button}
%i.fa.fa-eraser
Archiver
#confirm
%button#cancel.btn.btn-sm.btn-danger{type: :button}
%i.fa.fa-remove
Annuler
%button#valid.btn.btn-sm.btn-success{type: :submit}
%i.fa.fa-check
Valider
.col-md-5.col-lg-5
@ -22,7 +23,7 @@
%h2#dossier_id{:class => 'text-info', :style => 'text-align:right; margin-bottom:15px'}
= "Dossier n°#{@facade.dossier.id}"
- unless gestionnaire_signed_in?
- if user_signed_in? && current_user.email == @facade.dossier.user.email
-if @facade.dossier.validated?
%br
= form_tag(url_for({controller: :recapitulatif, action: :submit, dossier_id: @facade.dossier.id}), method: 'POST') do
@ -36,4 +37,4 @@
= render partial: '/dossiers/infos_dossier'
%br
= render partial: 'commentaires_flux'
= render partial: '/users/recapitulatif/commentaires_flux'

View file

@ -30,9 +30,9 @@
.actions
= f.submit "Se connecter", class:'btn btn-primary'
%br
= render "users/shared/links"
%br
%div{style:'text-align:center'}
-#\-
-#%br

View file

@ -1,3 +1,3 @@
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "S'inscrire", new_registration_path(resource_name) %><br />
<%= link_to "S'inscrire", new_registration_path(resource_name), {class: 'btn btn-sm btn-success'} %><br />
<% end -%>

View file

@ -52,7 +52,11 @@ fr:
blank: ': Le mot de passe est vide'
password_confirmation:
confirmation: ': Les deux mots de passe ne correspondent pas'
invite:
attributes:
email:
blank: est vide
taken: ': Invitation déjà envoyée'
devise:
confirmations:

View file

@ -28,6 +28,12 @@ Rails.application.routes.draw do
get 'users' => 'users#index'
namespace :users do
namespace :dossiers do
resources :invites, only: [:index, :show]
post '/commentaire' => 'commentaires#create'
end
resources :dossiers do
get '/description' => 'description#show'
get '/description/error' => 'description#error'
@ -79,6 +85,8 @@ Rails.application.routes.draw do
resources :dossiers do
post 'valid' => 'dossiers#valid'
post 'close' => 'dossiers#close'
post 'invites' => '/invites#create'
end
resources :commentaires, only: [:create]

View file

@ -0,0 +1,11 @@
class CreateInvites < ActiveRecord::Migration
def change
create_table :invites do |t|
t.string :email
t.string :email_sender
end
add_reference :invites, :dossier, references: :dossiers
add_reference :invites, :user, references: :users
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160127170437) do
ActiveRecord::Schema.define(version: 20160204155519) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -159,6 +159,13 @@ ActiveRecord::Schema.define(version: 20160127170437) do
add_index "gestionnaires", ["email"], name: "index_gestionnaires_on_email", unique: true, using: :btree
add_index "gestionnaires", ["reset_password_token"], name: "index_gestionnaires_on_reset_password_token", unique: true, using: :btree
create_table "invites", force: :cascade do |t|
t.string "email"
t.string "email_sender"
t.integer "dossier_id"
t.integer "user_id"
end
create_table "module_api_cartos", force: :cascade do |t|
t.integer "procedure_id"
t.boolean "use_api_carto", default: false

View file

@ -43,5 +43,20 @@ describe Users::CommentairesController, type: :controller do
end
end
end
context 'when invite is connected' do
let(:invite) { create(:invite, :with_user, dossier: dossier) }
before do
sign_in invite.user
dossier.replied!
post :create, dossier_id: dossier_id, texte_commentaire: texte_commentaire
dossier.reload
end
it { is_expected.to redirect_to users_dossiers_invite_path(invite.id) }
it { expect(dossier.state).to eq 'replied' }
end
end
end

View file

@ -0,0 +1,73 @@
require 'spec_helper'
describe InvitesController, type: :controller do
let(:dossier) { create(:dossier) }
let(:email) { 'plop@octo.com' }
describe '#POST create' do
let(:invite) { Invite.last }
before do
sign_in create(:gestionnaire)
end
subject { post :create, dossier_id: dossier.id, email: email }
it { expect { subject }.to change(Invite, :count).by(1) }
context 'when email is assign to an user' do
let! (:user) { create(:user, email: email) }
before do
subject
end
it { expect(invite.user).to eq user }
it { expect(flash[:notice]).to be_present }
end
context 'when email is not assign to an user' do
before do
subject
end
it { expect(invite.user).to be_nil }
it { expect(flash[:notice]).to be_present }
end
describe 'not an email' do
context 'when email is not valid' do
let(:email) { 'plip.com' }
before do
subject
end
it { expect { subject }.not_to change(Invite, :count) }
it { expect(flash[:alert]).to be_present }
end
context 'when email is already used' do
let!(:invite) { create(:invite, dossier: dossier) }
before do
subject
end
it { expect { subject }.not_to change(Invite, :count) }
it { expect(flash[:alert]).to be_present }
end
end
describe 'send invitation email' do
context 'when user does not exist' do
end
context 'when user exist' do
end
end
end
end

20
spec/factories/invite.rb Normal file
View file

@ -0,0 +1,20 @@
FactoryGirl.define do
factory :invite do
email 'plop@octo.com'
after(:build) do |invite, _evaluator|
if invite.dossier.nil?
invite.dossier = create(:dossier)
end
end
trait :with_user do
after(:build) do |invite, _evaluator|
if invite.user.nil?
invite.user = create(:user)
invite.email = invite.user.email
end
end
end
end
end

View file

@ -24,6 +24,7 @@ describe Dossier do
it { is_expected.to have_one(:etablissement) }
it { is_expected.to have_one(:entreprise) }
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:invites) }
end
describe 'delegation' do

View file

@ -0,0 +1,38 @@
require 'spec_helper'
describe Invite do
describe 'database columns' do
it { is_expected.to have_db_column(:email) }
end
describe 'associations' do
it { is_expected.to belong_to(:dossier) }
it { is_expected.to belong_to(:user) }
end
describe 'an email can be used for multiple dossier' do
let(:email1) { 'plop@octo.com' }
let!(:dossier1) { create(:dossier) }
let!(:dossier2) { create(:dossier) }
context 'when an email is invite on two dossier' do
subject do
create(:invite, email: email1, dossier: dossier1)
create(:invite, email: email1, dossier: dossier2)
end
it { expect{ subject }.to change(Invite, :count).by(2) }
end
context 'when an email is invite twice on a dossier' do
subject do
create(:invite, email: email1, dossier: dossier1)
create(:invite, email: email1, dossier: dossier1)
end
it { expect{ subject }.to raise_error ActiveRecord::RecordInvalid }
end
end
end

View file

@ -20,6 +20,7 @@ describe User, type: :model do
end
describe 'associations' do
it { is_expected.to have_many(:dossiers) }
it { is_expected.to have_many(:invites) }
end
describe '#find_for_france_connect' do
let(:siret) { '00000000000000' }

View file

@ -11,7 +11,7 @@ describe 'backoffice/dossiers/show.html.haml', type: :view do
assign(:facade, (DossierFacades.new dossier.id, gestionnaire.email))
end
context 'on the dossier admin page' do
context 'on the dossier gestionnaire page' do
before do
render
end

View file

@ -1,11 +1,12 @@
require 'spec_helper'
describe 'users/recapitulatif/show.html.haml', type: :view do
let(:dossier) { create(:dossier, :with_entreprise, state: state, procedure: create(:procedure, :with_api_carto)) }
let(:dossier) { create(:dossier, :with_entreprise, state: state, procedure: create(:procedure, :with_api_carto)) }
let(:dossier_id) { dossier.id }
let(:state) { 'draft' }
before do
sign_in dossier.user
assign(:facade, DossierFacades.new(dossier.id, dossier.user.email))
end
@ -28,7 +29,7 @@ describe 'users/recapitulatif/show.html.haml', type: :view do
expect(rendered).to have_content(dossier_id)
end
context 'les liens de modifications' do
describe 'les liens de modifications' do
context 'lien description' do
it 'le lien vers description est présent' do
expect(rendered).to have_css('#maj_infos')
@ -128,5 +129,45 @@ describe 'users/recapitulatif/show.html.haml', type: :view do
end
end
end
context 'when invite is logged' do
let!(:invite_user) { create(:user, email: 'invite@octo.com') }
before do
create(:invite) { create(:invite, email: invite_user.email, user: invite_user, dossier: dossier) }
sign_out dossier.user
sign_in invite_user
render
end
describe 'les liens de modifications' do
it 'describe link is not present' do
expect(rendered).not_to have_css('#maj_infos')
end
it 'map link is not present' do
expect(rendered).not_to have_css('#maj_carte')
end
it 'archive link is not present' do
expect(rendered).not_to have_content('Archiver')
end
end
context 'when dossier is validated' do
let(:state) { 'validated' }
before do
render
end
it 'submitted link is not present' do
expect(rendered).not_to have_content('Déposer mon dossier')
end
end
end
end
end