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