diff --git a/Gemfile b/Gemfile index dbefab4c0..98ccff836 100644 --- a/Gemfile +++ b/Gemfile @@ -87,6 +87,7 @@ gem "smart_listing" gem 'bootstrap-wysihtml5-rails', '~> 0.3.3.8' gem 'as_csv' +gem 'spreadsheet_architect' gem 'apipie-rails', '=0.3.1' gem "maruku" # for Markdown support in apipie @@ -111,6 +112,10 @@ group :test do gem 'vcr' end +group :development do + gem 'web-console', '~> 2.0' +end + group :development, :test do # gem 'terminal-notifier' # gem 'terminal-notifier-guard' @@ -120,7 +125,6 @@ group :development, :test do gem 'pry-byebug' # Access an IRB console on exception pages or by using <%= console %> in views - gem 'web-console', '~> 2.0' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' diff --git a/Gemfile.lock b/Gemfile.lock index a42023ff3..319e88f59 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -62,6 +62,10 @@ GEM autoprefixer-rails (5.2.1) execjs json + axlsx (2.0.1) + htmlentities (~> 4.3.1) + nokogiri (>= 1.4.1) + rubyzip (~> 1.0.0) bcrypt (3.1.11) bindata (2.1.0) binding_of_caller (0.7.2) @@ -294,6 +298,7 @@ GEM haml (~> 4.0.0) nokogiri (~> 1.6.0) ruby_parser (~> 3.5) + htmlentities (4.3.4) http-cookie (1.0.2) domain_name (~> 0.5) httpclient (2.6.0.1) @@ -437,6 +442,10 @@ GEM rgeo (0.3.20) rgeo-geojson (0.3.1) rgeo (~> 0.3) + rodf (0.3.7) + activesupport (>= 3.0, < 6.0) + builder (~> 3.0) + rubyzip (~> 1.0) rspec (3.2.0) rspec-core (~> 3.2.0) rspec-expectations (~> 3.2.0) @@ -473,7 +482,7 @@ GEM sexp_processor (~> 4.0) ruby_parser (3.7.0) sexp_processor (~> 4.1) - rubyzip (1.1.7) + rubyzip (1.0.0) safe_yaml (1.0.4) sass (3.4.22) sass-rails (5.0.6) @@ -513,6 +522,9 @@ GEM jquery-rails kaminari (~> 0.16.1) rails (>= 3.2) + spreadsheet_architect (1.4.7) + axlsx (>= 2.0) + rodf (>= 0.3.6) spring (1.3.6) spring-commands-rspec (1.0.4) spring (>= 0.9.1) @@ -645,6 +657,7 @@ DEPENDENCIES shoulda-matchers simplecov smart_listing + spreadsheet_architect spring spring-commands-rspec therubyracer @@ -658,4 +671,4 @@ DEPENDENCIES will_paginate-bootstrap BUNDLED WITH - 1.13.2 + 1.13.6 diff --git a/app/assets/javascripts/dossier_commentaires_modal.js b/app/assets/javascripts/dossier_commentaires_modal.js new file mode 100644 index 000000000..1b697bd4e --- /dev/null +++ b/app/assets/javascripts/dossier_commentaires_modal.js @@ -0,0 +1,16 @@ +$(document).on('page:load', init_modal_commentaire); +$(document).ready(init_modal_commentaire); + +function init_modal_commentaire() { + var modal = $("#modalCommentairesDossierParChamp"); + var body = modal.find(".modal-body"); + var originalBody = body.html(); + + modal.on("show.bs.modal", function (e) { + body.load(e.relatedTarget.getAttribute("data-href")); + }); + + $("#modalCommentairesDossierParChamp").on("hidden.bs.modal", function (e) { + body.html(originalBody); + }); +} \ No newline at end of file diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index cb7b0123c..1e7eba0fc 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -290,4 +290,4 @@ div.pagination { margin-right: 0px !important; } -} \ No newline at end of file +} diff --git a/app/controllers/backoffice/dossiers_controller.rb b/app/controllers/backoffice/dossiers_controller.rb index 4de0b31a2..ce39dbed1 100644 --- a/app/controllers/backoffice/dossiers_controller.rb +++ b/app/controllers/backoffice/dossiers_controller.rb @@ -1,4 +1,5 @@ class Backoffice::DossiersController < Backoffice::DossiersListController + respond_to :html, :xlsx, :ods, :csv def index super @@ -18,11 +19,17 @@ class Backoffice::DossiersController < Backoffice::DossiersListController end def download_dossiers_tps - dossiers = current_gestionnaire.dossiers.where.not(state: :draft) - - response.headers['Content-Type'] = 'text/csv' - - render csv: dossiers, status: 200 + if procedure = Procedure.find_by(id: params[:procedure_id]) + dossiers = dossiers_list_facade(param_liste).dossiers_to_display + respond_with Dossier.export_full_generation(dossiers, request.format) + else + dossiers = dossiers_list_facade(param_liste).dossiers_to_display + respond_to do |format| + format.xlsx { render xlsx: dossiers } + format.ods { render ods: dossiers } + format.csv { render csv: dossiers } + end + end end def search diff --git a/app/controllers/commentaires_controller.rb b/app/controllers/commentaires_controller.rb index bea003766..8a1ce41c8 100644 --- a/app/controllers/commentaires_controller.rb +++ b/app/controllers/commentaires_controller.rb @@ -1,7 +1,20 @@ class CommentairesController < ApplicationController + def index + @facade = DossierFacades.new( + params[:dossier_id], + (current_gestionnaire || current_user).email, + params[:champs_id] + ) + render layout: false + rescue ActiveRecord::RecordNotFound + flash.alert = t('errors.messages.dossier_not_found') + redirect_to url_for(controller: '/') + end + def create @commentaire = Commentaire.new @commentaire.dossier = Dossier.find(params['dossier_id']) + @commentaire.champ = @commentaire.dossier.champs.find(params[:champ_id]) if params[:champ_id] if is_gestionnaire? @commentaire.email = current_gestionnaire.email diff --git a/app/facades/dossier_facades.rb b/app/facades/dossier_facades.rb index 04ded4fd9..0616bce68 100644 --- a/app/facades/dossier_facades.rb +++ b/app/facades/dossier_facades.rb @@ -1,9 +1,10 @@ class DossierFacades #TODO rechercher en fonction de la personne/email - def initialize dossier_id, email + def initialize(dossier_id, email, champ_id = nil) @dossier = Dossier.where(archived: false).find(dossier_id) @email = email + @champ_id = champ_id end def dossier @@ -26,8 +27,12 @@ class DossierFacades @dossier.ordered_pieces_justificatives end + def champ_id + @champ_id + end + def commentaires - @dossier.ordered_commentaires.all.decorate + @dossier.ordered_commentaires.where(champ_id: @champ_id).decorate end def commentaire_email @@ -61,4 +66,4 @@ class DossierFacades def followers Gestionnaire.joins(:follows).where("follows.dossier_id=#{@dossier.id}") end -end \ No newline at end of file +end diff --git a/app/models/champ.rb b/app/models/champ.rb index cf4325b0e..f6ba23268 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -1,6 +1,7 @@ class Champ < ActiveRecord::Base belongs_to :dossier belongs_to :type_de_champ + has_many :commentaires delegate :libelle, :type_champ, :order_place, :mandatory, :description, :drop_down_list, to: :type_de_champ diff --git a/app/models/commentaire.rb b/app/models/commentaire.rb index 2283775d3..e59138ccb 100644 --- a/app/models/commentaire.rb +++ b/app/models/commentaire.rb @@ -1,5 +1,6 @@ class Commentaire < ActiveRecord::Base belongs_to :dossier + belongs_to :champ belongs_to :piece_justificative end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index c6ef99b8c..8bd92395a 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -1,4 +1,5 @@ class Dossier < ActiveRecord::Base + include SpreadsheetArchitect enum state: {draft: 'draft', initiated: 'initiated', @@ -279,16 +280,75 @@ class Dossier < ActiveRecord::Base procedure.cerfa_flag? && cerfa.size != 0 end - def as_csv(options={}) - dossier_attr = DossierSerializer.new(self).attributes - - unless entreprise.nil? - etablissement_attr = EtablissementCsvSerializer.new(self.etablissement).attributes.map { |k, v| ["etablissement.#{k}", v] }.to_h - entreprise_attr = EntrepriseSerializer.new(self.entreprise).attributes.map { |k, v| ["entreprise.#{k}", v] }.to_h - dossier_attr = dossier_attr.merge(etablissement_attr).merge(entreprise_attr) + def convert_specific_hash_values_to_string(hash_to_convert) + hash = {} + hash_to_convert.each do |key, value| + value = value.to_s if !value.kind_of?(Time) && !value.nil? + hash.store(key, value) end + return hash + end - dossier_attr + def convert_specific_array_values_to_string(array_to_convert) + array = [] + array_to_convert.each do |value| + value = value.to_s if !value.kind_of?(Time) && !value.nil? + array << value + end + return array + end + + def export_entreprise_data + unless entreprise.nil? + etablissement_attr = EtablissementCsvSerializer.new(self.etablissement).attributes.map { |k, v| ["etablissement.#{k}".parameterize.underscore.to_sym, v] }.to_h + entreprise_attr = EntrepriseSerializer.new(self.entreprise).attributes.map { |k, v| ["entreprise.#{k}".parameterize.underscore.to_sym, v] }.to_h + else + etablissement_attr = EtablissementSerializer.new(Etablissement.new).attributes.map { |k, v| ["etablissement.#{k}".parameterize.underscore.to_sym, v] }.to_h + entreprise_attr = EntrepriseSerializer.new(Entreprise.new).attributes.map { |k, v| ["entreprise.#{k}".parameterize.underscore.to_sym, v] }.to_h + end + return convert_specific_hash_values_to_string(etablissement_attr.merge(entreprise_attr)) + end + + def export_default_columns + dossier_attr = DossierSerializer.new(self).attributes + dossier_attr = convert_specific_hash_values_to_string(dossier_attr) + dossier_attr = dossier_attr.merge(self.export_entreprise_data) + return dossier_attr + end + + def spreadsheet_columns + self.export_default_columns.to_a + end + + def data_with_champs + serialized_dossier = DossierProcedureSerializer.new(self) + data = serialized_dossier.attributes.values + data += self.champs.order('type_de_champ_id ASC').map(&:value) + data += self.export_entreprise_data.values + return data + end + + def export_headers + serialized_dossier = DossierProcedureSerializer.new(self) + headers = serialized_dossier.attributes.keys + headers += self.procedure.types_de_champ.order('id ASC').map { |types_de_champ| types_de_champ.libelle.parameterize.underscore.to_sym } + headers += self.export_entreprise_data.keys + return headers + end + + def self.export_full_generation(dossiers, format) + data = [] + headers = dossiers.first.export_headers + dossiers.each do |dossier| + data << dossier.convert_specific_array_values_to_string(dossier.data_with_champs) + end + if ["csv"].include?(format) + return SpreadsheetArchitect.to_csv(data: data, headers: headers) + elsif ["xlsx"].include?(format) + return SpreadsheetArchitect.to_xlsx(data: data, headers: headers) + elsif ["ods"].include?(format) + return SpreadsheetArchitect.to_ods(data: data, headers: headers) + end end def reset! diff --git a/app/models/entreprise.rb b/app/models/entreprise.rb index 5e24a31bb..8e14d7640 100644 --- a/app/models/entreprise.rb +++ b/app/models/entreprise.rb @@ -1,4 +1,5 @@ class Entreprise < ActiveRecord::Base + belongs_to :dossier has_one :etablissement, dependent: :destroy has_one :rna_information, dependent: :destroy diff --git a/app/models/etablissement.rb b/app/models/etablissement.rb index 44e840205..1e80b4bad 100644 --- a/app/models/etablissement.rb +++ b/app/models/etablissement.rb @@ -1,4 +1,5 @@ class Etablissement < ActiveRecord::Base + belongs_to :dossier belongs_to :entreprise diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 553efdf4e..9d1b2e693 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -43,4 +43,4 @@ class TypeDeChamp < ActiveRecord::Base self.mandatory = false if self.type_champ == 'header_section' true end -end \ No newline at end of file +end diff --git a/app/serializers/dossier_procedure_serializer.rb b/app/serializers/dossier_procedure_serializer.rb new file mode 100644 index 000000000..278c6d4ce --- /dev/null +++ b/app/serializers/dossier_procedure_serializer.rb @@ -0,0 +1,8 @@ +class DossierProcedureSerializer < ActiveModel::Serializer + attributes :id, + :created_at, + :updated_at, + :archived, + :mandataire_social, + :state +end diff --git a/app/serializers/dossier_serializer.rb b/app/serializers/dossier_serializer.rb index 5060598ab..eb074761b 100644 --- a/app/serializers/dossier_serializer.rb +++ b/app/serializers/dossier_serializer.rb @@ -4,8 +4,7 @@ class DossierSerializer < ActiveModel::Serializer :updated_at, :archived, :mandataire_social, - :state, - :total_commentaire + :state has_one :entreprise has_one :etablissement @@ -15,4 +14,4 @@ class DossierSerializer < ActiveModel::Serializer has_many :champs_private has_many :pieces_justificatives has_many :types_de_piece_justificative -end \ No newline at end of file +end diff --git a/app/services/dossiers_list_gestionnaire_service.rb b/app/services/dossiers_list_gestionnaire_service.rb index 7cb09366f..cfd6bf63e 100644 --- a/app/services/dossiers_list_gestionnaire_service.rb +++ b/app/services/dossiers_list_gestionnaire_service.rb @@ -183,4 +183,4 @@ class DossiersListGestionnaireService def current_preference_smart_listing_page @current_devise_profil.preference_smart_listing_page end -end \ No newline at end of file +end diff --git a/app/views/backoffice/commentaires/index.html.haml b/app/views/backoffice/commentaires/index.html.haml new file mode 100644 index 000000000..a764a3e90 --- /dev/null +++ b/app/views/backoffice/commentaires/index.html.haml @@ -0,0 +1 @@ += render partial: '/users/recapitulatif/commentaires_flux' diff --git a/app/views/backoffice/dossiers/index.html.haml b/app/views/backoffice/dossiers/index.html.haml index 2f1f62703..0f0f3326b 100644 --- a/app/views/backoffice/dossiers/index.html.haml +++ b/app/views/backoffice/dossiers/index.html.haml @@ -2,10 +2,30 @@ #pref_list_menu = render partial: 'backoffice/dossiers/pref_list' - =link_to t('dynamics.backoffice.download_all_dossiers'), backoffice_download_dossiers_tps_path, {class: 'btn btn-success btn-sm', style: 'float: right; margin-right: 4%; margin-top: 7px'} %h1 =t('dynamics.backoffice.title') + %div.dropdown.pull-right#download_menu + - if @dossiers_list_facade.dossiers_to_display.count > 200 + %button.btn.btn-error.dropdown-toggle#dropdownDownloadMenu{ type: :button, 'data-toggle' => 'dropdown', 'aria-haspopup' => true, 'aria-expanded' => false, class: 'disabled'} + %span{'data-toggle' => :tooltip, "data-placement" => :left, title: 'Pour réduire le nombre de dossiers et ne pas dépasser la limite autorisée de 200, merci de bien vouloir appliquer des filtres.'} + = t('dynamics.backoffice.limit_excess_download_all_dossiers') + - else + %button.btn.btn-success.dropdown-toggle#dropdownDownloadMenu{ type: :button, 'data-toggle' => 'dropdown', 'aria-haspopup' => true, 'aria-expanded' => false } + %i.fa.fa-download + = t('dynamics.backoffice.download_all_dossiers') + %span.caret + %ul.dropdown-menu.dropdown-menu-right + %li + = link_to backoffice_download_dossiers_tps_path(format: :csv, procedure_id: params[:id]), { class: 'btn btn-sm' } do + = t('dynamics.backoffice.format_csv') + %li + = link_to backoffice_download_dossiers_tps_path(format: :xlsx, procedure_id: params[:id]), { class: 'btn btn-sm' } do + = t('dynamics.backoffice.format_xlsx') + %li + = link_to backoffice_download_dossiers_tps_path(format: :ods, procedure_id: params[:id]), { class: 'btn btn-sm' } do + = t('dynamics.backoffice.format_ods') + = render partial: 'backoffice/dossiers/onglets' = smart_listing_render :dossiers diff --git a/app/views/dossiers/_infos_dossier.html.haml b/app/views/dossiers/_infos_dossier.html.haml index c34efe26c..6d0bf1ef7 100644 --- a/app/views/dossiers/_infos_dossier.html.haml +++ b/app/views/dossiers/_infos_dossier.html.haml @@ -63,6 +63,12 @@ %tr %th{ style: 'width:25%' } =champ.libelle + -if gestionnaire_signed_in? + =link_to "COM", "", "data-href" => backoffice_dossier_commentaires_path(@facade.dossier, champs_id: champ.id), + "data-toggle" => "modal", "data-target" => "#modalCommentairesDossierParChamp" + -else + =link_to "COM", "", "data-href" => users_dossier_commentaires_path(@facade.dossier, champs_id: champ.id), + "data-toggle" => "modal", "data-target" => "#modalCommentairesDossierParChamp" %td -unless champ.decorate.value.blank? =champ.decorate.value.html_safe @@ -114,3 +120,18 @@ %button.action_button.btn.btn-warning %i.fa.fa-circle-o +#modalCommentairesDossierParChamp.modal.fade{"tabindex" => -1, "role" => "dialog"} + .modal-dialog{"role" => "document"} + .modal-content + .modal-header + %button.close{"data-dismiss" => "modal", "aria-label" => "Fermer"} + %span{"aria-hidden" => true} + × + .modal-title + Commentaires + .modal-body + %p + Chargement des commentaires en cours... + .modal-footer + %button.btn.btn-primary{"data-dismiss" => "modal"} + Fermer diff --git a/app/views/notification_mailer/dossier_received.html.erb b/app/views/notification_mailer/dossier_received.html.erb index 4bcdc394f..2b39f1abe 100644 --- a/app/views/notification_mailer/dossier_received.html.erb +++ b/app/views/notification_mailer/dossier_received.html.erb @@ -1 +1 @@ -<%= escape_once (MailTemplate.replace_tags @dossier.procedure.mail_received.body, @dossier).html_safe %> \ No newline at end of file +<%= MailTemplate.replace_tags(@dossier.procedure.mail_received.body, @dossier).html_safe %> \ No newline at end of file diff --git a/app/views/users/commentaires/index.html.haml b/app/views/users/commentaires/index.html.haml new file mode 100644 index 000000000..a764a3e90 --- /dev/null +++ b/app/views/users/commentaires/index.html.haml @@ -0,0 +1 @@ += render partial: '/users/recapitulatif/commentaires_flux' diff --git a/app/views/users/recapitulatif/_commentaires_flux.html.haml b/app/views/users/recapitulatif/_commentaires_flux.html.haml index ef9404be1..38202fb3e 100644 --- a/app/views/users/recapitulatif/_commentaires_flux.html.haml +++ b/app/views/users/recapitulatif/_commentaires_flux.html.haml @@ -1,6 +1,6 @@ .content#commentaires_flux{style:'width:100%;'} %div#commentaire_new{style: 'width:80%; margin-left:auto; margin-right:auto; margin-bottom:7%'} - = form_tag(url_for({ controller: 'commentaires', action: :create, dossier_id: @facade.dossier.id }), class: 'form-inline', method: 'POST', multipart: true) do + = form_tag(url_for({ controller: 'commentaires', action: :create, dossier_id: @facade.dossier.id, champ_id: @facade.champ_id }), class: 'form-inline', method: 'POST', multipart: true) do %textarea.form-control{id: 'texte_commentaire', class: 'wysihtml5', name: 'texte_commentaire', style: 'width: 100%; margin-bottom:2%', rows: '5', placeholder:"Commentaire"} %h4.text-primary{style: 'margin-top: 0px'} Ajout un fichier = file_field_tag "piece_justificative[content]", accept: PieceJustificative.accept_format, style: 'float: left; margin-left: 20px' diff --git a/config/locales/dynamics/fr.yml b/config/locales/dynamics/fr.yml index c16ec0ab7..c85b36fab 100644 --- a/config/locales/dynamics/fr.yml +++ b/config/locales/dynamics/fr.yml @@ -12,7 +12,11 @@ fr: pref_list: title: 'Gestion de colonnes affichées' description: 'Ce menu vous permet de choisir les différentes colonnes que vous souhaitez voir apparaître dans votre interface de suivi des dossiers.' - download_all_dossiers: 'Tous mes dossiers en CSV' + download_all_dossiers: 'Télécharger mes dossiers' + limit_excess_download_all_dossiers: 'Limite de dossiers fixée à 200 pour le téléchargement' + format_csv: 'Au format CSV' + format_xlsx: 'Au format XLSX' + format_ods: 'Au format ODS' research: placeholder: 'Rechercher un dossier ...' filter_procedure: @@ -42,4 +46,4 @@ fr: title: Dossiers onglet_accompagnateurs: add: - title: 'Ajouter un accompagnateur' \ No newline at end of file + title: 'Ajouter un accompagnateur' diff --git a/config/locales/dynamics/fr_opensimplif.yml b/config/locales/dynamics/fr_opensimplif.yml index 09b11be21..84a95896a 100644 --- a/config/locales/dynamics/fr_opensimplif.yml +++ b/config/locales/dynamics/fr_opensimplif.yml @@ -1,4 +1,4 @@ -fr_opensimplif: +fr: dynamics: page_title: OpenSimplif contact_email: simplification.sgmap@modernisation.gouv.fr @@ -12,7 +12,11 @@ fr_opensimplif: pref_list: title: 'Affichage du tableau de bord' description: 'Ce menu vous permet de choisir les différentes colonnes que vous souhaitez voir apparaître dans le tableau de bord.' - download_all_dossiers: 'Exporter' + download_all_dossiers: 'Télécharger' + limit_excess_download_all_dossiers: 'Limite de téléchargemebt fixée à 200' + format_csv: 'Au format CSV' + format_xlsx: 'Au format XLSX' + format_ods: 'Au format ODS' research: placeholder: 'Rechercher une simplification ...' filter_procedure: diff --git a/config/routes.rb b/config/routes.rb index 744c3ea61..d9daff608 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -75,6 +75,7 @@ Rails.application.routes.draw do post '/recapitulatif/submit' => 'recapitulatif#submit' post '/commentaire' => 'commentaires#create' + resources :commentaires, only: [:index] get '/carte/position' => 'carte#get_position' post '/carte/qp' => 'carte#get_qp' @@ -162,6 +163,7 @@ Rails.application.routes.draw do post 'close' => 'dossiers#close' put 'follow' => 'dossiers#follow' + resources :commentaires, only: [:index] end diff --git a/db/migrate/20161110082244_add_champ_id_to_commentaires.rb b/db/migrate/20161110082244_add_champ_id_to_commentaires.rb new file mode 100644 index 000000000..1dfc9595f --- /dev/null +++ b/db/migrate/20161110082244_add_champ_id_to_commentaires.rb @@ -0,0 +1,7 @@ +class AddChampIdToCommentaires < ActiveRecord::Migration + def change + change_table :commentaires do |t| + t.references :champ, null: true, index: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 7ef1e735e..724af68f2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161102154835) do +ActiveRecord::Schema.define(version: 20161115053251) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -62,6 +62,12 @@ ActiveRecord::Schema.define(version: 20161102154835) do add_index "administrations", ["email"], name: "index_administrations_on_email", unique: true, using: :btree add_index "administrations", ["reset_password_token"], name: "index_administrations_on_reset_password_token", unique: true, using: :btree + create_table "ar_internal_metadata", primary_key: "key", force: :cascade do |t| + t.string "value" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "assign_tos", id: false, force: :cascade do |t| t.integer "gestionnaire_id" t.integer "procedure_id" @@ -112,8 +118,10 @@ ActiveRecord::Schema.define(version: 20161102154835) do t.integer "dossier_id" t.datetime "updated_at", null: false t.integer "piece_justificative_id" + t.integer "champ_id" end + add_index "commentaires", ["champ_id"], name: "index_commentaires_on_champ_id", using: :btree add_index "commentaires", ["dossier_id"], name: "index_commentaires_on_dossier_id", using: :btree create_table "dossiers", force: :cascade do |t| @@ -306,7 +314,6 @@ ActiveRecord::Schema.define(version: 20161102154835) 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 diff --git a/spec/controllers/api/v1/dossiers_controller_spec.rb b/spec/controllers/api/v1/dossiers_controller_spec.rb index 5295b7d49..0c00308bb 100644 --- a/spec/controllers/api/v1/dossiers_controller_spec.rb +++ b/spec/controllers/api/v1/dossiers_controller_spec.rb @@ -115,7 +115,7 @@ describe API::V1::DossiersController do let!(:dossier) { Timecop.freeze(date_creation) { create(:dossier, :with_entreprise, procedure: procedure) } } let(:dossier_id) { dossier.id } let(:body) { JSON.parse(retour.body, symbolize_names: true) } - let(:field_list) { [:id, :created_at, :updated_at, :archived, :mandataire_social, :total_commentaire, :entreprise, :etablissement, :cerfa, :types_de_piece_justificative, :pieces_justificatives, :champs, :champs_private, :commentaires, :state] } + let(:field_list) { [:id, :created_at, :updated_at, :archived, :mandataire_social, :entreprise, :etablissement, :cerfa, :types_de_piece_justificative, :pieces_justificatives, :champs, :champs_private, :commentaires, :state] } subject { body[:dossier] } it 'return REST code 200', :show_in_doc do @@ -127,7 +127,6 @@ describe API::V1::DossiersController do it { expect(subject[:updated_at]).to eq('2008-09-01T08:05:00.000Z') } it { expect(subject[:archived]).to eq(dossier.archived) } it { expect(subject[:mandataire_social]).to eq(dossier.mandataire_social) } - it { expect(subject[:total_commentaire]).to eq(dossier.total_commentaire) } it { expect(subject.keys).to match_array(field_list) } diff --git a/spec/features/backoffice/flux_de_commentaires_spec.rb b/spec/features/backoffice/flux_de_commentaires_spec.rb new file mode 100644 index 000000000..5550feb62 --- /dev/null +++ b/spec/features/backoffice/flux_de_commentaires_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +feature 'backoffice: flux de commentaires' do + let(:gestionnaire) { create(:gestionnaire) } + let(:dossier) { create(:dossier, :with_entreprise) } + let(:dossier_id) { dossier.id } + + let(:champ1) { dossier.champs.first } + let(:champ2) { create(:champ, dossier: dossier, type_de_champ: create(:type_de_champ_public, libelle: "subtitle")) } + + let!(:commentaire1) { create(:commentaire, dossier: dossier, champ: champ1) } + let!(:commentaire2) { create(:commentaire, dossier: dossier) } + let!(:commentaire3) { create(:commentaire, dossier: dossier, champ: champ2) } + let!(:commentaire4) { create(:commentaire, dossier: dossier, champ: champ1) } + + before do + login_as gestionnaire, scope: :gestionnaire + visit backoffice_dossier_path(dossier) + end + + scenario "seuls les commentaires généraux sont affichés" do + comments = find("#commentaires_flux") + expect(comments).to have_selector(".description", count: 1) + end + + scenario "affichage des commentaires du champs", js: true do + find("#liste_champs th", text: champ1.libelle).click_link("COM") + expect(page).to have_css("#modalCommentairesDossierParChamp.in") + + modal = find("#modalCommentairesDossierParChamp") + expect(modal).to have_css(".description", count: 2) + end + + scenario "crée un commentaire sur un champ", js: true do + # ouverture modale + find("#liste_champs th", text: champ1.libelle).click_link("COM") + + # ajout du commentaire + form = find("#modalCommentairesDossierParChamp").find("#commentaire_new") + form.fill_in("texte_commentaire", with: "le corps du commentaire sur le champ #{champ1.libelle}") + form.click_on("Poster") + + # le commentaire ne s'ajoute pas aux commentaires généraux + comments = find("#commentaires_flux") + expect(comments).to have_selector(".description", count: 1) + + # ajout du commentaire aux commentaires du champs + find("#liste_champs th", text: champ1.libelle).click_link("COM") + modal = find("#modalCommentairesDossierParChamp") + expect(modal).to have_css(".description", count: 3) + end +end diff --git a/spec/features/users/flux_de_commentaires_spec.rb b/spec/features/users/flux_de_commentaires_spec.rb new file mode 100644 index 000000000..7a6e7fe61 --- /dev/null +++ b/spec/features/users/flux_de_commentaires_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +feature 'users: flux de commentaires' do + let(:user) { create(:user) } + let(:dossier) { create(:dossier, :with_entreprise, user: user, state: "replied") } + let(:dossier_id) { dossier.id } + + let(:champ1) { dossier.champs.first } + let(:champ2) { create(:champ, dossier: dossier, type_de_champ: create(:type_de_champ_public, libelle: "subtitle")) } + + let!(:commentaire1) { create(:commentaire, dossier: dossier, champ: champ1) } + let!(:commentaire2) { create(:commentaire, dossier: dossier) } + let!(:commentaire3) { create(:commentaire, dossier: dossier, champ: champ2) } + let!(:commentaire4) { create(:commentaire, dossier: dossier, champ: champ1) } + + before do + login_as user, scope: :user + visit users_dossier_recapitulatif_path(dossier) + end + + scenario "seuls les commentaires généraux sont affichés" do + comments = find("#commentaires_flux") + expect(comments).to have_selector(".description", count: 1) + end + + scenario "affichage des commentaires du champs", js: true do + th = find("#liste_champs th", text: champ1.libelle) + th.click_link("COM") + expect(page).to have_css("#modalCommentairesDossierParChamp.in") + + modal = find("#modalCommentairesDossierParChamp") + expect(modal).to have_css(".description", count: 2) + end + + scenario "crée un commentaire sur un champ", js: true do + # ouverture modale + find("#liste_champs th", text: champ1.libelle).click_link("COM") + + # ajout du commentaire + form = find("#modalCommentairesDossierParChamp").find("#commentaire_new") + form.fill_in("texte_commentaire", with: "le corps du commentaire sur le champ #{champ1.libelle}") + form.click_on("Poster") + + # le commentaire ne s'ajoute pas aux commentaires généraux + comments = find("#commentaires_flux") + expect(comments).to have_selector(".description", count: 1) + + # ajout du commentaire aux commentaires du champs + find("#liste_champs th", text: champ1.libelle).click_link("COM") + modal = find("#modalCommentairesDossierParChamp") + expect(modal).to have_css(".description", count: 3) + end +end diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index b4ee3c005..6cc4ebed6 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -578,45 +578,192 @@ describe Dossier do end end - describe '#as_csv?' do + describe '#convert_specific_hash_values_to_string(hash_to_convert)' do let(:procedure) { create(:procedure) } let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } - subject { dossier.as_csv } + let(:dossier_serialized_attributes) { DossierSerializer.new(dossier).attributes } - it { expect(subject[:archived]).to be_falsey } - it { expect(subject['etablissement.siret']).to eq('44011762001530') } - it { expect(subject['etablissement.siege_social']).to be_truthy } - it { expect(subject['etablissement.naf']).to eq('4950Z') } - it { expect(subject['etablissement.libelle_naf']).to eq('Transports par conduites') } - it { expect(subject['etablissement.adresse']).to eq("GRTGAZ IMMEUBLE BORA 6 RUE RAOUL NORDLING 92270 BOIS COLOMBES") } - it { expect(subject['etablissement.numero_voie']).to eq('6') } - it { expect(subject['etablissement.type_voie']).to eq('RUE') } - it { expect(subject['etablissement.nom_voie']).to eq('RAOUL NORDLING') } - it { expect(subject['etablissement.complement_adresse']).to eq('IMMEUBLE BORA') } - it { expect(subject['etablissement.code_postal']).to eq('92270') } - it { expect(subject['etablissement.localite']).to eq('BOIS COLOMBES') } - it { expect(subject['etablissement.code_insee_localite']).to eq('92009') } - it { expect(subject['entreprise.siren']).to eq('440117620') } - it { expect(subject['entreprise.capital_social']).to eq(537100000) } - it { expect(subject['entreprise.numero_tva_intracommunautaire']).to eq('FR27440117620') } - it { expect(subject['entreprise.forme_juridique']).to eq("SA à conseil d'administration (s.a.i.)") } - it { expect(subject['entreprise.forme_juridique_code']).to eq('5599') } - it { expect(subject['entreprise.nom_commercial']).to eq('GRTGAZ') } - it { expect(subject['entreprise.raison_sociale']).to eq('GRTGAZ') } - it { expect(subject['entreprise.siret_siege_social']).to eq('44011762001530') } - it { expect(subject['entreprise.code_effectif_entreprise']).to eq('51') } - it { expect(subject['entreprise.date_creation']).to eq('Thu, 28 Jan 2016 10:16:29 UTC +00:0') } - it { expect(subject['entreprise.nom']).to be_nil } - it { expect(subject['entreprise.prenom']).to be_nil } + subject { dossier.convert_specific_hash_values_to_string(dossier_serialized_attributes) } + + it { expect(dossier_serialized_attributes[:id]).to be_an(Integer) } + it { expect(dossier_serialized_attributes[:created_at]).to be_a(Time) } + it { expect(dossier_serialized_attributes[:updated_at]).to be_a(Time) } + it { expect(dossier_serialized_attributes[:archived]).to be_in([true, false]) } + it { expect(dossier_serialized_attributes[:mandataire_social]).to be_in([true, false]) } + it { expect(dossier_serialized_attributes[:state]).to be_a(String) } + + it { expect(subject[:id]).to be_a(String) } + it { expect(subject[:created_at]).to be_a(Time) } + it { expect(subject[:updated_at]).to be_a(Time) } + it { expect(subject[:archived]).to be_a(String) } + it { expect(subject[:mandataire_social]).to be_a(String) } + it { expect(subject[:state]).to be_a(String) } + end + + describe '#convert_specific_array_values_to_string(array_to_convert)' do + let(:procedure) { create(:procedure) } + let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } + let(:dossier_data_with_champs) { dossier.data_with_champs } + + subject { dossier.convert_specific_hash_values_to_string(dossier_data_with_champs) } + end + + describe '#export_entreprise_data' do + let(:procedure) { create(:procedure) } + let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } + + subject { dossier.export_entreprise_data } + + it { expect(subject[:etablissement_siret]).to eq('44011762001530') } + it { expect(subject[:etablissement_siege_social]).to eq('true') } + it { expect(subject[:etablissement_naf]).to eq('4950Z') } + it { expect(subject[:etablissement_libelle_naf]).to eq('Transports par conduites') } + it { expect(subject[:etablissement_adresse]).to eq('GRTGAZ IMMEUBLE BORA 6 RUE RAOUL NORDLING 92270 BOIS COLOMBES') } + it { expect(subject[:etablissement_numero_voie]).to eq('6') } + it { expect(subject[:etablissement_type_voie]).to eq('RUE') } + it { expect(subject[:etablissement_nom_voie]).to eq('RAOUL NORDLING') } + it { expect(subject[:etablissement_complement_adresse]).to eq('IMMEUBLE BORA') } + it { expect(subject[:etablissement_code_postal]).to eq('92270') } + it { expect(subject[:etablissement_localite]).to eq('BOIS COLOMBES') } + it { expect(subject[:etablissement_code_insee_localite]).to eq('92009') } + it { expect(subject[:entreprise_siren]).to eq('440117620') } + it { expect(subject[:entreprise_capital_social]).to eq('537100000') } + it { expect(subject[:entreprise_numero_tva_intracommunautaire]).to eq('FR27440117620') } + it { expect(subject[:entreprise_forme_juridique]).to eq("SA à conseil d'administration (s.a.i.)") } + it { expect(subject[:entreprise_forme_juridique_code]).to eq('5599') } + it { expect(subject[:entreprise_nom_commercial]).to eq('GRTGAZ') } + it { expect(subject[:entreprise_raison_sociale]).to eq('GRTGAZ') } + it { expect(subject[:entreprise_siret_siege_social]).to eq('44011762001530') } + it { expect(subject[:entreprise_code_effectif_entreprise]).to eq('51') } + it { expect(subject[:entreprise_date_creation]).to eq('Thu, 28 Jan 2016 10:16:29 UTC +00:0') } + it { expect(subject[:entreprise_nom]).to be_nil } + it { expect(subject[:entreprise_prenom]).to be_nil } + + it { expect(subject.count).to eq(EntrepriseSerializer.new(Entreprise.new).as_json[:entreprise].count + EtablissementSerializer.new(Etablissement.new).as_json[:etablissement].count) } + end + + describe '#export_default_columns' do + let(:procedure) { create(:procedure) } + let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } + subject { dossier.export_default_columns } + + it { expect(subject[:archived]).to eq('false') } + it { expect(subject[:etablissement_siret]).to eq('44011762001530') } + it { expect(subject[:etablissement_siege_social]).to eq('true') } + it { expect(subject[:etablissement_naf]).to eq('4950Z') } + it { expect(subject[:etablissement_libelle_naf]).to eq('Transports par conduites') } + it { expect(subject[:etablissement_adresse]).to eq('GRTGAZ IMMEUBLE BORA 6 RUE RAOUL NORDLING 92270 BOIS COLOMBES') } + it { expect(subject[:etablissement_numero_voie]).to eq('6') } + it { expect(subject[:etablissement_type_voie]).to eq('RUE') } + it { expect(subject[:etablissement_nom_voie]).to eq('RAOUL NORDLING') } + it { expect(subject[:etablissement_complement_adresse]).to eq('IMMEUBLE BORA') } + it { expect(subject[:etablissement_code_postal]).to eq('92270') } + it { expect(subject[:etablissement_localite]).to eq('BOIS COLOMBES') } + it { expect(subject[:etablissement_code_insee_localite]).to eq('92009') } + it { expect(subject[:entreprise_siren]).to eq('440117620') } + it { expect(subject[:entreprise_capital_social]).to eq('537100000') } + it { expect(subject[:entreprise_numero_tva_intracommunautaire]).to eq('FR27440117620') } + it { expect(subject[:entreprise_forme_juridique]).to eq("SA à conseil d'administration (s.a.i.)") } + it { expect(subject[:entreprise_forme_juridique_code]).to eq('5599') } + it { expect(subject[:entreprise_nom_commercial]).to eq('GRTGAZ') } + it { expect(subject[:entreprise_raison_sociale]).to eq('GRTGAZ') } + it { expect(subject[:entreprise_siret_siege_social]).to eq('44011762001530') } + it { expect(subject[:entreprise_code_effectif_entreprise]).to eq('51') } + it { expect(subject[:entreprise_date_creation]).to eq('Thu, 28 Jan 2016 10:16:29 UTC +00:0') } + it { expect(subject[:entreprise_nom]).to be_nil } + it { expect(subject[:entreprise_prenom]).to be_nil } context 'when dossier does not have enterprise' do let(:dossier) { create(:dossier, user: user, procedure: procedure) } - subject { dossier.as_csv } + subject { dossier.export_default_columns } - it { expect(subject[:archived]).to be_falsey } + it { expect(subject[:archived]).to eq('false') } end end + describe '#export_headers' do + let(:procedure) { create(:procedure, :with_type_de_champ) } + let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } + subject { dossier.export_headers } + + it { expect(subject).to include(:description) } + it { expect(subject.count).to eq(DossierProcedureSerializer.new(dossier).attributes.count + dossier.procedure.types_de_champ.count + dossier.export_entreprise_data.count) } + end + + describe '#data_with_champs' do + let(:procedure) { create(:procedure, :with_type_de_champ) } + let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } + subject { dossier.data_with_champs } + + it { expect(subject[0]).to be_a_kind_of(Integer) } + it { expect(subject[1]).to be_a_kind_of(Time) } + it { expect(subject[2]).to be_a_kind_of(Time) } + it { expect(subject[3]).to be_in([true, false]) } + it { expect(subject[4]).to be_in([true, false]) } + it { expect(subject[5]).to eq("draft") } + it { expect(subject.count).to eq(DossierProcedureSerializer.new(dossier).attributes.count + dossier.procedure.types_de_champ.count + dossier.export_entreprise_data.count) } + end + + describe '#Dossier.to_csv' do + let!(:procedure) { create(:procedure) } + let!(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } + + subject do + dossier_hash = {} + dossier_splitted = Dossier.to_csv.split("\n").map { |cell| cell.split(",") } + index = 0 + dossier_splitted[0].each do |column| + dossier_hash.store(column.to_sym, dossier_splitted[1][index]) + index = index + 1 + end + dossier_hash + end + + it { expect(subject[:archived]).to eq('false') } + it { expect(subject[:etablissement_siret]).to eq('44011762001530') } + it { expect(subject[:etablissement_siege_social]).to eq('true') } + it { expect(subject[:etablissement_naf]).to eq('4950Z') } + it { expect(subject[:etablissement_libelle_naf]).to eq('Transports par conduites') } + it { expect(subject[:etablissement_adresse]).to eq('GRTGAZ IMMEUBLE BORA 6 RUE RAOUL NORDLING 92270 BOIS COLOMBES') } + it { expect(subject[:etablissement_numero_voie]).to eq('6') } + it { expect(subject[:etablissement_type_voie]).to eq('RUE') } + it { expect(subject[:etablissement_nom_voie]).to eq('RAOUL NORDLING') } + it { expect(subject[:etablissement_complement_adresse]).to eq('IMMEUBLE BORA') } + it { expect(subject[:etablissement_code_postal]).to eq('92270') } + it { expect(subject[:etablissement_localite]).to eq('BOIS COLOMBES') } + it { expect(subject[:etablissement_code_insee_localite]).to eq('92009') } + it { expect(subject[:entreprise_siren]).to eq('440117620') } + it { expect(subject[:entreprise_capital_social]).to eq('537100000') } + it { expect(subject[:entreprise_numero_tva_intracommunautaire]).to eq('FR27440117620') } + it { expect(subject[:entreprise_forme_juridique]).to eq("SA à conseil d'administration (s.a.i.)") } + it { expect(subject[:entreprise_forme_juridique_code]).to eq('5599') } + it { expect(subject[:entreprise_nom_commercial]).to eq('GRTGAZ') } + it { expect(subject[:entreprise_raison_sociale]).to eq('GRTGAZ') } + it { expect(subject[:entreprise_siret_siege_social]).to eq('44011762001530') } + it { expect(subject[:entreprise_code_effectif_entreprise]).to eq('51') } + it { expect(subject[:entreprise_date_creation]).to eq('2016-01-28 10:16:29 UTC') } + it { expect(subject[:entreprise_nom]).to be_nil } + it { expect(subject[:entreprise_prenom]).to be_nil } + end + + describe '#Dossier.to_xlsx' do + let!(:procedure) { create(:procedure) } + let!(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } + + subject { Dossier.to_xlsx } + + it { expect(subject).is_a?(String) } + end + + describe '#Dossier.to_ods' do + let!(:procedure) { create(:procedure) } + let!(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } + + subject { Dossier.to_ods } + + it { expect(subject).is_a?(String) } + end + describe '#reset!' do let!(:dossier) { create :dossier, :with_entreprise, autorisation_donnees: true } let!(:rna_information) { create :rna_information, entreprise: dossier.entreprise } diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index db62fda24..6b7434208 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -17,7 +17,6 @@ describe Procedure do it { is_expected.to have_db_column(:description) } it { is_expected.to have_db_column(:organisation) } it { is_expected.to have_db_column(:direction) } - it { is_expected.to have_db_column(:test) } it { is_expected.to have_db_column(:euro_flag) } it { is_expected.to have_db_column(:logo) } it { is_expected.to have_db_column(:logo_secure_token) }