diff --git a/Gemfile b/Gemfile
index 5bd2a237d..12229eea2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -22,6 +22,8 @@ gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc
+# Enable deep clone of active record models
+gem 'deep_cloneable', '~> 2.2.1'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
diff --git a/Gemfile.lock b/Gemfile.lock
index 21f55c50e..31a9b9b55 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -119,6 +119,8 @@ GEM
sprockets (>= 2.0.0)
database_cleaner (1.4.1)
debug_inspector (0.0.2)
+ deep_cloneable (2.2.1)
+ activerecord (>= 3.1.0, < 5.2.0)
devise (3.4.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
@@ -634,6 +636,7 @@ DEPENDENCIES
coffee-rails (~> 4.1.0)
css_splitter
database_cleaner
+ deep_cloneable (~> 2.2.1)
devise
draper
factory_girl
@@ -690,4 +693,4 @@ DEPENDENCIES
will_paginate-bootstrap
BUNDLED WITH
- 1.11.2
+ 1.12.5
diff --git a/app/controllers/admin/procedures_controller.rb b/app/controllers/admin/procedures_controller.rb
index 4539cc02f..19e003a70 100644
--- a/app/controllers/admin/procedures_controller.rb
+++ b/app/controllers/admin/procedures_controller.rb
@@ -83,6 +83,23 @@ class Admin::ProceduresController < AdminController
change_status({archived: params[:archive]})
end
+ def clone
+ @procedure = current_administrateur.procedures.find(params[:procedure_id])
+
+ new_procedure = @procedure.clone
+ if new_procedure
+ flash.notice = 'Procédure clonée'
+ redirect_to edit_admin_procedure_path(id: new_procedure.id)
+ else
+ flash.now.alert = @procedure.errors.full_messages.join('
').html_safe
+ render 'index'
+ end
+
+ rescue ActiveRecord::RecordNotFound
+ flash.alert = 'Procédure inéxistante'
+ redirect_to admin_procedures_path
+ end
+
def active_class
@active_class = 'active'
end
diff --git a/app/controllers/commentaires_controller.rb b/app/controllers/commentaires_controller.rb
index f711a4ab5..4a0b75662 100644
--- a/app/controllers/commentaires_controller.rb
+++ b/app/controllers/commentaires_controller.rb
@@ -22,10 +22,11 @@ class CommentairesController < ApplicationController
end
@commentaire.body = params['texte_commentaire']
- @commentaire.save unless flash.alert
+ saved = false
+ saved = @commentaire.save unless flash.alert
if is_gestionnaire?
- NotificationMailer.new_answer(@commentaire.dossier).deliver_now!
+ 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
diff --git a/app/controllers/gestionnaires/passwords_controller.rb b/app/controllers/gestionnaires/passwords_controller.rb
new file mode 100644
index 000000000..a7a1d1734
--- /dev/null
+++ b/app/controllers/gestionnaires/passwords_controller.rb
@@ -0,0 +1,32 @@
+class Gestionnaires::PasswordsController < Devise::PasswordsController
+ # GET /resource/password/new
+ # def new
+ # super
+ # end
+
+ # POST /resource/password
+ # def create
+ # super
+ # end
+
+ # GET /resource/password/edit?reset_password_token=abcdef
+ # def edit
+ # super
+ # end
+
+ # PUT /resource/password
+ # def update
+ # super
+ # end
+
+ # protected
+
+ # def after_resetting_password_path_for(resource)
+ # super(resource)
+ # end
+
+ # The path used after sending reset password instructions
+ # def after_sending_reset_password_instructions_path_for(resource_name)
+ # super(resource_name)
+ # end
+end
diff --git a/app/models/procedure.rb b/app/models/procedure.rb
index 00aa073b6..5f4de31f6 100644
--- a/app/models/procedure.rb
+++ b/app/models/procedure.rb
@@ -58,4 +58,11 @@ class Procedure < ActiveRecord::Base
published?
end
+ def clone
+ procedure = self.deep_clone(include: [ :types_de_piece_justificative, :types_de_champ, :module_api_carto ])
+ procedure.archived = false
+ procedure.published = false
+ return procedure if procedure.save
+ end
+
end
diff --git a/app/views/admin/procedures/_draft_list.html.haml b/app/views/admin/procedures/_draft_list.html.haml
index 7d3505614..b169f2fc5 100644
--- a/app/views/admin/procedures/_draft_list.html.haml
+++ b/app/views/admin/procedures/_draft_list.html.haml
@@ -3,6 +3,7 @@
%thead
%th#ID= smart_listing.sortable 'ID', 'id'
%th#libelle= smart_listing.sortable 'Libellé', 'libelle'
+ %th#lien Actions
- @procedures.each do |procedure|
- procedure = procedure.decorate
@@ -10,6 +11,7 @@
%td.col-md-1.col-lg-1= procedure.id
%td.col-md-6.col-lg-6
= link_to(procedure.libelle, "/admin/procedures/#{procedure.id}")
+ %td= link_to('cloner', admin_procedure_clone_path(procedure.id), 'data-method' => :put, class: 'btn-xs btn-primary')
= smart_listing.paginate
= smart_listing.pagination_per_page_links
diff --git a/app/views/admin/procedures/_list.html.haml b/app/views/admin/procedures/_list.html.haml
index 651583f12..4735d527f 100644
--- a/app/views/admin/procedures/_list.html.haml
+++ b/app/views/admin/procedures/_list.html.haml
@@ -4,6 +4,7 @@
%th#ID= smart_listing.sortable 'ID', 'id'
%th#libelle= smart_listing.sortable 'Libellé', 'libelle'
%th#lien Lien
+ %th#lien Actions
- @procedures.each do |procedure|
- procedure = procedure.decorate
@@ -12,6 +13,7 @@
%td.col-md-6.col-lg-6
= link_to(procedure.libelle, "/admin/procedures/#{procedure.id}")
%td= link_to procedure.lien, procedure.lien
+ %td= link_to('cloner', admin_procedure_clone_path(procedure.id), 'data-method' => :put, class: 'btn-xs btn-primary')
= smart_listing.paginate
= smart_listing.pagination_per_page_links
diff --git a/app/views/gestionnaires/passwords/edit.html.haml b/app/views/gestionnaires/passwords/edit.html.haml
new file mode 100644
index 000000000..ff4027fe4
--- /dev/null
+++ b/app/views/gestionnaires/passwords/edit.html.haml
@@ -0,0 +1,31 @@
+= devise_error_messages!
+
+#form_login
+ = image_tag('logo-tps.png')
+ %br
+ %h2#gestionnaire_login Changement de mot de passe
+
+ %br
+ %br
+ #new_user
+ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
+ = f.hidden_field :reset_password_token
+ %h4
+ = f.label 'Nouveau mot de passe'
+
+ .input-group
+ .input-group-addon
+ %span.glyphicon.glyphicon-asterisk
+ = f.password_field :password, autofocus: true, autocomplete: "off", class: 'form-control'
+ %br
+ %h4
+ = f.label 'Retaper le mot de passe'
+ .input-group
+ .input-group-addon
+ %span.glyphicon.glyphicon-asterisk
+ = f.password_field :password_confirmation, autocomplete: "off", class: 'form-control'
+ %br
+ %br
+ .actions
+ = f.submit "Changer", class:'btn btn-primary'
+ %br
\ No newline at end of file
diff --git a/app/views/gestionnaires/passwords/new.html.haml b/app/views/gestionnaires/passwords/new.html.haml
new file mode 100644
index 000000000..9c15a8ca9
--- /dev/null
+++ b/app/views/gestionnaires/passwords/new.html.haml
@@ -0,0 +1,22 @@
+= devise_error_messages!
+
+#form_login
+ = image_tag('logo-tps.png')
+ %br
+ %h2#gestionnaire_login Mot de passe oublié
+
+ %br
+ %br
+ #new_user
+ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f|
+ %h4
+ = f.label :email
+ .input-group
+ .input-group-addon
+ %span.glyphicon.glyphicon-user
+ = f.email_field :email, class: 'form-control', placeholder: 'Email'
+ %br
+ %br
+ .actions
+ = f.submit "Renvoyer", class:'btn btn-primary'
+ %br
\ No newline at end of file
diff --git a/app/views/gestionnaires/sessions/new.html.haml b/app/views/gestionnaires/sessions/new.html.haml
index 3a96664d7..a3de3cd1e 100644
--- a/app/views/gestionnaires/sessions/new.html.haml
+++ b/app/views/gestionnaires/sessions/new.html.haml
@@ -25,4 +25,5 @@
%br
.actions
= f.submit "Se connecter", class:'btn btn-primary'
- %br
\ No newline at end of file
+ %br
+ = render "gestionnaires/shared/links"
diff --git a/app/views/gestionnaires/shared/_links.html.erb b/app/views/gestionnaires/shared/_links.html.erb
new file mode 100644
index 000000000..51021059c
--- /dev/null
+++ b/app/views/gestionnaires/shared/_links.html.erb
@@ -0,0 +1,2 @@
+<%= link_to "Mot de passe oublié ?", new_password_path(resource_name) %>
+
diff --git a/config/routes.rb b/config/routes.rb
index d142cadda..98b331bc3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -9,8 +9,9 @@ Rails.application.routes.draw do
}, skip: [:password, :registrations]
devise_for :gestionnaires, controllers: {
- sessions: 'gestionnaires/sessions'
- }, skip: [:password, :registrations]
+ sessions: 'gestionnaires/sessions',
+ passwords: 'gestionnaires/passwords'
+ }, skip: [:registrations]
devise_for :users, controllers: {
sessions: 'users/sessions',
@@ -25,6 +26,8 @@ Rails.application.routes.draw do
devise_scope :gestionnaire do
get '/gestionnaires/sign_in/demo' => 'gestionnaires/sessions#demo'
+ get '/gestionnaires/edit' => 'gestionnaires/registrations#edit', :as => 'edit_gestionnaires_registration'
+ put '/gestionnaires' => 'gestionnaires/registrations#update', :as => 'gestionnaires_registration'
end
devise_scope :administrateur do
@@ -100,6 +103,7 @@ Rails.application.routes.draw do
put 'archive' => 'procedures#archive', as: :archive
put 'publish' => 'procedures#publish', as: :publish
+ put 'clone' => 'procedures#clone', as: :clone
resource :accompagnateurs, only: [:show, :update]
diff --git a/spec/controllers/admin/procedures_controller_spec.rb b/spec/controllers/admin/procedures_controller_spec.rb
index 11587b94d..8d8779dc3 100644
--- a/spec/controllers/admin/procedures_controller_spec.rb
+++ b/spec/controllers/admin/procedures_controller_spec.rb
@@ -265,4 +265,35 @@ describe Admin::ProceduresController, type: :controller do
it { expect(flash[:alert]).to have_content 'Procédure inéxistante' }
end
end
+
+ describe 'PUT #clone' do
+ let!(:procedure) { create(:procedure, administrateur: admin) }
+ subject { put :clone, procedure_id: procedure.id }
+
+ it { expect{ subject }.to change(Procedure, :count).by(1) }
+
+ context 'when admin is the owner of the procedure' do
+ before do
+ subject
+ end
+
+ it 'creates a new procedure and redirect to it' do
+ expect(response).to redirect_to edit_admin_procedure_path(id: Procedure.last.id)
+ expect(flash[:notice]).to have_content 'Procédure clonée'
+ end
+ end
+
+ context 'when admin is not the owner of the procedure' do
+ let(:admin_2) { create(:administrateur) }
+
+ before do
+ sign_out admin
+ sign_in admin_2
+ subject
+ end
+
+ it { expect(response).to redirect_to :admin_procedures }
+ it { expect(flash[:alert]).to have_content 'Procédure inéxistante' }
+ end
+ end
end
diff --git a/spec/controllers/backoffice/commentaires_controller_spec.rb b/spec/controllers/backoffice/commentaires_controller_spec.rb
index a66229f20..a85f0cc99 100644
--- a/spec/controllers/backoffice/commentaires_controller_spec.rb
+++ b/spec/controllers/backoffice/commentaires_controller_spec.rb
@@ -91,5 +91,17 @@ describe Backoffice::CommentairesController, type: :controller do
end
end
end
+
+ describe 'comment cannot be saved' do
+ before do
+ allow_any_instance_of(Commentaire).to receive(:save).and_return(false)
+ end
+ it 'Notification email is not sent' do
+ expect(NotificationMailer).not_to receive(:new_answer)
+ expect(NotificationMailer).not_to receive(:deliver_now!)
+
+ post :create, dossier_id: dossier_id, texte_commentaire: texte_commentaire
+ end
+ end
end
end
diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb
index b36e64837..f805f0292 100644
--- a/spec/models/procedure_spec.rb
+++ b/spec/models/procedure_spec.rb
@@ -122,4 +122,44 @@ describe Procedure do
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
end
end
+
+ describe 'clone' do
+ let(:archived) { false }
+ let(:published) { false }
+ let(:procedure) { create(:procedure, archived: archived, published: published) }
+ let!(:type_de_champ_0) { create(:type_de_champ, procedure: procedure, order_place: 0) }
+ let!(:type_de_champ_1) { create(:type_de_champ, procedure: procedure, order_place: 1) }
+ let!(:piece_justificative_0) { create(:type_de_piece_justificative, procedure: procedure, order_place: 0) }
+ let!(:piece_justificative_1) { create(:type_de_piece_justificative, procedure: procedure, order_place: 1) }
+ subject { procedure.clone }
+
+ it 'should duplicate specific objects with different id' do
+ expect(subject.id).not_to eq(procedure.id)
+ expect(subject).to have_same_attributes_as(procedure)
+ expect(subject.module_api_carto).to have_same_attributes_as(procedure.module_api_carto)
+
+ subject.types_de_champ.zip(procedure.types_de_champ).each do |stc, ptc|
+ expect(stc).to have_same_attributes_as(ptc)
+ end
+
+ subject.types_de_piece_justificative.zip(procedure.types_de_piece_justificative).each do |stc, ptc|
+ expect(stc).to have_same_attributes_as(ptc)
+ end
+ end
+
+ it 'should not duplicate specific related objects' do
+ expect(subject.dossiers).to eq([])
+ expect(subject.gestionnaires).to eq([])
+ expect(subject.assign_to).to eq([])
+ end
+
+ describe 'procedure status is reset' do
+ let(:archived) { true }
+ let(:published) { true }
+ it 'sets published and archived to false' do
+ expect(subject.archived).to be_falsey
+ expect(subject.published).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 7411d7377..bca602fe6 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -113,4 +113,11 @@ RSpec.configure do |config|
end
end
}
+
+ RSpec::Matchers.define :have_same_attributes_as do |expected|
+ match do |actual|
+ ignored = [:id, :procedure_id, :updated_at, :created_at]
+ actual.attributes.with_indifferent_access.except(*ignored) == expected.attributes.with_indifferent_access.except(*ignored)
+ end
+ end
end