diff --git a/.circleci/config.yml b/.circleci/config.yml index 90725ad32..45417e72c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,7 +64,7 @@ jobs: - "0a:67:42:7d:7e:b7:e1:3c:48:8f:bf:68:10:51:a8:44" - deploy: command: | - if [ "${CIRCLE_BRANCH}" == "staging" ]; then + if [ "${CIRCLE_BRANCH}" == "develop" ]; then bundle exec rake deploy_ha fi diff --git a/.scss-lint.yml b/.scss-lint.yml index 7ee185bb3..835a46690 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -1,4 +1,4 @@ -exclude: 'app/assets/stylesheets/reset.scss' +exclude: 'app/assets/stylesheets/new_design/reset.scss' linters: BangFormat: diff --git a/app/assets/images/mailer/gestionnaire_mailer/logo.png b/app/assets/images/mailer/gestionnaire_mailer/logo.png new file mode 100755 index 000000000..b8c63e7c3 Binary files /dev/null and b/app/assets/images/mailer/gestionnaire_mailer/logo.png differ diff --git a/app/assets/stylesheets/_constants.scss b/app/assets/stylesheets/_constants.scss index 1208ef340..119b9eaea 100644 --- a/app/assets/stylesheets/_constants.scss +++ b/app/assets/stylesheets/_constants.scss @@ -3,5 +3,3 @@ $light-blue: #F2F6FA; // Bootstrap constants $font-size-base: 16px; - -$page-width: 1040px; diff --git a/app/assets/stylesheets/_placeholders.scss b/app/assets/stylesheets/_placeholders.scss deleted file mode 100644 index 71870ccec..000000000 --- a/app/assets/stylesheets/_placeholders.scss +++ /dev/null @@ -1,14 +0,0 @@ -%horizontal-list { - list-style-type: none; - margin: 0; - padding: 0; - font-size: 0px; -} - -%horizontal-list-item { - display: inline-block; - - &:last-of-type { - margin-right: 0; - } -} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 33338dbdb..aac24c497 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -13,13 +13,8 @@ // file per style scope. // // = require _card -// = require _colors -// = require _constants // = require _helpers -// = require _mixins -// = require _placeholders // = require _turbolinks -// = require _typography // = require admin_procedures_modal // = require admin_type_de_champ // = require backoffice @@ -31,9 +26,7 @@ // = require dossier_show // = require dossiers // = require etapes -// = require fonts // = require france_connect_particulier -// = require landing // = require left_panel // = require login // = require main_container @@ -47,7 +40,6 @@ // = require recapitulatif // = require search // = require siret -// = require stats // = require support_navigator_banner // = require switch_menu // = require typeahead diff --git a/app/assets/stylesheets/new_application.scss b/app/assets/stylesheets/new_application.scss deleted file mode 100644 index db2e8c65c..000000000 --- a/app/assets/stylesheets/new_application.scss +++ /dev/null @@ -1,10 +0,0 @@ -// = require reset -// = require custom_reset -// = require common -// = require utils -// = require fonts -// = require new_alert -// = require new_header -// = require new_footer -// = require landing -// = require navbar diff --git a/app/assets/stylesheets/_colors.scss b/app/assets/stylesheets/new_design/_colors.scss similarity index 100% rename from app/assets/stylesheets/_colors.scss rename to app/assets/stylesheets/new_design/_colors.scss diff --git a/app/assets/stylesheets/new_design/_constants.scss b/app/assets/stylesheets/new_design/_constants.scss new file mode 100644 index 000000000..897512e1d --- /dev/null +++ b/app/assets/stylesheets/new_design/_constants.scss @@ -0,0 +1,3 @@ +$page-width: 1040px; + +$default-padding: 15px; diff --git a/app/assets/stylesheets/_mixins.scss b/app/assets/stylesheets/new_design/_mixins.scss similarity index 100% rename from app/assets/stylesheets/_mixins.scss rename to app/assets/stylesheets/new_design/_mixins.scss diff --git a/app/assets/stylesheets/new_design/_placeholders.scss b/app/assets/stylesheets/new_design/_placeholders.scss new file mode 100644 index 000000000..fbb5bf9d5 --- /dev/null +++ b/app/assets/stylesheets/new_design/_placeholders.scss @@ -0,0 +1,21 @@ +@import "constants"; +@import "mixins"; + +%horizontal-list { + list-style-type: none; + margin: 0; + padding: 0; + font-size: 0px; + display: flex; + flex-wrap: wrap; +} + +%horizontal-list-item { + display: inline-block; +} + +%page-width-container { + @include horizontal-padding($default-padding); + max-width: $page-width + 2 * $default-padding; + margin: 0 auto; +} diff --git a/app/assets/stylesheets/_typography.scss b/app/assets/stylesheets/new_design/_typography.scss similarity index 100% rename from app/assets/stylesheets/_typography.scss rename to app/assets/stylesheets/new_design/_typography.scss diff --git a/app/assets/stylesheets/new_design/avis_sign_up.scss b/app/assets/stylesheets/new_design/avis_sign_up.scss new file mode 100644 index 000000000..9c94d35af --- /dev/null +++ b/app/assets/stylesheets/new_design/avis_sign_up.scss @@ -0,0 +1,91 @@ +@import "typography"; +@import "colors"; + +.avis-sign-up { + display: flex; + + .left, + .right { + width: 50%; + padding: 60px 86px; + } + + .left { + p { + margin: auto; + max-width: 410px; + text-align: center; + } + + .description { + font-size: 30px; + line-height: 1.3; + } + + .dossier { + font-size: 18px; + font-weight: bold; + margin-top: 15px; + } + } + + .right { + background-color: $light-grey; + + h1 { + font-size: 36px; + font-weight: bold; + margin-bottom: 60px; + } + + form { + max-width: 420px; + } + + label, + input { + display: block; + width: 100%; + } + + label { + font-size: 14px; + line-height: 1.57; + margin: 24px 0 8px; + } + + input { + border: solid 1px $border-grey; + border-radius: 4px; + height: 56px; + padding: 0 15px; + font-family: Muli; + font-size: 14px; + + &:disabled { + background-color: $border-grey; + } + } + + button { + display: inline-block; + height: 60px; + line-height: 60px; + border: none; + border-radius: 60px; + background-color: $blue; + color: #FFFFFF; + font-size: 16px; + text-align: center; + width: 100%; + margin: 55px 0; + + &:hover { + color: #FFFFFF; + text-decoration: none; + background-color: $light-blue; + cursor: pointer; + } + } + } +} diff --git a/app/assets/stylesheets/new_design/beta.scss b/app/assets/stylesheets/new_design/beta.scss new file mode 100644 index 000000000..c8480d52d --- /dev/null +++ b/app/assets/stylesheets/new_design/beta.scss @@ -0,0 +1,15 @@ +#beta { + text-align: center; + text-transform: uppercase; + position: fixed; + bottom: 26px; + right: -35px; + transform: rotate(-45deg); + width: 150px; + background-color: #008CBA; + color: #FFFFFF; + padding: 5px; + font-size: 15px; + font-weight: 700; + z-index: 10; +} diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/new_design/common.scss similarity index 100% rename from app/assets/stylesheets/common.scss rename to app/assets/stylesheets/new_design/common.scss diff --git a/app/assets/stylesheets/custom_reset.scss b/app/assets/stylesheets/new_design/custom_reset.scss similarity index 100% rename from app/assets/stylesheets/custom_reset.scss rename to app/assets/stylesheets/new_design/custom_reset.scss diff --git a/app/assets/stylesheets/fonts.scss b/app/assets/stylesheets/new_design/fonts.scss similarity index 100% rename from app/assets/stylesheets/fonts.scss rename to app/assets/stylesheets/new_design/fonts.scss diff --git a/app/assets/stylesheets/landing.scss b/app/assets/stylesheets/new_design/landing.scss similarity index 64% rename from app/assets/stylesheets/landing.scss rename to app/assets/stylesheets/new_design/landing.scss index e5f6b2251..b5fa9cb12 100644 --- a/app/assets/stylesheets/landing.scss +++ b/app/assets/stylesheets/new_design/landing.scss @@ -12,12 +12,30 @@ } .landing-panel-inner-content { - width: $page-width; - margin: 0 auto; + @extend %page-width-container; +} + +$landing-breakpoint: 1040px; + +.hero-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + + @media (max-width: $landing-breakpoint) { + justify-content: center; + } +} + +.hero-text { + max-width: 500px; + + @media (max-width: $landing-breakpoint) { + margin: auto; + } } .hero-tagline { - width: 500px; font-size: 30px; margin-bottom: 0px; } @@ -29,12 +47,16 @@ font-weight: bold; } -.hero-text { - width: 500px; -} - .hero-illustration { - width: 500px; + max-width: 500px; + + img { + max-width: 100%; + } + + @media (max-width: 1030px) { + margin: auto; + } } .hero-button { @@ -69,6 +91,7 @@ } .landing-panel-title { + width: 100%; font-size: 30px; font-weight: normal; text-align: center; @@ -86,15 +109,22 @@ .features { @extend %horizontal-list; -} + width: 100%; + align-items: baseline; + justify-content: space-between; -$feature-width: 320px; -$features-count: 3; + @media (max-width: $landing-breakpoint) { + justify-content: center; + } +} .feature { @extend %horizontal-list-item; - width: $feature-width; - margin-right: calc((#{$page-width} - (#{$feature-width} * #{$features-count})) / (#{$features-count} - 1)); + width: 320px; + + @media (max-width: $landing-breakpoint) { + margin: 15px 20px; + } } .feature-text { @@ -116,18 +146,27 @@ $features-count: 3; .quotes { @extend %horizontal-list; -} + width: 100%; + justify-content: space-between; -$quote-width: 500px; -$quote-count: 2; + @media (max-width: $landing-breakpoint) { + justify-content: center; + } +} .quote { @extend %horizontal-list-item; - width: $quote-width; - margin-right: calc((#{$page-width} - (#{$quote-width} * #{$quote-count}))/ (#{$quote-count} - 1)); + max-width: 500px; background-color: #FFFFFF; box-shadow: 0 4px 16px 0 rgba(153, 153, 153, 0.2); padding: 24px; + display: flex; + justify-content: flex-start; + align-items: flex-start; + + @media (max-width: $landing-breakpoint) { + margin: 15px 0; + } } .quote-quotation-mark { @@ -136,13 +175,16 @@ $quote-count: 2; .quote-content { font-size: 18px; - width: 388px; margin-bottom: 24px; } +.quote-content-wrapper { + margin-left: 20px; + width: 100%; +} + .quote-author { font-size: 14px; - margin-left: 64px; } .quote-author-name { @@ -155,16 +197,18 @@ $quote-count: 2; .numbers { @extend %horizontal-list; + justify-content: space-around; + width: 100%; } -$number-width: 320px; -$number-count: 3; - .number { @extend %horizontal-list-item; - width: $number-width; - margin-right: calc((#{$page-width} - (#{$number-width} * #{$number-count}))/ (#{$number-count} - 1)); + width: 320px; text-align: center; + + @media (max-width: $landing-breakpoint) { + margin-bottom: 15px; + } } .number-value { @@ -178,25 +222,33 @@ $number-count: 3; font-size: 20px; } +$users-breakpoint: 950px; + .users { @extend %horizontal-list; -} + justify-content: space-between; + width: 100%; -$image-width: 170px; -$images-total-width: $image-width * 5; -$images-count: 5; + @media (max-width: $users-breakpoint) { + justify-content: space-around; + } +} .user { @extend %horizontal-list-item; - margin-right: calc((#{$page-width} - (#{$images-total-width}))/ (#{$images-count} - 1)); + width: 170px; &:hover { opacity: 0.6; } + + @media (max-width: $users-breakpoint) { + margin: 0 15px 15px; + } } .user-image { - width: $image-width; + width: 170px; } .cta-panel { @@ -204,6 +256,13 @@ $images-count: 5; color: #FFFFFF; } +.cta-panel-wrapper { + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} + .cta-panel-title { font-size: 24px; font-weight: bold; @@ -212,22 +271,21 @@ $images-count: 5; .cta-panel-explanation { font-size: 24px; - margin-bottom: 0; + margin-bottom: 10px; } -$cta-panel-button-height: 60px; $cta-panel-button-border-size: 2px; .cta-panel-button { @include horizontal-padding(30px); display: block; - height: $cta-panel-button-height; - line-height: $cta-panel-button-height - (2 * $cta-panel-button-border-size); - border-radius: $cta-panel-button-height; + padding: 10px; + border-radius: 100px; border: $cta-panel-button-border-size solid #FFFFFF; color: #FFFFFF; font-size: 24px; + text-align: center; &:hover { color: #FFFFFF; diff --git a/app/assets/stylesheets/new_alert.scss b/app/assets/stylesheets/new_design/new_alert.scss similarity index 100% rename from app/assets/stylesheets/new_alert.scss rename to app/assets/stylesheets/new_design/new_alert.scss diff --git a/app/assets/stylesheets/new_design/new_application.scss b/app/assets/stylesheets/new_design/new_application.scss new file mode 100644 index 000000000..82fb5fc2a --- /dev/null +++ b/app/assets/stylesheets/new_design/new_application.scss @@ -0,0 +1,6 @@ +// = require ./reset +// = require ./custom_reset +// = require ./common +// = require ./utils +// = require ./fonts +// = require_tree . diff --git a/app/assets/stylesheets/new_footer.scss b/app/assets/stylesheets/new_design/new_footer.scss similarity index 76% rename from app/assets/stylesheets/new_footer.scss rename to app/assets/stylesheets/new_design/new_footer.scss index ccfab7146..c88b853cd 100644 --- a/app/assets/stylesheets/new_footer.scss +++ b/app/assets/stylesheets/new_design/new_footer.scss @@ -10,23 +10,25 @@ } .footer-inner-content { - width: $page-width; - margin: 0 auto; + @extend %page-width-container; } .footer-columns { @extend %horizontal-list; + justify-content: flex-start; } -$footer-column-width: 320px; -$footer-column-count: 3; - .footer-column { @extend %horizontal-list-item; - width: $footer-column-width; - margin-right: calc((#{$page-width} - (#{$footer-column-width} * #{$footer-column-count})) / (#{$footer-column-count} - 1)); font-size: 14px; vertical-align: top; + flex-grow: 1; + min-width: 320px; + + @media (max-width: 1000px) { + width: 100%; + margin-bottom: 14px; + } } .footer-logos, diff --git a/app/assets/stylesheets/new_header.scss b/app/assets/stylesheets/new_design/new_header.scss similarity index 89% rename from app/assets/stylesheets/new_header.scss rename to app/assets/stylesheets/new_design/new_header.scss index a76bbf9a1..aa542550e 100644 --- a/app/assets/stylesheets/new_header.scss +++ b/app/assets/stylesheets/new_design/new_header.scss @@ -1,6 +1,7 @@ @import "constants"; @import "colors"; @import "mixins"; +@import "placeholders"; // FIXME: Rename when the header is generalized .new-header { @@ -13,8 +14,9 @@ } .header-inner-content { - width: $page-width; - margin: 0 auto; + @extend %page-width-container; + display: flex; + justify-content: space-between; } .header-logo { diff --git a/app/assets/stylesheets/reset.scss b/app/assets/stylesheets/new_design/reset.scss similarity index 100% rename from app/assets/stylesheets/reset.scss rename to app/assets/stylesheets/new_design/reset.scss diff --git a/app/assets/stylesheets/stats.scss b/app/assets/stylesheets/new_design/stats.scss similarity index 91% rename from app/assets/stylesheets/stats.scss rename to app/assets/stylesheets/new_design/stats.scss index 42fef6ac1..2f0b4f37a 100644 --- a/app/assets/stylesheets/stats.scss +++ b/app/assets/stylesheets/new_design/stats.scss @@ -1,5 +1,3 @@ -@import "card"; - $dark-grey: #333333; $light-grey: #999999; $blue: rgba(61, 149, 236, 1); @@ -8,12 +6,23 @@ $blue-hover: rgba(61, 149, 236, 0.8); $default-space: 15px; $new-h1-margin-bottom: 4 * $default-space; +$new-h2-margin-bottom: 3 * $default-space; -.new-h1 { +.new-h1, +.new-h2 { color: $dark-grey; text-align: center; - margin-top: 0; + font-weight: bold; +} + +.new-h1 { margin-bottom: $new-h1-margin-bottom; + font-size: 41px; +} + +.new-h2 { + margin-bottom: $new-h2-margin-bottom; + font-size: 36px; } $statistiques-padding-top: $default-space * 2; @@ -33,6 +42,7 @@ $statistiques-padding-top: $default-space * 2; $stat-card-margin-bottom: 3 * $default-space; .stat-card { + padding: 15px; margin-bottom: $stat-card-margin-bottom; border-radius: 5px; box-shadow: none; @@ -49,7 +59,7 @@ $stat-card-half-horizontal-spacing: 4 * $default-space; .stat-card-title { color: $dark-grey; font-size: 26px; - font-weight: 500; + font-weight: bold; width: 200px; } diff --git a/app/assets/stylesheets/utils.scss b/app/assets/stylesheets/new_design/utils.scss similarity index 81% rename from app/assets/stylesheets/utils.scss rename to app/assets/stylesheets/new_design/utils.scss index 04a4559bf..958f8ff9c 100644 --- a/app/assets/stylesheets/utils.scss +++ b/app/assets/stylesheets/new_design/utils.scss @@ -13,3 +13,7 @@ .center { text-align: center; } + +.hidden { + display: none; +} diff --git a/app/controllers/admin/mail_templates_controller.rb b/app/controllers/admin/mail_templates_controller.rb index 5967c3dd5..a0696fd49 100644 --- a/app/controllers/admin/mail_templates_controller.rb +++ b/app/controllers/admin/mail_templates_controller.rb @@ -2,29 +2,33 @@ class Admin::MailTemplatesController < AdminController before_action :retrieve_procedure def index - @mails = mails + @mail_templates = mail_templates end def edit - @mail_template = find_the_right_mail params[:id] - @mail_template_name = params[:id] + @mail_template = find_mail_template_by_slug(params[:id]) end def update - mail_template = find_the_right_mail params[:id] + mail_template = find_mail_template_by_slug(params[:id]) mail_template.update_attributes(update_params) redirect_to admin_procedure_mail_templates_path end private - def mails - %w(initiated received closed refused without_continuation) - .map { |name| @procedure.send(name + "_mail") } + def mail_templates + [ + @procedure.initiated_mail_template, + @procedure.received_mail_template, + @procedure.closed_mail_template, + @procedure.refused_mail_template, + @procedure.without_continuation_mail_template + ] end - def find_the_right_mail type - mails.find { |m| m.class.slug == type } + def find_mail_template_by_slug(slug) + mail_templates.find { |template| template.class.const_get(:SLUG) == slug } end def update_params diff --git a/app/controllers/admin/previsualisations_controller.rb b/app/controllers/admin/previsualisations_controller.rb index f3a78abf5..a2cb7efac 100644 --- a/app/controllers/admin/previsualisations_controller.rb +++ b/app/controllers/admin/previsualisations_controller.rb @@ -10,9 +10,6 @@ class Admin::PrevisualisationsController < AdminController @champs = @dossier.ordered_champs - @headers = @champs.inject([]) do |acc, champ| - acc.push(champ) if champ.type_champ == 'header_section' - acc - end + @headers = @champs.select { |champ| champ.type_champ == 'header_section' } end end diff --git a/app/controllers/api/statistiques_controller.rb b/app/controllers/api/statistiques_controller.rb index b920f47c6..fe12ffa2a 100644 --- a/app/controllers/api/statistiques_controller.rb +++ b/app/controllers/api/statistiques_controller.rb @@ -1,5 +1,4 @@ class API::StatistiquesController < ApplicationController - def dossiers_stats render json: { total: total_dossiers, diff --git a/app/controllers/api/v1/dossiers_controller.rb b/app/controllers/api/v1/dossiers_controller.rb index e77e11351..ddafcf82c 100644 --- a/app/controllers/api/v1/dossiers_controller.rb +++ b/app/controllers/api/v1/dossiers_controller.rb @@ -1,14 +1,10 @@ class API::V1::DossiersController < APIController - api :GET, '/procedures/:procedure_id/dossiers/', 'Liste de tous les dossiers d\'une procédure' param :procedure_id, Integer, desc: "L'identifiant de la procédure", required: true param :token, String, desc: "Token administrateur", required: true error code: 401, desc: "Non authorisé" error code: 404, desc: "Procédure inconnue" - meta champs: { - } - def index procedure = current_administrateur.procedures.find(params[:procedure_id]) dossiers = procedure.dossiers.where.not(state: :draft).paginate(page: params[:page]) @@ -25,10 +21,6 @@ class API::V1::DossiersController < APIController error code: 401, desc: "Non authorisé" error code: 404, desc: "Procédure ou dossier inconnu" - meta champs: { - - } - def show procedure = current_administrateur.procedures.find(params[:procedure_id]) dossier = procedure.dossiers.find(params[:id]) diff --git a/app/controllers/api/v1/procedures_controller.rb b/app/controllers/api/v1/procedures_controller.rb index b8a9fbdaa..cf370a8ff 100644 --- a/app/controllers/api/v1/procedures_controller.rb +++ b/app/controllers/api/v1/procedures_controller.rb @@ -5,10 +5,6 @@ class API::V1::ProceduresController < APIController error code: 401, desc: "Non authorisé" error code: 404, desc: "Procédure inconnue" - meta champs: { - - } - def show procedure = current_administrateur.procedures.find(params[:id]).decorate diff --git a/app/controllers/backoffice/avis_controller.rb b/app/controllers/backoffice/avis_controller.rb new file mode 100644 index 000000000..4d77496d2 --- /dev/null +++ b/app/controllers/backoffice/avis_controller.rb @@ -0,0 +1,95 @@ +class Backoffice::AvisController < ApplicationController + + before_action :authenticate_gestionnaire!, except: [:sign_up, :create_gestionnaire] + before_action :redirect_if_no_sign_up_needed, only: [:sign_up] + before_action :check_avis_exists_and_email_belongs_to_avis, only: [:sign_up, :create_gestionnaire] + + def create + avis = Avis.new(create_params.merge(claimant: current_gestionnaire)) + avis.dossier = dossier + + email = create_params[:email] + gestionnaire = Gestionnaire.find_by(email: email) + if gestionnaire + avis.gestionnaire = gestionnaire + avis.email = nil + end + + if avis.save + flash[:notice] = "Votre demande d'avis a bien été envoyée à #{email}" + end + + redirect_to backoffice_dossier_path(dossier) + end + + def update + if avis.update(update_params) + NotificationService.new('avis', params[:dossier_id]).notify + flash[:notice] = 'Merci, votre avis a été enregistré.' + end + + redirect_to backoffice_dossier_path(avis.dossier_id) + end + + def sign_up + @email = params[:email] + @dossier = Avis.includes(:dossier).find(params[:id]).dossier + + render layout: 'new_application' + end + + def create_gestionnaire + email = params[:email] + password = params['gestionnaire']['password'] + + gestionnaire = Gestionnaire.new(email: email, password: password) + + if gestionnaire.save + sign_in(gestionnaire, scope: :gestionnaire) + Avis.link_avis_to_gestionnaire(gestionnaire) + avis = Avis.find(params[:id]) + redirect_to url_for(backoffice_dossier_path(avis.dossier_id)) + else + flash[:alert] = gestionnaire.errors.full_messages.join('
') + redirect_to url_for(avis_sign_up_path(params[:id], email)) + end + end + + private + + def dossier + current_gestionnaire.dossiers.find(params[:dossier_id]) + end + + def avis + current_gestionnaire.avis.find(params[:id]) + end + + def create_params + params.require(:avis).permit(:email, :introduction) + end + + def update_params + params.require(:avis).permit(:answer) + end + + def redirect_if_no_sign_up_needed + avis = Avis.find(params[:id]) + + if current_gestionnaire.present? + # a gestionnaire is authenticated ... lets see if it can view the dossier + + redirect_to backoffice_dossier_url(avis.dossier) + elsif avis.gestionnaire.present? && avis.gestionnaire.email == params[:email] + # the avis gestionnaire has already signed up and it sould sign in + + redirect_to new_gestionnaire_session_url + end + end + + def check_avis_exists_and_email_belongs_to_avis + if !Avis.avis_exists_and_email_belongs_to_avis?(params[:id], params[:email]) + redirect_to url_for(root_path) + end + end +end diff --git a/app/controllers/backoffice/dossiers/procedure_controller.rb b/app/controllers/backoffice/dossiers/procedure_controller.rb index 76b96bf66..5f96c1508 100644 --- a/app/controllers/backoffice/dossiers/procedure_controller.rb +++ b/app/controllers/backoffice/dossiers/procedure_controller.rb @@ -1,5 +1,4 @@ class Backoffice::Dossiers::ProcedureController < Backoffice::DossiersListController - def index super @@ -22,4 +21,5 @@ class Backoffice::Dossiers::ProcedureController < Backoffice::DossiersListContro def retrieve_procedure current_gestionnaire.procedures.find params[:id] end + end diff --git a/app/controllers/backoffice/dossiers_controller.rb b/app/controllers/backoffice/dossiers_controller.rb index 1c421a6de..753b40302 100644 --- a/app/controllers/backoffice/dossiers_controller.rb +++ b/app/controllers/backoffice/dossiers_controller.rb @@ -1,13 +1,17 @@ class Backoffice::DossiersController < Backoffice::DossiersListController respond_to :html, :xlsx, :ods, :csv + prepend_before_action :store_current_location, only: :show before_action :ensure_gestionnaire_is_authorized, only: :show def index + return redirect_to backoffice_invitations_path if current_gestionnaire.avis.any? + procedure = current_gestionnaire.procedure_filter if procedure.nil? procedure_list = dossiers_list_facade.gestionnaire_procedures_name_and_id_list + if procedure_list.count == 0 flash.alert = "Vous n'avez aucune procédure d'affectée." return redirect_to root_path @@ -20,18 +24,22 @@ class Backoffice::DossiersController < Backoffice::DossiersListController end def show - create_dossier_facade params[:id] + dossier_id = params[:id] + create_dossier_facade dossier_id unless @facade.nil? @champs_private = @facade.champs_private - @headers_private = @champs_private.inject([]) do |acc, champ| - acc.push(champ) if champ.type_champ == 'header_section' - acc - end + @headers_private = @champs_private.select { |champ| champ.type_champ == 'header_section' } end - Notification.where(dossier_id: params[:id].to_i).update_all already_read: true + # if the current_gestionnaire does not own the dossier, it is here to give an advice + # and it should not remove the notifications + if current_gestionnaire.dossiers.find_by(id: dossier_id).present? + Notification.where(dossier_id: dossier_id).update_all(already_read: true) + end + + @new_avis = Avis.new(introduction: "Bonjour, merci de me donner votre avis sur ce dossier.") end def filter @@ -92,8 +100,6 @@ class Backoffice::DossiersController < Backoffice::DossiersListController dossier.received! flash.notice = 'Dossier considéré comme reçu.' - NotificationMailer.send_notification(dossier, dossier.procedure.received_mail).deliver_now! - redirect_to backoffice_dossier_path(id: dossier.id) end @@ -105,7 +111,7 @@ class Backoffice::DossiersController < Backoffice::DossiersListController dossier.next_step! 'gestionnaire', 'refuse' flash.notice = 'Dossier considéré comme refusé.' - NotificationMailer.send_notification(dossier, dossier.procedure.refused_mail).deliver_now! + NotificationMailer.send_notification(dossier, dossier.procedure.refused_mail_template).deliver_now! redirect_to backoffice_dossier_path(id: dossier.id) end @@ -118,7 +124,7 @@ class Backoffice::DossiersController < Backoffice::DossiersListController dossier.next_step! 'gestionnaire', 'without_continuation' flash.notice = 'Dossier considéré comme sans suite.' - NotificationMailer.send_notification(dossier, dossier.procedure.without_continuation_mail).deliver_now! + NotificationMailer.send_notification(dossier, dossier.procedure.without_continuation_mail_template).deliver_now! redirect_to backoffice_dossier_path(id: dossier.id) end @@ -131,7 +137,7 @@ class Backoffice::DossiersController < Backoffice::DossiersListController dossier.next_step! 'gestionnaire', 'close' flash.notice = 'Dossier traité avec succès.' - NotificationMailer.send_notification(dossier, dossier.procedure.closed_mail).deliver_now! + NotificationMailer.send_notification(dossier, dossier.procedure.closed_mail_template).deliver_now! redirect_to backoffice_dossier_path(id: dossier.id) end @@ -187,12 +193,17 @@ class Backoffice::DossiersController < Backoffice::DossiersListController private - def ensure_gestionnaire_is_authorized - current_gestionnaire.dossiers.find(params[:id]) + def store_current_location + if !gestionnaire_signed_in? + store_location_for(:gestionnaire, request.url) + end + end - rescue ActiveRecord::RecordNotFound - flash.alert = t('errors.messages.dossier_not_found') - redirect_to url_for(controller: '/backoffice') + def ensure_gestionnaire_is_authorized + unless current_gestionnaire.can_view_dossier?(params[:id]) + flash.alert = t('errors.messages.dossier_not_found') + redirect_to url_for(controller: '/backoffice') + end end def create_dossier_facade dossier_id diff --git a/app/controllers/backoffice_controller.rb b/app/controllers/backoffice_controller.rb index 7cad9b2a4..5f426be36 100644 --- a/app/controllers/backoffice_controller.rb +++ b/app/controllers/backoffice_controller.rb @@ -1,4 +1,8 @@ class BackofficeController < ApplicationController + include SmartListing::Helper::ControllerExtensions + helper SmartListing::Helper + + before_action :authenticate_gestionnaire!, only: [:invitations] def index if !gestionnaire_signed_in? @@ -7,4 +11,18 @@ class BackofficeController < ApplicationController redirect_to(:backoffice_dossiers) end end + + def invitations + pending_avis = current_gestionnaire.avis.without_answer.includes(dossier: [:procedure]).by_latest + @pending_avis = smart_listing_create :pending_avis, + pending_avis, + partial: 'backoffice/dossiers/list_invitations', + array: true + + avis_with_answer = current_gestionnaire.avis.with_answer.includes(dossier: [:procedure]).by_latest + @avis_with_answer = smart_listing_create :avis_with_answer, + avis_with_answer, + partial: 'backoffice/dossiers/list_invitations', + array: true + end end diff --git a/app/controllers/france_connect/particulier_controller.rb b/app/controllers/france_connect/particulier_controller.rb index a625d0440..1fd9e507f 100644 --- a/app/controllers/france_connect/particulier_controller.rb +++ b/app/controllers/france_connect/particulier_controller.rb @@ -1,5 +1,4 @@ class FranceConnect::ParticulierController < ApplicationController - def login client = FranceConnectParticulierClient.new diff --git a/app/controllers/ping_controller.rb b/app/controllers/ping_controller.rb index 361adb56c..5848f722c 100644 --- a/app/controllers/ping_controller.rb +++ b/app/controllers/ping_controller.rb @@ -1,5 +1,4 @@ class PingController < ApplicationController - def index Rails.logger.silence do if (ActiveRecord::Base.connected?) @@ -9,5 +8,4 @@ class PingController < ApplicationController end end end - end diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index ec123d601..6736fc88c 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -1,19 +1,11 @@ class RootController < ApplicationController def index - - begin - route = Rails.application.routes.recognize_path(request.referrer) - rescue ActionController::RoutingError - route = Rails.application.routes.recognize_path(new_user_session_path) - end - - if user_signed_in? && !route[:controller].match('users').nil? - return redirect_to users_dossiers_path - - elsif administrateur_signed_in? && !route[:controller].match('admin').nil? + if administrateur_signed_in? return redirect_to admin_procedures_path elsif gestionnaire_signed_in? + return redirect_to backoffice_invitations_path if current_gestionnaire.avis.any? + procedure_id = current_gestionnaire.procedure_filter if procedure_id.nil? procedure_list = current_gestionnaire.procedures @@ -30,15 +22,10 @@ class RootController < ApplicationController elsif user_signed_in? return redirect_to users_dossiers_path - elsif administrateur_signed_in? - return redirect_to admin_procedures_path - elsif administration_signed_in? return redirect_to administrations_path end - @demo_environment_host = "https://tps-dev.apientreprise.fr" unless Rails.env.development? - render 'landing', :layout => 'new_application' end end diff --git a/app/controllers/sessions/sessions_controller.rb b/app/controllers/sessions/sessions_controller.rb index d288616c3..e30c51fc8 100644 --- a/app/controllers/sessions/sessions_controller.rb +++ b/app/controllers/sessions/sessions_controller.rb @@ -1,5 +1,4 @@ class Sessions::SessionsController < Devise::SessionsController - before_action :before_sign_in, only: [:create] def before_sign_in diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 66d1bf202..eb96939c1 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -1,45 +1,49 @@ class StatsController < ApplicationController + layout "new_application" + + MEAN_NUMBER_OF_CHAMPS_IN_A_FORM = 24.0 def index procedures = Procedure.where(:published => true) dossiers = Dossier.where.not(:state => :draft) - @procedures_30_days_flow = thirty_days_flow_hash(procedures) - @dossiers_30_days_flow = thirty_days_flow_hash(dossiers, :initiated_at) - - @procedures_cumulative = cumulative_hash(procedures) - @dossiers_cumulative = cumulative_hash(dossiers, :initiated_at) - @procedures_count = procedures.count @dossiers_count = dossiers.count + + @procedures_cumulative = cumulative_hash(procedures) + @procedures_in_the_last_4_months = last_four_months_hash(procedures) + + @dossiers_cumulative = cumulative_hash(dossiers, :initiated_at) + @dossiers_in_the_last_4_months = last_four_months_hash(dossiers, :initiated_at) + + @procedures_count_per_administrateur = procedures_count_per_administrateur(procedures) + + @dossier_instruction_mean_time = Rails.cache.fetch("dossier_instruction_mean_time", expires_in: 1.day) do + dossier_instruction_mean_time(dossiers) + end + + @dossier_filling_mean_time = Rails.cache.fetch("dossier_filling_mean_time", expires_in: 1.day) do + dossier_filling_mean_time(dossiers) + end + + @avis_usage = avis_usage + @avis_average_answer_time = avis_average_answer_time + @avis_answer_percentages = avis_answer_percentages end private - def thirty_days_flow_hash(association, date_attribute = :created_at) - min_date = 30.days.ago.to_date + def last_four_months_hash(association, date_attribute = :created_at) + min_date = 3.months.ago.beginning_of_month.to_date max_date = Time.now.to_date - thirty_days_flow_hash = association + association .where(date_attribute => min_date..max_date) - .group("date_trunc('day', #{date_attribute.to_s})") + .group("DATE_TRUNC('month', #{date_attribute.to_s})") .count - - clean_hash(thirty_days_flow_hash, min_date, max_date) - end - - def clean_hash(h, min_date, max_date) - # Convert keys to date - h = Hash[h.map { |(k, v)| [k.to_date, v] }] - - # Add missing vales where count is 0 - (min_date..max_date).each do |date| - if h[date].nil? - h[date] = 0 - end - end - - h + .to_a + .sort{ |x, y| x[0] <=> y[0] } + .map { |e| [I18n.l(e.first, format: "%B %Y"), e.last] } end def cumulative_hash(association, date_attribute = :created_at) @@ -53,4 +57,152 @@ class StatsController < ApplicationController .reduce({}, :merge) end + def procedures_count_per_administrateur(procedures) + count_per_administrateur = procedures.group(:administrateur_id).count.values + { + 'Une procédure' => count_per_administrateur.select { |count| count == 1 }.count, + 'Entre deux et cinq procédures' => count_per_administrateur.select { |count| 2 <= count && count <= 5 }.count, + 'Plus de cinq procédures' => count_per_administrateur.select { |count| 5 < count }.count + } + end + + def mean(collection) + (collection.sum.to_f / collection.size).round(2) + end + + def dossier_instruction_mean_time(dossiers) + # In the 12 last months, we compute for each month + # the average time it took to instruct a dossier + # We compute monthly averages by first making an average per procedure + # and then computing the average for all the procedures + + min_date = 11.months.ago + max_date = Time.now.to_date + + processed_dossiers = dossiers + .where(:processed_at => min_date..max_date) + .pluck(:procedure_id, :initiated_at, :processed_at) + + # Group dossiers by month + processed_dossiers_by_month = processed_dossiers + .group_by do |dossier| + dossier[2].beginning_of_month.to_s + end + + processed_dossiers_by_month.map do |month, value| + # Group the dossiers for this month by procedure + dossiers_grouped_by_procedure = value.group_by { |dossier| dossier[0] } + + # Compute the mean time for this procedure + procedure_processing_times = dossiers_grouped_by_procedure.map do |procedure_id, procedure_dossiers| + procedure_dossiers_processing_time = procedure_dossiers.map do |dossier| + (dossier[2] - dossier[1]).to_f / (3600 * 24) + end + + mean(procedure_dossiers_processing_time) + end + + # Compute the average mean time for all the procedures of this month + month_average = mean(procedure_processing_times) + + [month, month_average] + end.to_h + end + + def dossier_filling_mean_time(dossiers) + # In the 12 last months, we compute for each month + # the average time it took to fill a dossier + # We compute monthly averages by first making an average per procedure + # and then computing the average for all the procedures + # For each procedure, we normalize the data: the time is calculated + # for a 24 champs form (the current form mean length) + + min_date = 11.months.ago + max_date = Time.now.to_date + + processed_dossiers = dossiers + .where(:processed_at => min_date..max_date) + .pluck(:procedure_id, :created_at, :initiated_at, :processed_at) + + # Group dossiers by month + processed_dossiers_by_month = processed_dossiers + .group_by do |e| + e[3].beginning_of_month.to_s + end + + processed_dossiers_by_month.map do |month, value| + # Group the dossiers for this month by procedure + dossiers_grouped_by_procedure = value.group_by { |dossier| dossier[0] } + + # Compute the mean time for this procedure + procedure_processing_times = dossiers_grouped_by_procedure.map do |procedure_id, procedure_dossiers| + procedure_dossiers_processing_time = procedure_dossiers.map do |dossier| + (dossier[2] - dossier[1]).to_f / 60 + end + + procedure_mean = mean(procedure_dossiers_processing_time) + + # We normalize the data for 24 fields + procedure_fields_count = Procedure.find(procedure_id).types_de_champ.count + procedure_mean * (MEAN_NUMBER_OF_CHAMPS_IN_A_FORM / procedure_fields_count) + end + + # Compute the average mean time for all the procedures of this month + month_average = mean(procedure_processing_times) + + [month, month_average] + end.to_h + end + + def avis_usage + [3.week.ago, 2.week.ago, 1.week.ago].map do |min_date| + max_date = min_date + 1.week + + weekly_dossiers = Dossier.includes(:avis).where(created_at: min_date..max_date).to_a + + weekly_dossiers_count = weekly_dossiers.count + + if weekly_dossiers_count == 0 + result = 0 + else + weekly_dossier_with_avis_count = weekly_dossiers.select { |dossier| dossier.avis.present? }.count + result = ((weekly_dossier_with_avis_count.to_f / weekly_dossiers_count) * 100).round(2) + end + + [min_date.to_i, result] + end + end + + def avis_average_answer_time + [3.week.ago, 2.week.ago, 1.week.ago].map do |min_date| + max_date = min_date + 1.week + + average = Avis.with_answer + .where(created_at: min_date..max_date) + .average("EXTRACT(EPOCH FROM updated_at - created_at) / 86400") + + result = average ? average.to_f.round(2) : 0 + + [min_date.to_i, result] + end + end + + def avis_answer_percentages + [3.week.ago, 2.week.ago, 1.week.ago].map do |min_date| + max_date = min_date + 1.week + + weekly_avis = Avis.where(created_at: min_date..max_date) + + weekly_avis_count = weekly_avis.count + + if weekly_avis_count == 0 + [min_date.to_i, 0] + else + answered_weekly_avis_count = weekly_avis.with_answer.count + result = ((answered_weekly_avis_count.to_f / weekly_avis_count) * 100).round(2) + + [min_date.to_i, result] + end + end + end end diff --git a/app/controllers/users/carte_controller.rb b/app/controllers/users/carte_controller.rb index f38f52ab5..3c10c7df9 100644 --- a/app/controllers/users/carte_controller.rb +++ b/app/controllers/users/carte_controller.rb @@ -1,5 +1,4 @@ class Users::CarteController < UsersController - before_action only: [:show] do authorized_routes? self.class end diff --git a/app/controllers/users/description_controller.rb b/app/controllers/users/description_controller.rb index bdd38cce9..24ed37896 100644 --- a/app/controllers/users/description_controller.rb +++ b/app/controllers/users/description_controller.rb @@ -52,7 +52,7 @@ class Users::DescriptionController < UsersController else if dossier.draft? dossier.initiated! - NotificationMailer.send_notification(dossier, procedure.initiated_mail).deliver_now! + NotificationMailer.send_notification(dossier, procedure.initiated_mail_template).deliver_now! end flash.notice = 'Félicitations, votre demande a bien été enregistrée.' redirect_to url_for(controller: :recapitulatif, action: :show, dossier_id: dossier.id) diff --git a/app/controllers/users/dossiers/invites_controller.rb b/app/controllers/users/dossiers/invites_controller.rb index c10f93d0a..c890dc6c8 100644 --- a/app/controllers/users/dossiers/invites_controller.rb +++ b/app/controllers/users/dossiers/invites_controller.rb @@ -1,5 +1,4 @@ class Users::Dossiers::InvitesController < UsersController - def authenticate_user! session["user_return_to"] = request.fullpath return redirect_to new_user_registration_path(user_email: params[:email]) if !params[:email].blank? && User.find_by_email(params[:email]).nil? diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 8f42f2581..1d9964d72 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -16,13 +16,13 @@ class Users::DossiersController < UsersController @dossiers_filtered = case @liste when 'brouillon' - @user_dossiers.brouillon.order_by_updated_at + @user_dossiers.state_brouillon.order_by_updated_at when 'a_traiter' - @user_dossiers.en_construction.order_by_updated_at + @user_dossiers.state_en_construction.order_by_updated_at when 'en_instruction' - @user_dossiers.en_instruction.order_by_updated_at + @user_dossiers.state_en_instruction.order_by_updated_at when 'termine' - @user_dossiers.termine.order_by_updated_at + @user_dossiers.state_termine.order_by_updated_at when 'invite' current_user.invites else diff --git a/app/controllers/users/recapitulatif_controller.rb b/app/controllers/users/recapitulatif_controller.rb index 8315380c8..88dd4d2f8 100644 --- a/app/controllers/users/recapitulatif_controller.rb +++ b/app/controllers/users/recapitulatif_controller.rb @@ -1,5 +1,4 @@ class Users::RecapitulatifController < UsersController - before_action only: [:show] do authorized_routes? self.class end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 3e8efd878..2a990beaf 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -33,7 +33,8 @@ class Users::SessionsController < Sessions::SessionsController if user_signed_in? redirect_to after_sign_in_path_for(:user) elsif gestionnaire_signed_in? - redirect_to backoffice_path + location = stored_location_for(:gestionnaire) || backoffice_path + redirect_to location elsif administrateur_signed_in? redirect_to admin_path else diff --git a/app/decorators/champ_decorator.rb b/app/decorators/champ_decorator.rb index 1688c5f91..a56f24947 100644 --- a/app/decorators/champ_decorator.rb +++ b/app/decorators/champ_decorator.rb @@ -2,9 +2,15 @@ class ChampDecorator < Draper::Decorator delegate_all def value - return object.value == 'on' ? 'Oui' : 'Non' if type_champ == 'checkbox' - return JSON.parse(object.value).join(', ') if type_champ == 'multiple_drop_down_list' && object.value.present? - object.value + if type_champ == "date" && object.value.present? + Date.parse(object.value).strftime("%d/%m/%Y") + elsif type_champ == 'checkbox' + object.value == 'on' ? 'Oui' : 'Non' + elsif type_champ == 'multiple_drop_down_list' && object.value.present? + JSON.parse(object.value).join(', ') + else + object.value + end end def description_with_links diff --git a/app/mailers/avis_mailer.rb b/app/mailers/avis_mailer.rb new file mode 100644 index 000000000..cf435cefc --- /dev/null +++ b/app/mailers/avis_mailer.rb @@ -0,0 +1,9 @@ +class AvisMailer < ApplicationMailer + + def avis_invitation(avis) + @avis = avis + email = @avis.gestionnaire.try(:email) || @avis.email + mail(to: email, subject: "Donnez votre avis sur le dossier nº #{@avis.dossier.id} (#{@avis.dossier.procedure.libelle})") + end + +end diff --git a/app/mailers/gestionnaire_mailer.rb b/app/mailers/gestionnaire_mailer.rb index 8acbe7237..118537a8d 100644 --- a/app/mailers/gestionnaire_mailer.rb +++ b/app/mailers/gestionnaire_mailer.rb @@ -8,6 +8,11 @@ class GestionnaireMailer < ApplicationMailer send_mail email, email_admin, "Vous avez été assigné à un nouvel administrateur sur la plateforme TPS" end + def last_week_overview(gestionnaire, overview) + headers['X-mailjet-campaign'] = 'last_week_overview' + send_mail gestionnaire.email, overview, 'Résumé de la semaine' + end + private def vars_mailer email, args diff --git a/app/mailers/new_admin_mailer.rb b/app/mailers/new_admin_mailer.rb index 6ec6e7092..4527451a0 100644 --- a/app/mailers/new_admin_mailer.rb +++ b/app/mailers/new_admin_mailer.rb @@ -4,7 +4,7 @@ class NewAdminMailer < ApplicationMailer @admin = admin @password = password - mail(to: 'tech@apientreprise.fr', + mail(to: 'tech@tps.apientreprise.fr', subject: "Création d'un compte Admin TPS") end end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index d2ddbb195..6c7d4b91c 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -3,16 +3,16 @@ class NotificationMailer < ApplicationMailer after_action :create_commentaire_for_notification, only: :send_notification - def send_notification dossier, mail_template + def send_notification(dossier, mail_template) vars_mailer(dossier) - @obj = mail_template.object_for_dossier dossier + @object = mail_template.object_for_dossier dossier @body = mail_template.body_for_dossier dossier - mail(subject: @obj) { |format| format.html { @body } } + mail(subject: @object) { |format| format.html { @body } } end - def new_answer dossier + def new_answer(dossier) send_mail dossier, "Nouveau message pour votre dossier TPS nº #{dossier.id}" end @@ -22,16 +22,16 @@ class NotificationMailer < ApplicationMailer Commentaire.create( dossier: @dossier, email: I18n.t("dynamics.contact_email"), - body: ["[#{@obj}]", @body].join("

") + body: ["[#{@object}]", @body].join("

") ) end - def vars_mailer dossier + def vars_mailer(dossier) @dossier = dossier @user = dossier.user end - def send_mail dossier, subject + def send_mail(dossier, subject) vars_mailer dossier mail(subject: subject) diff --git a/app/models/administrateur.rb b/app/models/administrateur.rb index ab69d0339..731e5df5c 100644 --- a/app/models/administrateur.rb +++ b/app/models/administrateur.rb @@ -27,5 +27,4 @@ class Administrateur < ActiveRecord::Base break token unless Administrateur.find_by(api_token: token) end end - end diff --git a/app/models/avis.rb b/app/models/avis.rb new file mode 100644 index 000000000..2d2480a2e --- /dev/null +++ b/app/models/avis.rb @@ -0,0 +1,29 @@ +class Avis < ApplicationRecord + belongs_to :dossier + belongs_to :gestionnaire + belongs_to :claimant, class_name: 'Gestionnaire' + + after_create :notify_gestionnaire + + scope :with_answer, -> { where.not(answer: nil) } + scope :without_answer, -> { where(answer: nil) } + scope :for_dossier, ->(dossier_id) { where(dossier_id: dossier_id) } + scope :by_latest, -> { order(updated_at: :desc) } + + def email_to_display + gestionnaire.try(:email) || email + end + + def notify_gestionnaire + AvisMailer.avis_invitation(self).deliver_now + end + + def self.link_avis_to_gestionnaire(gestionnaire) + Avis.where(email: gestionnaire.email).update_all(email: nil, gestionnaire_id: gestionnaire.id) + end + + def self.avis_exists_and_email_belongs_to_avis?(avis_id, email) + avis = Avis.find_by(id: avis_id) + avis.present? && avis.email == email + end +end diff --git a/app/models/champ.rb b/app/models/champ.rb index ce12e32a3..5b90b309a 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -5,6 +5,7 @@ class Champ < ActiveRecord::Base delegate :libelle, :type_champ, :order_place, :mandatory, :description, :drop_down_list, to: :type_de_champ + before_save :format_date_to_iso, if: Proc.new { type_champ == 'date' } after_save :internal_notification, if: Proc.new { !dossier.nil? } def mandatory? @@ -12,12 +13,12 @@ class Champ < ActiveRecord::Base end def data_provide - return 'datepicker' if (type_champ == 'datetime' || type_champ == 'date') && !(BROWSER.value.chrome? || BROWSER.value.edge?) + return 'datepicker' if (type_champ == 'datetime') && !(BROWSER.value.chrome? || BROWSER.value.edge?) return 'typeahead' if type_champ == 'address' end def data_date_format - ('dd/mm/yyyy' if type_champ == 'datetime' || type_champ == 'date') + ('dd/mm/yyyy' if type_champ == 'datetime') end def same_hour? num @@ -55,6 +56,15 @@ class Champ < ActiveRecord::Base private + def format_date_to_iso + date = begin + Date.parse(value).strftime("%F") + rescue + nil + end + self.value = date + end + def internal_notification unless dossier.state == 'draft' NotificationService.new('champs', self.dossier.id, self.libelle).notify diff --git a/app/models/concerns/mail_template_concern.rb b/app/models/concerns/mail_template_concern.rb index f64de99d8..25c87d133 100644 --- a/app/models/concerns/mail_template_concern.rb +++ b/app/models/concerns/mail_template_concern.rb @@ -4,50 +4,23 @@ module MailTemplateConcern include Rails.application.routes.url_helpers include ActionView::Helpers::UrlHelper - TAGS = { - numero_dossier: { - description: "Permet d'afficher le numéro de dossier de l'utilisateur.", - templates: [ - "initiated_mail", - "received_mail", - "closed_mail", - "refused_mail", - "without_continuation_mail" - ] - }, - lien_dossier: { - description: "Permet d'afficher un lien vers le dossier de l'utilisateur.", - templates: [ - "initiated_mail", - "received_mail", - "closed_mail", - "refused_mail", - "without_continuation_mail" - ] - }, - libelle_procedure: { - description: "Permet d'afficher le libellé de la procédure.", - templates: [ - "initiated_mail", - "received_mail", - "closed_mail", - "refused_mail", - "without_continuation_mail" - ] - }, - date_de_decision: { - description: "Permet d'afficher la date à laquelle la décision finale (acceptation, refus, classement sans suite) sur le dossier a été prise.", - templates: [ - "closed_mail", - "refused_mail", - "without_continuation_mail" - ] - } + TAGS = [] + TAGS << TAG_NUMERO_DOSSIER = { + name: "numero_dossier", + description: "Permet d'afficher le numéro de dossier de l'utilisateur." + } + TAGS << TAG_LIEN_DOSSIER = { + name: "lien_dossier", + description: "Permet d'afficher un lien vers le dossier de l'utilisateur." + } + TAGS << TAG_LIBELLE_PROCEDURE = { + name: "libelle_procedure", + description: "Permet d'afficher le libellé de la procédure." + } + TAGS << TAG_DATE_DE_DECISION = { + name: "date_de_decision", + description: "Permet d'afficher la date à laquelle la décision finale (acceptation, refus, classement sans suite) sur le dossier a été prise." } - - def self.tags_for_template(template) - TAGS.select { |key, value| value[:templates].include?(template) } - end def object_for_dossier(dossier) replace_tags(object, dossier) @@ -59,17 +32,13 @@ module MailTemplateConcern def replace_tags(string, dossier) TAGS.inject(string) do |acc, tag| - acc.gsub!("--#{tag.first}--", replace_tag(tag.first.to_sym, dossier)) || acc + acc.gsub!("--#{tag[:name]}--", replace_tag(tag, dossier)) || acc end end module ClassMethods - def slug - self.name.demodulize.underscore.parameterize - end - def default - body = ActionController::Base.new.render_to_string(template: self.name.underscore) + body = ActionController::Base.new.render_to_string(template: self.const_get(:TEMPLATE_NAME)) self.new(object: self.const_get(:DEFAULT_OBJECT), body: body) end end @@ -78,13 +47,13 @@ module MailTemplateConcern def replace_tag(tag, dossier) case tag - when :numero_dossier + when TAG_NUMERO_DOSSIER dossier.id.to_s - when :lien_dossier + when TAG_LIEN_DOSSIER link_to users_dossier_recapitulatif_url(dossier), users_dossier_recapitulatif_url(dossier), target: '_blank' - when :libelle_procedure + when TAG_LIBELLE_PROCEDURE dossier.procedure.libelle - when :date_de_decision + when TAG_DATE_DE_DECISION dossier.processed_at.present? ? dossier.processed_at.localtime.strftime("%d/%m/%Y") : "" else '--BALISE_NON_RECONNUE--' diff --git a/app/models/dossier.rb b/app/models/dossier.rb index c6eee566f..8203618ed 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -1,13 +1,24 @@ class Dossier < ActiveRecord::Base - enum state: {draft: 'draft', - initiated: 'initiated', - replied: 'replied', #action utilisateur demandé - updated: 'updated', #etude par l'administration en cours - received: 'received', - closed: 'closed', - refused: 'refused', - without_continuation: 'without_continuation' - } + enum state: { + draft: 'draft', + initiated: 'initiated', + replied: 'replied', # action utilisateur demandé + updated: 'updated', # etude par l'administration en cours + received: 'received', + closed: 'closed', + refused: 'refused', + without_continuation: 'without_continuation' + } + + BROUILLON = %w(draft) + NOUVEAUX = %w(initiated) + OUVERT = %w(updated replied) + WAITING_FOR_GESTIONNAIRE = %w(updated) + WAITING_FOR_USER = %w(replied) + EN_CONSTRUCTION = %w(initiated updated replied) + EN_INSTRUCTION = %w(received) + A_INSTRUIRE = %w(received) + TERMINE = %w(closed refused without_continuation) has_one :etablissement, dependent: :destroy has_one :entreprise, dependent: :destroy @@ -25,10 +36,36 @@ class Dossier < ActiveRecord::Base has_many :invites_gestionnaires, class_name: 'InviteGestionnaire', dependent: :destroy has_many :follows has_many :notifications, dependent: :destroy + has_many :avis, dependent: :destroy belongs_to :procedure belongs_to :user + scope :state_brouillon, -> { where(state: BROUILLON) } + scope :state_not_brouillon, -> { where.not(state: BROUILLON) } + scope :state_nouveaux, -> { where(state: NOUVEAUX) } + scope :state_ouvert, -> { where(state: OUVERT) } + scope :state_waiting_for_gestionnaire, -> { where(state: WAITING_FOR_GESTIONNAIRE) } + scope :state_waiting_for_user, -> { where(state: WAITING_FOR_USER) } + scope :state_en_construction, -> { where(state: EN_CONSTRUCTION) } + scope :state_en_instruction, -> { where(state: EN_INSTRUCTION) } + scope :state_a_instruire, -> { where(state: A_INSTRUIRE) } + scope :state_termine, -> { where(state: TERMINE) } + + scope :archived, -> { where(archived: true) } + scope :not_archived, -> { where(archived: false) } + + scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) } + + scope :all_state, -> { not_archived.state_not_brouillon.order_by_updated_at(:asc) } + scope :nouveaux, -> { not_archived.state_nouveaux.order_by_updated_at(:asc) } + scope :ouvert, -> { not_archived.state_ouvert.order_by_updated_at(:asc) } + scope :waiting_for_gestionnaire, -> { not_archived.state_waiting_for_gestionnaire.order_by_updated_at(:asc) } + scope :waiting_for_user, -> { not_archived.state_waiting_for_user.order_by_updated_at(:asc) } + scope :a_instruire, -> { not_archived.state_a_instruire.order_by_updated_at(:asc) } + scope :termine, -> { not_archived.state_termine.order_by_updated_at(:asc) } + scope :downloadable, -> { state_not_brouillon.order_by_updated_at(:asc) } + accepts_nested_attributes_for :individual delegate :siren, to: :entreprise @@ -41,20 +78,10 @@ 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 :send_notification_email validates :user, presence: true - BROUILLON = %w(draft) - NOUVEAUX = %w(initiated) - OUVERT = %w(updated replied) - WAITING_FOR_GESTIONNAIRE = %w(updated) - WAITING_FOR_USER = %w(replied) - EN_CONSTRUCTION = %w(initiated updated replied) - EN_INSTRUCTION = %w(received) - A_INSTRUIRE = %w(received) - TERMINE = %w(closed refused without_continuation) - ALL_STATE = %w(initiated updated replied received closed refused without_continuation) - def unreaded_notifications @unreaded_notif ||= notifications.where(already_read: false) end @@ -158,49 +185,10 @@ class Dossier < ActiveRecord::Base state end - def self.all_state order = 'ASC' - where(state: ALL_STATE, archived: false).order("updated_at #{order}") - end - def brouillon? BROUILLON.include?(state) end - scope :brouillon, -> { where(state: BROUILLON) } - - scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) } - - def self.nouveaux order = 'ASC' - where(state: NOUVEAUX, archived: false).order("updated_at #{order}") - end - - def self.waiting_for_gestionnaire order = 'ASC' - where(state: WAITING_FOR_GESTIONNAIRE, archived: false).order("updated_at #{order}") - end - - def self.waiting_for_user order = 'ASC' - where(state: WAITING_FOR_USER, archived: false).order("updated_at #{order}") - end - - scope :en_construction, -> { where(state: EN_CONSTRUCTION) } - - def self.ouvert order = 'ASC' - where(state: OUVERT, archived: false).order("updated_at #{order}") - end - - def self.a_instruire order = 'ASC' - where(state: A_INSTRUIRE, archived: false).order("updated_at #{order}") - end - - scope :en_instruction, -> { where(state: EN_INSTRUCTION) } - - scope :termine, -> { where(state: TERMINE) } - - scope :archived, -> { where(archived: true) } - scope :not_archived, -> { where(archived: false) } - - scope :downloadable, -> { all_state } - def cerfa_available? procedure.cerfa_flag? && cerfa.size != 0 end @@ -315,4 +303,10 @@ class Dossier < ActiveRecord::Base def serialize_value_for_export(value) value.nil? || value.kind_of?(Time) ? value : value.to_s end + + def send_notification_email + if state_changed? && EN_INSTRUCTION.include?(state) + NotificationMailer.send_notification(self, procedure.received_mail_template).deliver_now! + end + end end diff --git a/app/models/entreprise.rb b/app/models/entreprise.rb index 8e14d7640..5e24a31bb 100644 --- a/app/models/entreprise.rb +++ b/app/models/entreprise.rb @@ -1,5 +1,4 @@ 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 1e80b4bad..44e840205 100644 --- a/app/models/etablissement.rb +++ b/app/models/etablissement.rb @@ -1,5 +1,4 @@ class Etablissement < ActiveRecord::Base - belongs_to :dossier belongs_to :entreprise diff --git a/app/models/exercice.rb b/app/models/exercice.rb index e6ec6a4cb..6b002c092 100644 --- a/app/models/exercice.rb +++ b/app/models/exercice.rb @@ -2,5 +2,4 @@ class Exercice < ActiveRecord::Base belongs_to :etablissement validates :ca, presence: true, allow_blank: false, allow_nil: false - end diff --git a/app/models/france_connect_particulier_client.rb b/app/models/france_connect_particulier_client.rb index 967ab84bd..bc0e99e9c 100644 --- a/app/models/france_connect_particulier_client.rb +++ b/app/models/france_connect_particulier_client.rb @@ -1,5 +1,4 @@ class FranceConnectParticulierClient < OpenIDConnect::Client - def initialize params={} super( identifier: FRANCE_CONNECT.particulier_identifier, diff --git a/app/models/gestionnaire.rb b/app/models/gestionnaire.rb index b5e84bcdd..6492cca64 100644 --- a/app/models/gestionnaire.rb +++ b/app/models/gestionnaire.rb @@ -12,6 +12,7 @@ class Gestionnaire < ActiveRecord::Base has_many :followed_dossiers, through: :follows, source: :dossier has_many :follows has_many :preference_list_dossiers + has_many :avis after_create :build_default_preferences_list_dossier after_create :build_default_preferences_smart_listing_page @@ -24,6 +25,11 @@ class Gestionnaire < ActiveRecord::Base self[:procedure_filter] end + def can_view_dossier?(dossier_id) + avis.where(dossier_id: dossier_id).any? || + dossiers.where(id: dossier_id).any? + end + def toggle_follow_dossier dossier_id dossier = dossier_id dossier = Dossier.find(dossier_id) unless dossier_id.class == Dossier @@ -41,6 +47,10 @@ class Gestionnaire < ActiveRecord::Base Follow.where(gestionnaire_id: id, dossier_id: dossier_id).any? end + def assigned_on_procedure?(procedure_id) + procedures.find_by(id: procedure_id).present? + end + def build_default_preferences_list_dossier procedure_id=nil PreferenceListDossier.available_columns_for(procedure_id).each do |table| @@ -92,6 +102,26 @@ class Gestionnaire < ActiveRecord::Base notifications.pluck(:dossier_id).uniq.count end + def last_week_overview + start_date = DateTime.now.beginning_of_week + + active_procedure_overviews = procedures + .where(published: true) + .all + .map { |procedure| procedure.procedure_overview(start_date, dossiers_with_notifications_count_for_procedure(procedure)) } + .select(&:had_some_activities?) + + if active_procedure_overviews.count == 0 && notifications.count == 0 + nil + else + { + start_date: start_date, + procedure_overviews: active_procedure_overviews, + notifications: notifications + } + end + end + private def valid_couple_table_attr? table, column diff --git a/app/models/mails/closed_mail.rb b/app/models/mails/closed_mail.rb index 2d113f71d..80f706bf9 100644 --- a/app/models/mails/closed_mail.rb +++ b/app/models/mails/closed_mail.rb @@ -1,9 +1,11 @@ module Mails - class ClosedMail < ActiveRecord::Base + class ClosedMail < ApplicationRecord include MailTemplateConcern + SLUG = "closed_mail" + TEMPLATE_NAME = "mails/closed_mail" DISPLAYED_NAME = "Accusé d'acceptation" DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été accepté' - + ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE, TAG_DATE_DE_DECISION] end end diff --git a/app/models/mails/initiated_mail.rb b/app/models/mails/initiated_mail.rb index 4f401e65e..9d8edc903 100644 --- a/app/models/mails/initiated_mail.rb +++ b/app/models/mails/initiated_mail.rb @@ -1,9 +1,11 @@ module Mails - class InitiatedMail < ActiveRecord::Base + class InitiatedMail < ApplicationRecord include MailTemplateConcern + SLUG = "initiated_mail" + TEMPLATE_NAME = "mails/initiated_mail" DISPLAYED_NAME = 'Accusé de réception' DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été bien reçu' - + ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE] end end diff --git a/app/models/mails/received_mail.rb b/app/models/mails/received_mail.rb index be65a36b2..27175df37 100644 --- a/app/models/mails/received_mail.rb +++ b/app/models/mails/received_mail.rb @@ -1,9 +1,11 @@ module Mails - class ReceivedMail < ActiveRecord::Base + class ReceivedMail < ApplicationRecord include MailTemplateConcern + SLUG = "received_mail" + TEMPLATE_NAME = "mails/received_mail" DISPLAYED_NAME = 'Accusé de passage en instruction' DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- va être instruit' - + ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE] end end diff --git a/app/models/mails/refused_mail.rb b/app/models/mails/refused_mail.rb index 281788535..f9b859e74 100644 --- a/app/models/mails/refused_mail.rb +++ b/app/models/mails/refused_mail.rb @@ -2,8 +2,10 @@ module Mails class RefusedMail < ApplicationRecord include MailTemplateConcern + SLUG = "refused_mail" + TEMPLATE_NAME = "mails/refused_mail" DISPLAYED_NAME = 'Accusé de rejet du dossier' DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été refusé' - + ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE, TAG_DATE_DE_DECISION] end end diff --git a/app/models/mails/without_continuation_mail.rb b/app/models/mails/without_continuation_mail.rb index 3d3bd8f74..0115037c3 100644 --- a/app/models/mails/without_continuation_mail.rb +++ b/app/models/mails/without_continuation_mail.rb @@ -2,8 +2,10 @@ module Mails class WithoutContinuationMail < ApplicationRecord include MailTemplateConcern + SLUG = "without_continuation" + TEMPLATE_NAME = "mails/without_continuation_mail" DISPLAYED_NAME = 'Accusé de classement sans suite' DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été classé sans suite' - + ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE, TAG_DATE_DE_DECISION] end end diff --git a/app/models/notification.rb b/app/models/notification.rb index af36da06e..53d8af02d 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -1,11 +1,14 @@ class Notification < ActiveRecord::Base - belongs_to :dossier enum type_notif: { - commentaire: 'commentaire', - cerfa: 'cerfa', - piece_justificative: 'piece_justificative', - champs: 'champs', - submitted: 'submitted' - } + commentaire: 'commentaire', + cerfa: 'cerfa', + piece_justificative: 'piece_justificative', + champs: 'champs', + submitted: 'submitted', + avis: 'avis' + } + + belongs_to :dossier + scope :unread, -> { where(already_read: false) } end diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 2e910acd3..c46b2d6da 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -3,7 +3,6 @@ class Procedure < ActiveRecord::Base has_many :types_de_champ, class_name: 'TypeDeChampPublic', dependent: :destroy has_many :types_de_champ_private, dependent: :destroy has_many :dossiers - has_many :notifications, through: :dossiers has_one :procedure_path, dependent: :destroy @@ -16,6 +15,12 @@ class Procedure < ActiveRecord::Base has_many :preference_list_dossiers + has_one :initiated_mail, class_name: "Mails::InitiatedMail", dependent: :destroy + has_one :received_mail, class_name: "Mails::ReceivedMail", dependent: :destroy + has_one :closed_mail, class_name: "Mails::ClosedMail", dependent: :destroy + has_one :refused_mail, class_name: "Mails::RefusedMail", dependent: :destroy + has_one :without_continuation_mail, class_name: "Mails::WithoutContinuationMail", dependent: :destroy + delegate :use_api_carto, to: :module_api_carto accepts_nested_attributes_for :types_de_champ, :reject_if => proc { |attributes| attributes['libelle'].blank? }, :allow_destroy => true @@ -25,31 +30,11 @@ class Procedure < ActiveRecord::Base mount_uploader :logo, ProcedureLogoUploader - validates :libelle, presence: true, allow_blank: false, allow_nil: false - validates :description, presence: true, allow_blank: false, allow_nil: false - - # for all those mails do - # has_one :initiated_mail, class_name: 'Mails::InitiatedMail' - # - # add a method to return default mail if none is saved - # def initiated_mail_with_override - # self.initiated_mail_without_override || InitiatedMail.default - # end - # alias_method_chain :initiated_mail, :override - - MAIL_TEMPLATE_TYPES = %w(InitiatedMail ReceivedMail ClosedMail RefusedMail WithoutContinuationMail) - - MAIL_TEMPLATE_TYPES.each do |name| - has_one "#{name.underscore}".to_sym, class_name: "Mails::#{name}", dependent: :destroy - define_method("#{name.underscore}_with_override") do - self.send("#{name.underscore}_without_override") || Object.const_get("Mails::#{name}").default - end - alias_method_chain "#{name.underscore.to_sym}".to_s, :override - end - scope :not_archived, -> { where(archived: false) } scope :by_libelle, -> { order(libelle: :asc) } + validates :libelle, presence: true, allow_blank: false, allow_nil: false + validates :description, presence: true, allow_blank: false, allow_nil: false def path procedure_path.path unless procedure_path.nil? @@ -68,7 +53,7 @@ class Procedure < ActiveRecord::Base end def self.active id - Procedure.where(archived: false, published: true).find(id) + not_archived.where(published: true).find(id) end def switch_types_de_champ index_of_first_element @@ -84,12 +69,17 @@ class Procedure < ActiveRecord::Base end def switch_list_order(list, index_of_first_element) - return false if index_of_first_element < 0 - return false if index_of_first_element == list.count - 1 - return false if list.count < 1 - list[index_of_first_element].update_attributes(order_place: index_of_first_element + 1) - list[index_of_first_element + 1].update_attributes(order_place: index_of_first_element) - true + if index_of_first_element < 0 || + index_of_first_element == list.count - 1 || + list.count < 1 + + false + else + list[index_of_first_element].update_attributes(order_place: index_of_first_element + 1) + list[index_of_first_element + 1].update_attributes(order_place: index_of_first_element) + + true + end end def locked? @@ -109,9 +99,11 @@ class Procedure < ActiveRecord::Base procedure.logo_secure_token = nil procedure.remote_logo_url = self.logo_url - MAIL_TEMPLATE_TYPES.each do |mtt| - procedure.send("#{mtt.underscore}=", self.send("#{mtt.underscore}_without_override").try(:dup)) - end + procedure.initiated_mail = initiated_mail.try(:dup) + procedure.received_mail = received_mail.try(:dup) + procedure.closed_mail = closed_mail.try(:dup) + procedure.refused_mail = refused_mail.try(:dup) + procedure.without_continuation_mail = without_continuation_mail.try(:dup) return procedure if procedure.save end @@ -141,4 +133,27 @@ class Procedure < ActiveRecord::Base } end + def procedure_overview(start_date, notifications_count) + ProcedureOverview.new(self, start_date, notifications_count) + end + + def initiated_mail_template + initiated_mail || Mails::InitiatedMail.default + end + + def received_mail_template + received_mail || Mails::ReceivedMail.default + end + + def closed_mail_template + closed_mail || Mails::ClosedMail.default + end + + def refused_mail_template + refused_mail || Mails::RefusedMail.default + end + + def without_continuation_mail_template + without_continuation_mail || Mails::WithoutContinuationMail.default + end end diff --git a/app/models/procedure_overview.rb b/app/models/procedure_overview.rb new file mode 100644 index 000000000..464001cf2 --- /dev/null +++ b/app/models/procedure_overview.rb @@ -0,0 +1,82 @@ +class ProcedureOverview + include Rails.application.routes.url_helpers + attr_accessor :libelle, :notifications_count, :received_dossiers_count, :created_dossiers_count, :processed_dossiers_count, :date + + def initialize(procedure, start_date, notifications_count) + @libelle = procedure.libelle + @procedure_url = backoffice_dossiers_procedure_url(procedure) + @notifications_count = notifications_count + + @received_dossiers_count = procedure.dossiers.where(state: :received).count + @created_dossiers_count = procedure.dossiers + .where(created_at: start_date..DateTime.now) + .where.not(state: :draft) + .count + @processed_dossiers_count = procedure.dossiers.where(processed_at: start_date..DateTime.now).count + end + + def had_some_activities? + [received_dossiers_count, + created_dossiers_count, + processed_dossiers_count, + notifications_count].reduce(:+) > 0 + end + + def to_html + [libelle_description, + dossiers_en_instruction_description, + created_dossier_description, + processed_dossier_description, + notifications_description].compact.join('
') + end + + private + + def libelle_description + "#{libelle}" + end + + def dossiers_en_instruction_description + case received_dossiers_count + when 0 + nil + when 1 + "1 dossier est en cours d'instruction" + else + "#{received_dossiers_count} dossiers sont en cours d'instruction" + end + end + + def created_dossier_description + case created_dossiers_count + when 0 + nil + when 1 + '1 nouveau dossier a été déposé' + else + "#{created_dossiers_count} nouveaux dossiers ont été déposés" + end + end + + def processed_dossier_description + case processed_dossiers_count + when 0 + nil + when 1 + '1 dossier a été instruit' + else + "#{processed_dossiers_count} dossiers ont été instruits" + end + end + + def notifications_description + case notifications_count + when 0 + nil + when 1 + '1 notification en attente sur les dossiers que vous suivez' + else + "#{notifications_count} notifications en attente sur les dossiers que vous suivez" + end + end +end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index dcdf0a102..86747bc5a 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -1,26 +1,26 @@ class TypeDeChamp < ActiveRecord::Base enum type_champs: { - text: 'text', - textarea: 'textarea', - date: 'date', - datetime: 'datetime', - number: 'number', - checkbox: 'checkbox', - civilite: 'civilite', - email: 'email', - phone: 'phone', - address: 'address', - yes_no: 'yes_no', - drop_down_list: 'drop_down_list', - multiple_drop_down_list: 'multiple_drop_down_list', - pays: 'pays', - regions: 'regions', - departements: 'departements', - engagement: 'engagement', - header_section: 'header_section', - explication: 'explication', - dossier_link: 'dossier_link' - } + text: 'text', + textarea: 'textarea', + date: 'date', + datetime: 'datetime', + number: 'number', + checkbox: 'checkbox', + civilite: 'civilite', + email: 'email', + phone: 'phone', + address: 'address', + yes_no: 'yes_no', + drop_down_list: 'drop_down_list', + multiple_drop_down_list: 'multiple_drop_down_list', + pays: 'pays', + regions: 'regions', + departements: 'departements', + engagement: 'engagement', + header_section: 'header_section', + explication: 'explication', + dossier_link: 'dossier_link' + } belongs_to :procedure diff --git a/app/models/user.rb b/app/models/user.rb index 7ea29d260..c4dab2a5f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,8 @@ class User < ActiveRecord::Base - enum loged_in_with_france_connect: {particulier: 'particulier', - entreprise: 'entreprise'} + enum loged_in_with_france_connect: { + particulier: 'particulier', + entreprise: 'entreprise' + } # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable @@ -35,5 +37,4 @@ class User < ActiveRecord::Base def invite? dossier_id invites.pluck(:dossier_id).include?(dossier_id.to_i) end - end diff --git a/app/services/dossiers_list_gestionnaire_service.rb b/app/services/dossiers_list_gestionnaire_service.rb index 63a91783c..b110fde11 100644 --- a/app/services/dossiers_list_gestionnaire_service.rb +++ b/app/services/dossiers_list_gestionnaire_service.rb @@ -45,7 +45,7 @@ class DossiersListGestionnaireService end def termine - @termine ||= filter_dossiers.termine.not_archived + @termine ||= filter_dossiers.termine end def filter_dossiers diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 18415ebbd..a401064cf 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -35,6 +35,8 @@ class NotificationService attribut when 'submitted' "Le dossier nº #{@dossier_id} a été déposé." + when 'avis' + 'Un nouvel avis a été rendu' else 'Notification par défaut' end diff --git a/app/views/admin/mail_templates/edit.html.haml b/app/views/admin/mail_templates/edit.html.haml index a35c13334..af010fb95 100644 --- a/app/views/admin/mail_templates/edit.html.haml +++ b/app/views/admin/mail_templates/edit.html.haml @@ -4,7 +4,7 @@ = simple_form_for @mail_template, as: 'mail_template', - url: admin_procedure_mail_template_path(@procedure, @mail_template.class.slug), + url: admin_procedure_mail_template_path(@procedure, @mail_template.class.const_get(:SLUG)), method: :put do |f| .row .col-md-6 @@ -22,9 +22,9 @@ Balise %th Description - - MailTemplateConcern.tags_for_template(@mail_template_name).each do |balise| + - @mail_template.class.const_get(:ALLOWED_TAGS).each do |tag| %tr %td.center - = "--#{balise.first}--" + = "--#{tag[:name]}--" %td - = balise.second[:description] + = tag[:description] diff --git a/app/views/admin/mail_templates/index.html.haml b/app/views/admin/mail_templates/index.html.haml index b8eff250a..32deb0a28 100644 --- a/app/views/admin/mail_templates/index.html.haml +++ b/app/views/admin/mail_templates/index.html.haml @@ -5,9 +5,9 @@ %tr %th{ colspan: 2 } Type d'email - - @mails.each do |mail| + - @mail_templates.each do |mail_template| %tr %td - = mail.class.const_get(:DISPLAYED_NAME) + = mail_template.class.const_get(:DISPLAYED_NAME) %td.text-right - = link_to "Personnaliser l'e-mail", edit_admin_procedure_mail_template_path(@procedure, mail.class.slug) + = link_to "Personnaliser l'e-mail", edit_admin_procedure_mail_template_path(@procedure, mail_template.class.const_get(:SLUG)) diff --git a/app/views/avis_mailer/avis_invitation.html.haml b/app/views/avis_mailer/avis_invitation.html.haml new file mode 100644 index 000000000..78added0b --- /dev/null +++ b/app/views/avis_mailer/avis_invitation.html.haml @@ -0,0 +1,28 @@ +%html + %body + %p + Bonjour, + %br + = "Vous avez été invité par #{@avis.claimant.email} à donner votre avis sur le dossier nº #{@avis.dossier.id} de la procédure : #{@avis.dossier.procedure.libelle}." + %br + Message de votre interlocuteur : + + %p{ style: 'border: 1px solid grey' } + = @avis.introduction + + - if @avis.gestionnaire.present? + %p + = link_to "Connectez-vous pour donner votre avis", backoffice_dossier_url(@avis.dossier) + - else + %p + = link_to "Inscrivez-vous pour donner votre avis", avis_sign_up_url(@avis.id, @avis.email) + + Bonne journée, + %br + %br + L'équipe Téléprocédures Simplifiées + %br + %br + %hr + %br + Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme. diff --git a/app/views/backoffice/avis/sign_up.html.haml b/app/views/backoffice/avis/sign_up.html.haml new file mode 100644 index 000000000..faeb3dadb --- /dev/null +++ b/app/views/backoffice/avis/sign_up.html.haml @@ -0,0 +1,15 @@ +.avis-sign-up + .left + %p.description= @dossier.procedure.libelle + %p.dossier Dossier nº #{@dossier.id} + .right + %h1 Créez-vous un compte + + = form_for(Gestionnaire.new, url: { controller: 'backoffice/avis', action: :create_gestionnaire }, method: :post) do |f| + = f.label :email, 'Email' + = f.email_field :email, value: @email, disabled: true + + = f.label :password, 'Mot de passe' + = f.password_field :password, autofocus: true, required: true, placeholder: '8 caractères minimum' + + %button Créer un compte diff --git a/app/views/backoffice/dossiers/_list_invitations.html.haml b/app/views/backoffice/dossiers/_list_invitations.html.haml new file mode 100644 index 000000000..526bc5415 --- /dev/null +++ b/app/views/backoffice/dossiers/_list_invitations.html.haml @@ -0,0 +1,20 @@ +- if smart_listing.collection.any? + %table#dossiers-list.table + %thead + %th + Nº + %th + Procédure + %th + Invité le + %tbody + - smart_listing.collection.each do |avis| + %tr.dossier-row{ id: "tr_dossier_#{avis.dossier.id}", 'data-dossier_url' => backoffice_dossier_url(id: avis.dossier.id) } + %td= avis.dossier.id + %td= avis.dossier.procedure.libelle + %td= avis.created_at.strftime('%d/%m/%Y %H:%M') + = smart_listing.paginate + +- else + .center{ colspan: 2 } + %em Aucun dossier diff --git a/app/views/backoffice/invitations.html.haml b/app/views/backoffice/invitations.html.haml new file mode 100644 index 000000000..b9ffaad00 --- /dev/null +++ b/app/views/backoffice/invitations.html.haml @@ -0,0 +1,22 @@ +.col-md-12 + .default-data-block.default_visible + .row.show-block + .header + .title + .carret-right + .carret-down + = "#{@pending_avis.count} avis à rendre" + .body + = smart_listing_render :pending_avis + + %br + + .default-data-block + .row.show-block + .header + .title + .carret-right + .carret-down + = "#{@avis_with_answer.count} avis #{"rendu".pluralize(@avis_with_answer.count)}" + .body + = smart_listing_render :avis_with_answer diff --git a/app/views/backoffice/invitations.js.erb b/app/views/backoffice/invitations.js.erb new file mode 100644 index 000000000..a56437328 --- /dev/null +++ b/app/views/backoffice/invitations.js.erb @@ -0,0 +1,4 @@ +<%= smart_listing_update :pending_avis %> +<%= smart_listing_update :avis_with_answer %> + +link_init(); diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb index 80466e643..f83f30336 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.erb +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -1,4 +1,4 @@ -

Bonjour <%= @resource.email %>!

+

Bonjour,

Vous avez demandé à regénérer votre mot de passe sur la plateforme TPS. Pour ceci, merci de suivre le lien suivant :

@@ -6,10 +6,6 @@

Si vous n'avez pas effectué une telle demande, merci d'ignorer ce mail. Votre mot de passe ne sera pas changé.

-

Bonne journée

+

Bonne journée,

- -

- --- -
- L'équipe Téléprocédures Simplifiées

+

L'équipe Téléprocédures Simplifiées

diff --git a/app/views/dossiers/_avis.html.haml b/app/views/dossiers/_avis.html.haml new file mode 100644 index 000000000..489130448 --- /dev/null +++ b/app/views/dossiers/_avis.html.haml @@ -0,0 +1,56 @@ +- if current_gestionnaire && current_gestionnaire.assigned_on_procedure?(@facade.dossier.procedure_id) + + .default-data-block.default_visible + .row.show-block.infos + .header + .col-xs-12.title + .carret-right + .carret-down + AVIS EXTERNES + .body + .display-block-on-print + - dossier_facade.dossier.avis.by_latest.each do |avis| + - if avis.answer + .panel.panel-success + .panel-heading + %strong= avis.email_to_display + a donné son avis le + = avis.updated_at.localtime.strftime('%d/%m/%Y à %H:%M') + .panel-body + %strong Vous : + = avis.introduction + %hr + %strong= "#{avis.email_to_display} :" + = avis.answer + - else + .panel.panel-info + .panel-heading + Avis demandé à + %strong= avis.email_to_display + le + = avis.created_at.localtime.strftime('%d/%m/%Y à %H:%M') + .panel-body + %strong Vous : + = avis.introduction + %hr + .center + %em Avis en attente + + -# FIXME prevent bug when the user is also a gestionnaire on the procedure #375 + - if @new_avis.present? + .hidden-print + .panel.panel-default + .panel-heading + Demander un avis externe + .panel-body + .help-block + Invitez une personne externe à consulter le dossier et à vous donner un avis sur celui ci. + %br + Cette personne pourra également contribuer au fil de messagerie, mais ne pourra pas modifier le dossier. + + = simple_form_for @new_avis, url: backoffice_dossier_avis_index_path(dossier_facade.dossier.object.id) do |f| + + = f.input 'email', label: "Email de la personne qui doit donner un avis" + = f.input 'introduction', label: "Message" + + = f.submit "Envoyer la demande d'avis", class: 'btn btn-default' diff --git a/app/views/dossiers/_dossier_show.html.haml b/app/views/dossiers/_dossier_show.html.haml index 632a32f20..cfbe4ee02 100644 --- a/app/views/dossiers/_dossier_show.html.haml +++ b/app/views/dossiers/_dossier_show.html.haml @@ -1,3 +1,5 @@ += render partial: 'dossiers/edit_avis', locals: { dossier_facade: @facade } + = render partial: 'dossiers/messagerie', locals: { dossier_facade: @facade } - if @facade.procedure.individual_with_siret @@ -51,8 +53,7 @@ = render partial: '/users/carte/map', locals: { dossier: @facade.dossier } = render partial: 'users/carte/init_carto', locals: { dossier: @facade.dossier } - -- if @current_gestionnaire && gestionnaire_signed_in? && @champs_private.count > 0 +- if @current_gestionnaire && gestionnaire_signed_in? && current_gestionnaire.assigned_on_procedure?(@facade.dossier.procedure_id) && @champs_private.count > 0 .default-data-block.default_visible .row.show-block#private-fields .header @@ -65,3 +66,5 @@ = (private_fields_count == 1) ? "1 champ" : "#{private_fields_count} champs" .body = render partial: '/dossiers/infos_private_fields' + += render partial: 'dossiers/avis', locals: { dossier_facade: @facade } diff --git a/app/views/dossiers/_edit_avis.html.haml b/app/views/dossiers/_edit_avis.html.haml new file mode 100644 index 000000000..491291793 --- /dev/null +++ b/app/views/dossiers/_edit_avis.html.haml @@ -0,0 +1,13 @@ +- if current_gestionnaire + - avis_for_dossier = current_gestionnaire.avis.for_dossier(dossier_facade.dossier.id).by_latest + - if avis_for_dossier.any? + .panel.panel-default + .panel-body + %h4 Votre avis est sollicité sur le dossier : + - avis_for_dossier.each do |avis| + %hr + %p= avis.introduction + = simple_form_for avis, url: backoffice_dossier_avis_path(dossier_facade.dossier, avis) do |f| + = f.input 'answer', label: "Votre avis" + - submit_label = if avis.answer then "Modifier votre avis" else "Enregistrer votre avis" end + = f.submit submit_label, class: 'btn btn-default' diff --git a/app/views/gestionnaire_mailer/last_week_overview.html.haml b/app/views/gestionnaire_mailer/last_week_overview.html.haml new file mode 100644 index 000000000..a82bb9497 --- /dev/null +++ b/app/views/gestionnaire_mailer/last_week_overview.html.haml @@ -0,0 +1,26 @@ +%table{ align: 'center', border: '0', cellpadding: '0', cellspacing: '0', height: '100%', style: 'background-color: #fafafa', width: '100%' } + %tbody + %tr + %td{ align: 'center', style: 'height: 100%; margin: 0; padding: 30px; width: 100%; border-top: 0', valign: 'top' } + %table{ border: '0', cellpadding: '0', cellspacing: '0', style: 'border-collapse: collapse; border: 0; max-width: 600px!important;', width: '100%' } + %tbody + %tr + %td{ style: 'background: #ffffff none no-repeat center/cover; background-color: #ffffff; background-image: none; background-repeat: no-repeat; background-position: center; background-size: cover; border-top: 0; padding-top: 0;', valign: 'top' } + %table{ border: '0', cellpadding: '0', cellspacing: '0', style: 'min-width: 100%; border-collapse: collapse', width: '100%' } + %tr + %td{ style: 'padding: 0 30px; mso-line-height-rule: exactly; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; ', valign: 'top' } + %img{ align: 'middle', alt: 'Logo TPS', src: image_url('mailer/gestionnaire_mailer/logo.png'), style: 'max-width: 125px; padding: 30px 0; display: inline !important; vertical-align: bottom; border: 0; height: auto; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic;' } + %tr + %td{ style: 'padding: 0 30px 30px; word-break: break-word; color: #333333; font-family: Helvetica; font-size: 16px; line-height: 150%; text-align: left; border-bottom: 2px solid #4393F3;', valign: 'top' } + Bonjour, voici votre résumé de l'activité de la semaine du #{l(@args[:start_date], format: '%d %B')} au #{l(DateTime.now, format: '%d %B')}. + %br + %br + + - @args[:procedure_overviews].each do |procedure_overview| + = procedure_overview.to_html.html_safe + %br + %br + Bonne journée, + %br + %br + L'équipe Téléprocédures Simplifiées diff --git a/app/views/gestionnaire_mailer/new_assignement.text.erb b/app/views/gestionnaire_mailer/new_assignement.text.erb index 694f454b8..9af14a3e6 100644 --- a/app/views/gestionnaire_mailer/new_assignement.text.erb +++ b/app/views/gestionnaire_mailer/new_assignement.text.erb @@ -1,4 +1,4 @@ -Bienvenue sur la plateforme TPS +Bienvenue sur la plateforme TPS, Vous venez d'être assigné à un administrateur sur la plateforme TPS. Voici quelques informations utiles : @@ -7,5 +7,4 @@ Vous venez d'être assigné à un administrateur sur la plateforme TPS. Voici qu Bonne journée, ---- L'équipe Téléprocédures Simplifiées diff --git a/app/views/gestionnaire_mailer/new_gestionnaire.text.erb b/app/views/gestionnaire_mailer/new_gestionnaire.text.erb index f7472cb9e..7fdcce89d 100644 --- a/app/views/gestionnaire_mailer/new_gestionnaire.text.erb +++ b/app/views/gestionnaire_mailer/new_gestionnaire.text.erb @@ -1,4 +1,4 @@ -Bienvenue sur la plateforme TPS +Bienvenue sur la plateforme TPS, Vous venez d'être nommé accompagnateur sur la plateforme TPS. Pour mémoire, voici quelques informations utiles : @@ -8,5 +8,4 @@ Vous venez d'être nommé accompagnateur sur la plateforme TPS. Pour mémoire, v Bonne journée, ---- L'équipe Téléprocédures Simplifiées diff --git a/app/views/invite_mailer/invite_guest.text.erb b/app/views/invite_mailer/invite_guest.text.erb index 73894115d..48774e6e0 100644 --- a/app/views/invite_mailer/invite_guest.text.erb +++ b/app/views/invite_mailer/invite_guest.text.erb @@ -1,12 +1,11 @@ -Bonjour <%= @invite.email %> +Bonjour, 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. +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)+"?email=#{@invite.email}" %>. +Afin de répondre à cette invitation, merci de vous inscrire avec l'adresse email <%= @invite.email %> sur <%= users_dossiers_invite_url(@invite.id)+"?email=#{@invite.email}" %>. -Bonne journée. +Bonne journée, ---- L'équipe Téléprocédures Simplifiées diff --git a/app/views/invite_mailer/invite_user.text.erb b/app/views/invite_mailer/invite_user.text.erb index 53e98e30c..3c1b9abee 100644 --- a/app/views/invite_mailer/invite_user.text.erb +++ b/app/views/invite_mailer/invite_user.text.erb @@ -1,10 +1,9 @@ -Bonjour <%= @invite.email %> +Bonjour, L'utilisateur <%= @invite.email_sender %> souhaite que vous participiez à l'élaboration d'un dossier sur la plateforme TPS. Pour le consulter, merci de suivre ce lien : <%= users_dossiers_invite_url(@invite.id) %> -Bonne journée. +Bonne journée, ---- L'équipe Téléprocédures Simplifiées diff --git a/app/views/layouts/_google_analytics.html.haml b/app/views/layouts/_google_analytics.html.haml new file mode 100644 index 000000000..5ab328f45 --- /dev/null +++ b/app/views/layouts/_google_analytics.html.haml @@ -0,0 +1,9 @@ +- ua_id = Rails.env.production? ? 'UA-63927236-2' : 'UA-63927236-4' +:javascript + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + + ga('create', '#{ua_id}', 'auto'); + ga('send', 'pageview'); diff --git a/app/views/layouts/_new_header.haml b/app/views/layouts/_new_header.haml index 82554fd60..6e656bbc4 100644 --- a/app/views/layouts/_new_header.haml +++ b/app/views/layouts/_new_header.haml @@ -1,7 +1,6 @@ .new-header{ class: current_page?(root_path) ? nil : 'new-header-with-border' } .header-inner-content - %img.header-logo.pull-left{ src: image_url("header/logo-tps.svg") } + = link_to root_path do + %img.header-logo{ src: image_url("header/logo-tps.svg") } - = link_to "Connexion", new_user_session_path, :class => "header-login-button pull-right" - - .clear-fix + = link_to "Connexion", new_user_session_path, class: "header-login-button" diff --git a/app/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_index.html.haml b/app/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_index.html.haml index c1dfe2bd7..d2ea6ea5b 100644 --- a/app/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_index.html.haml +++ b/app/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_index.html.haml @@ -20,6 +20,10 @@ #infos-block .split-hr-left #procedure-list + - if current_gestionnaire.avis.any? + = link_to backoffice_invitations_path do + .procedure-list-element{ class: ('active' if request.path == backoffice_invitations_path) } + Invitations - current_gestionnaire.procedures.by_libelle.each do |procedure| = link_to backoffice_dossiers_procedure_path(procedure.id), { title: procedure.libelle } do .procedure-list-element{ class: ('active' if procedure.id.to_s == params[:id]) } diff --git a/app/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_show.html.haml b/app/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_show.html.haml index ccc0aaa36..2560a0bb8 100644 --- a/app/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_show.html.haml +++ b/app/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_show.html.haml @@ -2,8 +2,8 @@ .infos #dossier_id= t('dynamics.dossiers.numéro') + @facade.dossier.id.to_s -#action-block - - if gestionnaire_signed_in? +- if current_gestionnaire && current_gestionnaire.assigned_on_procedure?(@facade.dossier.procedure_id) + #action-block - if !@facade.dossier.read_only? || @facade.dossier.initiated? = link_to 'Passer en instruction', backoffice_dossier_receive_path(@facade.dossier), method: :post, class: 'btn btn-danger btn-block', data: { confirm: "Confirmer vous le passage en instruction de ce dossier ?" } @@ -51,12 +51,12 @@ - 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}" + .type= "Un attribut a été changé: #{notification.liste.last}" - elsif ['piece_justificative'].include?(notification.type_notif) - if notification.liste.size > 1 .type= "Plusieurs pièces jointes ont été changés, dont: #{notification.liste.join(" ")}" - else - .type= "Une pièce jointe à été changée: #{notification.liste.last}" + .type= "Une pièce jointe a été changée: #{notification.liste.last}" - else .type= notification.liste.last .split-hr diff --git a/app/views/layouts/left_panels/_left_panel_backofficecontroller_invitations.html.haml b/app/views/layouts/left_panels/_left_panel_backofficecontroller_invitations.html.haml new file mode 100644 index 000000000..b4114cab1 --- /dev/null +++ b/app/views/layouts/left_panels/_left_panel_backofficecontroller_invitations.html.haml @@ -0,0 +1 @@ += render partial: 'layouts/left_panels/left_panel_backoffice_dossierscontroller_index' diff --git a/app/views/layouts/left_panels/_left_panel_users_dossierscontroller_index.html.haml b/app/views/layouts/left_panels/_left_panel_users_dossierscontroller_index.html.haml index 9a6fff7a1..9b561f312 100644 --- a/app/views/layouts/left_panels/_left_panel_users_dossierscontroller_index.html.haml +++ b/app/views/layouts/left_panels/_left_panel_users_dossierscontroller_index.html.haml @@ -15,25 +15,25 @@ .procedure-list-element#brouillon{ class: ('active' if @liste == 'brouillon') } Brouillons .badge.progress-bar-default - = @user_dossiers.brouillon.count + = @user_dossiers.state_brouillon.count %a{ :href => "#{url_for users_dossiers_path(liste: 'a_traiter')}", 'data-toggle' => :tooltip, title: 'Les dossiers qui requièrent une action de votre part.' } .procedure-list-element#a_traiter{ class: ('active' if @liste == 'a_traiter') } En construction .badge.progress-bar-danger - = @user_dossiers.en_construction.count + = @user_dossiers.state_en_construction.count %a{ :href => "#{url_for users_dossiers_path(liste: 'en_instruction')}", 'data-toggle' => :tooltip, title: 'Les dossiers en cours d\'examen par l\'administration compétante.' } .procedure-list-element#en_instruction{ class: ('active' if @liste == 'en_instruction') } En instruction .badge.progress-bar-default - = @user_dossiers.en_instruction.count + = @user_dossiers.state_en_instruction.count %a{ :href => "#{url_for users_dossiers_path(liste: 'termine')}", 'data-toggle' => :tooltip, title: 'Les dossiers cloturés qui peuvent être "Accepté", "Refusé" ou "Sans suite".' } .procedure-list-element#termine{ class: ('active' if @liste == 'termine') } Terminé .badge.progress-bar-success - = @user_dossiers.termine.count + = @user_dossiers.state_termine.count %a{ :href => "#{url_for users_dossiers_path(liste: 'invite')}" } .procedure-list-element#invite{ class: ('active' if @liste == 'invite') } diff --git a/app/views/layouts/navbars/_navbar_backoffice_dossierscontroller_show.html.haml b/app/views/layouts/navbars/_navbar_backoffice_dossierscontroller_show.html.haml index 97027181d..6604a98c1 100644 --- a/app/views/layouts/navbars/_navbar_backoffice_dossierscontroller_show.html.haml +++ b/app/views/layouts/navbars/_navbar_backoffice_dossierscontroller_show.html.haml @@ -1,40 +1,43 @@ .col-xs-7.main-info = @facade.dossier.procedure.libelle .col-xs-3.options - .row - .col-xs-12 - - if current_gestionnaire.follow?(@facade.dossier.id) - = link_to backoffice_dossier_follow_path(dossier_id: @facade.dossier.id), "data-method" => :put, class: "button-navbar-action", id: "suivre_dossier_#{@facade.dossier.id}" do - %i.fa.fa-user-times - Ne plus suivre - - else - = link_to backoffice_dossier_follow_path(dossier_id: @facade.dossier.id), 'data-method' => :put, class: 'button-navbar-action', id: "suivre_dossier_#{@facade.dossier.id}" do - %i.fa.fa-user-plus - Suivre le dossier - .row - .col-xs-12 - #invitations.dropdown-toggle{ 'data-toggle' => 'dropdown', 'aria-haspopup' => true, 'aria-expanded' => false } - %i.fa.fa-user - = t('utils.involved') - .badge.progress-bar-info - = @facade.dossier.invites.count - .dropdown-menu.dropdown-menu-right.dropdown-pannel - %h4= t('dynamics.dossiers.followers.title') - %ul - - unless @facade.followers.empty? - - @facade.followers.each do |follower| - %li= follower.email - - else - = t('dynamics.dossiers.followers.empty') - %h4= t('dynamics.dossiers.invites.title') - %ul - - unless @facade.invites.empty? - - @facade.invites.each do |invite| - %li= invite.email - - else - = t('dynamics.dossiers.invites.empty') + - if current_gestionnaire.assigned_on_procedure?(@facade.dossier.procedure_id) + .row + .col-xs-12 + - if current_gestionnaire.follow?(@facade.dossier.id) + = link_to backoffice_dossier_follow_path(dossier_id: @facade.dossier.id), "data-method" => :put, class: "button-navbar-action", id: "suivre_dossier_#{@facade.dossier.id}" do + %i.fa.fa-user-times + Ne plus suivre + - else + = link_to backoffice_dossier_follow_path(dossier_id: @facade.dossier.id), 'data-method' => :put, class: 'button-navbar-action', id: "suivre_dossier_#{@facade.dossier.id}" do + %i.fa.fa-user-plus + Suivre le dossier + .row + .col-xs-12 + #invitations.dropdown-toggle{ 'data-toggle' => 'dropdown', 'aria-haspopup' => true, 'aria-expanded' => false } + %i.fa.fa-user + = t('utils.involved') + .badge.progress-bar-info + = @facade.dossier.invites.count + .dropdown-menu.dropdown-menu-right.dropdown-pannel + %h4= t('dynamics.dossiers.followers.title') + %ul + - unless @facade.followers.empty? + - @facade.followers.each do |follower| + %li= follower.email + - else + = t('dynamics.dossiers.followers.empty') + %h4= t('dynamics.dossiers.invites.title') + %p + %b Attention, les invitations sur les dossiers vont disparaître en faveur des avis externes situés en bas de la page + %ul + - unless @facade.invites.empty? + - @facade.invites.each do |invite| + %li= invite.email + - else + = t('dynamics.dossiers.invites.empty') - %li - = 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', data: { confirm: "Envoyer l'invitation ?" } + %li + = 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', data: { confirm: "Envoyer l'invitation ?" } diff --git a/app/views/layouts/navbars/_navbar_backofficecontroller_invitations.html.haml b/app/views/layouts/navbars/_navbar_backofficecontroller_invitations.html.haml new file mode 100644 index 000000000..eb80c1517 --- /dev/null +++ b/app/views/layouts/navbars/_navbar_backofficecontroller_invitations.html.haml @@ -0,0 +1,2 @@ +.col-xs-10.main-info + INVITATIONS diff --git a/app/views/layouts/new_application.html.haml b/app/views/layouts/new_application.html.haml index ba6539661..19c5bc700 100644 --- a/app/views/layouts/new_application.html.haml +++ b/app/views/layouts/new_application.html.haml @@ -3,6 +3,7 @@ %meta{ "http-equiv" => "Content-Type", :content => "text/html; charset=UTF-8" } %meta{ "http-equiv" => "X-UA-Compatible", :content => "IE=edge" } %meta{ :name => "turbolinks-cache-control", :content => "no-cache" } + %meta{ name: "viewport", content: "width=device-width, initial-scale=1" } = csrf_meta_tags = action_cable_meta_tag @@ -13,7 +14,7 @@ = favicon_link_tag(image_url("favicons/32x32.png"), type: "image/png", sizes: "32x32") = favicon_link_tag(image_url("favicons/96x96.png"), type: "image/png", sizes: "96x96") - = stylesheet_link_tag "new_application", :media => "all", "data-turbolinks-track" => true + = stylesheet_link_tag "new_design/new_application", :media => "all", "data-turbolinks-track" => true = stylesheet_link_tag "print", :media => "print", "data-turbolinks-track" => true %body @@ -36,6 +37,7 @@ = render partial: "layouts/mailjet_newsletter" = javascript_include_tag "application", "data-turbolinks-track" => true + = yield :charts_js - if Rails.env == "test" %script{ :type => "text/javascript" } (typeof jQuery !== "undefined") && (jQuery.fx.off = true); diff --git a/app/views/mails/closed_mail.html.haml b/app/views/mails/closed_mail.html.haml index 5870fc1cf..2acc43fa7 100644 --- a/app/views/mails/closed_mail.html.haml +++ b/app/views/mails/closed_mail.html.haml @@ -1,4 +1,4 @@ -Bonjour +Bonjour, %br %br Votre dossier nº --numero_dossier-- a été accepté le --date_de_decision--. @@ -7,11 +7,13 @@ Votre dossier nº --numero_dossier-- a été accepté le --date_de_decision--. A tout moment, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier-- %br %br -Bonne journée +Bonne journée, %br %br -\--- +L'équipe Téléprocédures Simplifiées %br -Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme. %br -\--- +— +%br +%br +Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme. diff --git a/app/views/mails/initiated_mail.html.haml b/app/views/mails/initiated_mail.html.haml index ce3ae6324..b2e170224 100644 --- a/app/views/mails/initiated_mail.html.haml +++ b/app/views/mails/initiated_mail.html.haml @@ -1,4 +1,4 @@ -Bonjour +Bonjour, %br %br Votre administration vous confirme la bonne réception de votre dossier nº --numero_dossier--. @@ -7,11 +7,13 @@ Votre administration vous confirme la bonne réception de votre dossier nº --n A tout moment, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier-- %br %br -Bonne journée +Bonne journée, %br %br -\--- +L'équipe Téléprocédures Simplifiées %br -Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme. %br -\--- +— +%br +%br +Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme. diff --git a/app/views/mails/received_mail.html.haml b/app/views/mails/received_mail.html.haml index 113139052..acd67a6a6 100644 --- a/app/views/mails/received_mail.html.haml +++ b/app/views/mails/received_mail.html.haml @@ -1,14 +1,16 @@ -Bonjour +Bonjour, %br %br Votre administration vous confirme la bonne réception de votre dossier nº --numero_dossier--. Celui-ci sera instruit dans le délai légal déclaré par votre interlocuteur. %br %br -Bonne journée +Bonne journée, %br %br -\--- +L'équipe Téléprocédures Simplifiées %br -Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme. %br -\--- +— +%br +%br +Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme. diff --git a/app/views/mails/refused_mail.html.haml b/app/views/mails/refused_mail.html.haml index b2796bf3c..83415496b 100644 --- a/app/views/mails/refused_mail.html.haml +++ b/app/views/mails/refused_mail.html.haml @@ -1,4 +1,4 @@ -Bonjour +Bonjour, %br %br Votre dossier nº --numero_dossier-- a été refusé le --date_de_decision--. @@ -7,11 +7,13 @@ Votre dossier nº --numero_dossier-- a été refusé le --date_de_decision--. Pour en savoir plus sur le motif du refus, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier-- %br %br -Bonne journée +Bonne journée, %br %br -\--- +L'équipe Téléprocédures Simplifiées %br -Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme. %br -\--- +— +%br +%br +Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme. diff --git a/app/views/mails/without_continuation_mail.html.haml b/app/views/mails/without_continuation_mail.html.haml index ddac75477..7882763f2 100644 --- a/app/views/mails/without_continuation_mail.html.haml +++ b/app/views/mails/without_continuation_mail.html.haml @@ -1,4 +1,4 @@ -Bonjour +Bonjour, %br %br Votre dossier nº --numero_dossier-- a été classé sans suite le --date_de_decision--. @@ -7,11 +7,13 @@ Votre dossier nº --numero_dossier-- a été classé sans suite le --date_de_de Pour en savoir plus sur les raisons de ce classement sans suite, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier-- %br %br -Bonne journée +Bonne journée, %br %br -\--- +L'équipe Téléprocédures Simplifiées %br -Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme. %br -\--- +— +%br +%br +Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme. diff --git a/app/views/new_admin_mailer/new_admin_email.text.erb b/app/views/new_admin_mailer/new_admin_email.text.erb index 547713f13..678123a26 100644 --- a/app/views/new_admin_mailer/new_admin_email.text.erb +++ b/app/views/new_admin_mailer/new_admin_email.text.erb @@ -5,5 +5,6 @@ Plateforme : <%= TPS::Application::URL %> Login : <%= @admin.email %> Password : <%= @password %> ---- +Bonne journée, + L'équipe Téléprocédures Simplifiées diff --git a/app/views/notification_mailer/new_answer.text.erb b/app/views/notification_mailer/new_answer.text.erb index 039d306de..240df0b81 100644 --- a/app/views/notification_mailer/new_answer.text.erb +++ b/app/views/notification_mailer/new_answer.text.erb @@ -1,14 +1,13 @@ -Bonjour <%= @user.email %> +Bonjour, Un nouveau message est disponible dans votre espace TPS. Pour le consulter, merci de vous rendre sur <%=users_dossier_recapitulatif_url(dossier_id: @dossier.id)%> -Bonne journée +Bonne journée, ---- -Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme. ---- - ---- L'équipe Téléprocédures Simplifiées + +— + +Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme. diff --git a/app/views/root/landing.html.haml b/app/views/root/landing.html.haml index 561246170..ddff3b458 100644 --- a/app/views/root/landing.html.haml +++ b/app/views/root/landing.html.haml @@ -1,23 +1,24 @@ .landing .landing-panel.hero-panel .landing-panel-inner-content - .hero-text.pull-left - %p.hero-tagline - %em.hero-tagline-em Dématérialisez - %br - vos procédures administratives en quelques minutes + .hero-wrapper + .hero-text + %p.hero-tagline + %em.hero-tagline-em Dématérialisez + %br + vos procédures administratives en quelques minutes - = link_to "Demander une démo", - "mailto:#{t("dynamics.contact_email")}?subject=Demande de démo TPS", - :class => "hero-button" + = link_to "Demander une démo", + "mailto:#{t("dynamics.contact_email")}?subject=Demande de démo TPS", + class: "hero-button", + target: "_blank", + onclick: "javascript: ga('send', 'pageview', '/demander-une-demo')" - %p.hero-phone-cta - ou nous appeler au 01 40 15 68 49 + %p.hero-phone-cta + ou nous appeler au 01 40 15 68 49 - .hero-illustration.pull-right - %img{ :src => image_url("landing/hero/dematerialiser.svg") } - - .clearfix + .hero-illustration + %img{ :src => image_url("landing/hero/dematerialiser.svg") } .landing-panel.features-panel .landing-panel-inner-content @@ -55,28 +56,26 @@ %ul.quotes %li.quote - %img.quote-quotation-mark.pull-left{ :src => image_url("landing/testimonials/quotation-mark.svg") } - %p.quote-content.pull-right - TPS est un outil de dématérialisation adapté au dépôt de dossiers de demande d’inscription au registre des transporteurs routiers en Ile-de-France. Les échanges avec les usagers sont facilités, ce qui permet de réduire les délais d’instructions et de gagner en efficacité. - .clearfix + %img.quote-quotation-mark{ :src => image_url("landing/testimonials/quotation-mark.svg") } + .quote-content-wrapper + %p.quote-content + TPS est un outil de dématérialisation adapté au dépôt de dossiers de demande d’inscription au registre des transporteurs routiers en Ile-de-France. Les échanges avec les usagers sont facilités, ce qui permet de réduire les délais d’instructions et de gagner en efficacité. - %p.quote-author.pull-left - %span.quote-author-name Elodie Le Rhun - %br - Chef de bureau, DRIEA Ile-de-France - .clearfix + %p.quote-author + %span.quote-author-name Elodie Le Rhun + %br + Chef de bureau, DRIEA Ile-de-France %li.quote - %img.quote-quotation-mark.pull-left{ :src => image_url("landing/testimonials/quotation-mark.svg") } - %p.quote-content.pull-right - TPS c’est surtout l’assurance d’un dialogue en toute fluidité et en toute transparence entre les porteurs de projet et l’administration. Un service qui garantit une économie de temps et beaucoup moins de manipulations des dossiers. - .clearfix + %img.quote-quotation-mark{ :src => image_url("landing/testimonials/quotation-mark.svg") } + .quote-content-wrapper + %p.quote-content + TPS c’est surtout l’assurance d’un dialogue en toute fluidité et en toute transparence entre les porteurs de projet et l’administration. Un service qui garantit une économie de temps et beaucoup moins de manipulations des dossiers. - %p.quote-author.pull-left - %span.quote-author-name Nadja Briki - %br - Déléguée de la Préfète du Pas-de-Calais - .clearfix + %p.quote-author + %span.quote-author-name Nadja Briki + %br + Déléguée de la Préfète du Pas-de-Calais - cache "numbers-panel", :expires_in => 3.hours do .landing-panel.numbers-panel @@ -128,14 +127,15 @@ .landing-panel.cta-panel .landing-panel-inner-content - .pull-right - = link_to "Demander une démo", - "mailto:#{t('dynamics.contact_email')}?subject=Demande de démo TPS", - :class => "cta-panel-button" - %p.cta-panel-phone-cta - ou nous appeler au 01 40 15 68 49 - .pull-left - %h1.cta-panel-title Commencez à dématerialiser vos procédures - %p.cta-panel-explanation Nous vous accompagnons dans la prise en main de l’outil - .clearfix - + .cta-panel-wrapper + %div + %h1.cta-panel-title Commencez à dématerialiser vos procédures + %p.cta-panel-explanation Nous vous accompagnons dans la prise en main de l’outil + %div + = link_to "Demander une démo", + "mailto:#{t('dynamics.contact_email')}?subject=Demande de démo TPS", + class: "cta-panel-button", + target: "_blank", + onclick: "javascript: ga('send', 'pageview', '/demander-une-demo')" + %p.cta-panel-phone-cta + ou nous appeler au 01 40 15 68 49 diff --git a/app/views/stats/index.html.haml b/app/views/stats/index.html.haml index c3d40561a..4ca7c7914 100644 --- a/app/views/stats/index.html.haml +++ b/app/views/stats/index.html.haml @@ -3,49 +3,87 @@ %h1.new-h1 Statistiques .stat-cards - - .card.stat-card.stat-card-half.pull-left - %ul.segmented-control.pull-right - %li.segmented-control-item.segmented-control-item-active{ :onclick => "TPS.toggleChart(event, '.cumulative-procedures-chart');" } - Cumul - %li.segmented-control-item{ :onclick => "TPS.toggleChart(event, '.flux-procedures-chart');" } - Flux (30 jours) - %span.stat-card-title.pull-left Procédures dématérialisées - .clearfix - - .chart-container - .chart.cumulative-procedures-chart - = area_chart @procedures_cumulative, - :colors => ["rgba(61, 149, 236, 1)"] - .chart.flux-procedures-chart.hidden - = line_chart @procedures_30_days_flow, - :colors => ["rgba(61, 149, 236, 1)"] - - .card.stat-card.stat-card-half.pull-left - %ul.segmented-control.pull-right - %li.segmented-control-item.segmented-control-item-active{ :onclick => "TPS.toggleChart(event, '.cumulative-dossiers-chart');" } - Cumul - %li.segmented-control-item{ :onclick => "TPS.toggleChart(event, '.flux-dossiers-chart');" } - Flux (30 jours) - %span.stat-card-title.pull-left Dossiers déposés - .clearfix - - .chart-container - .chart.cumulative-dossiers-chart - = area_chart @dossiers_cumulative, - :colors => ["rgba(61, 149, 236, 1)"] - .chart.flux-dossiers-chart.hidden - = line_chart @dossiers_30_days_flow, - :colors => ["rgba(61, 149, 236, 1)"] - - .card.stat-card.stat-card-half.big-number-card.pull-left + .stat-card.stat-card-half.big-number-card.pull-left %span.big-number-card-title TOTAL PROCÉDURES DÉMATÉRIALISÉES %span.big-number-card-number = number_with_delimiter(@procedures_count) - .card.stat-card.stat-card-half.big-number-card.pull-left + .stat-card.stat-card-half.big-number-card.pull-left %span.big-number-card-title TOTAL DOSSIERS DÉPOSÉS %span.big-number-card-number = number_with_delimiter(@dossiers_count) + .stat-card.stat-card-half.pull-left + %ul.segmented-control.pull-right + %li.segmented-control-item.segmented-control-item-active{ :onclick => "TPS.toggleChart(event, '.monthly-procedures-chart');" } + Par mois + %li.segmented-control-item{ :onclick => "TPS.toggleChart(event, '.cumulative-procedures-chart');" } + Cumul + %span.stat-card-title.pull-left Procédures dématérialisées + .clearfix + + .chart-container + .chart.monthly-procedures-chart + = column_chart @procedures_in_the_last_4_months + .chart.cumulative-procedures-chart.hidden + = area_chart @procedures_cumulative + + .stat-card.stat-card-half.pull-left + %ul.segmented-control.pull-right + %li.segmented-control-item.segmented-control-item-active{ :onclick => "TPS.toggleChart(event, '.monthly-dossiers-chart');" } + Par mois + %li.segmented-control-item{ :onclick => "TPS.toggleChart(event, '.cumulative-dossiers-chart');" } + Cumul + %span.stat-card-title.pull-left Dossiers déposés + .clearfix + + .chart-container + .chart.monthly-dossiers-chart + = column_chart @dossiers_in_the_last_4_months + .chart.cumulative-dossiers-chart.hidden + = area_chart @dossiers_cumulative + + .stat-card.stat-card-half.pull-left + %span.stat-card-title + Nombre d'administrations ayant dématérialisé N procédures + + .chart-container + .chart + = pie_chart @procedures_count_per_administrateur + + - if administration_signed_in? + .stat-card.stat-card-half.pull-left + %span.stat-card-title Temps de traitement moyen d'un dossier + + .chart-container + .chart + = line_chart @dossier_instruction_mean_time, + :ytitle => "Jours" + + .stat-card.stat-card-half.pull-left + %span.stat-card-title Temps de remplissage moyen d'un dossier + + .chart-container + .chart + = line_chart @dossier_filling_mean_time, + :ytitle => "Minutes" + .clearfix + + - if administration_signed_in? + %h2.new-h2 Avis + + .stat-cards + .stat-card.stat-card-half.pull-left + %span.stat-card-title Taux d'utilisation des avis + = line_chart @avis_usage, ytitle: 'dossiers avec avis / total dossiers', xtitle: 'semaines' + + .stat-card.stat-card-half.pull-left + %span.stat-card-title Temps de réponse moyen par avis + = line_chart @avis_average_answer_time, ytitle: 'jours', xtitle: 'semaines' + + .stat-card.stat-card-half.pull-left + %span.stat-card-title Pourcentage d'avis rempli + = line_chart @avis_answer_percentages, ytitle: 'avis avec réponse / total avis', xtitle: 'semaines' + + .clearfix diff --git a/app/views/users/description/champs/_date.html.haml b/app/views/users/description/champs/_date.html.haml new file mode 100644 index 000000000..f04b581a6 --- /dev/null +++ b/app/views/users/description/champs/_date.html.haml @@ -0,0 +1,5 @@ +%input.form-control{ name: "champs['#{champ.id}']", + placeholder: "JJ/MM/AAAA", + id: "champs_#{champ.id}", + value: champ.value, + type: "date" } diff --git a/app/views/users/description/champs/_render_list_champs.html.haml b/app/views/users/description/champs/_render_list_champs.html.haml index 01c395d33..6bb776379 100644 --- a/app/views/users/description/champs/_render_list_champs.html.haml +++ b/app/views/users/description/champs/_render_list_champs.html.haml @@ -47,14 +47,16 @@ - when 'explication' + - when 'date' + = render partial: 'users/description/champs/date', locals: { champ: champ } + - else %input.form-control{ name: "champs['#{champ.id}']", placeholder: champ.libelle, id: "champs_#{champ.id}", value: champ.value, - type: champ.type_champ, - 'data-provide' => champ.data_provide, - 'data-date-format' => champ.data_date_format } + type: champ.type_champ } + - unless champ.description.empty? %div{ id: "description_champs_#{champ.id}", class: ('help-block' unless champ.type_champ == 'engagement') } diff --git a/app/views/welcome_mailer/welcome_email.text.erb b/app/views/welcome_mailer/welcome_email.text.erb index 90b547ce5..5fb3d1d8a 100644 --- a/app/views/welcome_mailer/welcome_email.text.erb +++ b/app/views/welcome_mailer/welcome_email.text.erb @@ -1,8 +1,8 @@ -Bienvenue sur la plateforme TPS +Bienvenue sur la plateforme TPS, Nous vous remercions de vous être inscrit sur TPS. Pour mémoire, voici quelques informations utiles : - URL : <%= root_url %>> + URL : <%= root_url %> Login : <%= @user.email %> Oubli de mot de passe, pas de problème : @@ -11,5 +11,4 @@ Oubli de mot de passe, pas de problème : Bonne journée, ---- L'équipe Téléprocédures Simplifiées diff --git a/app/workers/auto_archive_procedure_worker.rb b/app/workers/auto_archive_procedure_worker.rb index 61701b4ff..54875a961 100644 --- a/app/workers/auto_archive_procedure_worker.rb +++ b/app/workers/auto_archive_procedure_worker.rb @@ -2,13 +2,12 @@ class AutoArchiveProcedureWorker include Sidekiq::Worker def perform(*args) - procedures_to_archive = Procedure.not_archived.where("auto_archive_on <= ?", Date.today) + Procedure.not_archived.where("auto_archive_on <= ?", Date.today).each do |procedure| + procedure.dossiers.state_en_construction.each do |dossier| + dossier.received! + end - procedures_to_archive.each do |p| - p.dossiers.en_construction.update_all(state: :received) + procedure.update_attributes!(archived: true) end - - procedures_to_archive.update_all(archived: true, auto_archive_on: nil) - end end diff --git a/app/workers/weekly_overview_worker.rb b/app/workers/weekly_overview_worker.rb new file mode 100644 index 000000000..8f15194a7 --- /dev/null +++ b/app/workers/weekly_overview_worker.rb @@ -0,0 +1,13 @@ +class WeeklyOverviewWorker + include Sidekiq::Worker + + def perform(*args) + # Feature flipped to avoid mails in staging due to unprocessed dossier + if Features.weekly_overview + Gestionnaire.all + .map { |gestionnaire| [gestionnaire, gestionnaire.last_week_overview] } + .reject { |_, overview| overview.nil? } + .each { |gestionnaire, overview| GestionnaireMailer.last_week_overview(gestionnaire, overview).deliver_now } + end + end +end diff --git a/config/deploy.rb b/config/deploy.rb index eae817ea8..1895843ba 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -15,7 +15,6 @@ raise "Bad to=#{+ENV['to']}" unless ["staging", "production"].include?(ENV['to'] raise "missing domain, run with 'rake deploy domain=37.187.154.237'" if ENV['domain'].nil? -print "Deploy to #{ENV['to']} environment branch #{branch}\n" # set :domain, '5.135.190.60' set :domain, ENV['domain'] @@ -26,25 +25,19 @@ set :deploy_to, '/var/www/tps_dev' case ENV["to"] when "staging" - if ENV['branch'].nil? - set :branch, 'staging' - else - set :branch, ENV['branch'] - end + set :branch, ENV['branch'] || 'develop' set :deploy_to, '/var/www/tps_dev' set :user, 'tps_dev' # Username in the server to SSH to. appname = 'tps_dev' when "production" - if ENV['branch'].nil? - set :branch, 'master' - else - set :branch, ENV['branch'] - end + set :branch, ENV['branch'] || 'master' set :deploy_to, '/var/www/tps' set :user, 'tps' # Username in the server to SSH to. appname = 'tps' end +print "Deploy to #{ENV['to']} environment branch #{branch}\n" + set :rails_env, ENV["to"] # For system-wide RVM install. @@ -76,7 +69,6 @@ set :shared_paths, [ 'config/france_connect.yml', 'config/initializers/mailjet.rb', 'config/initializers/storage_url.rb', - 'app/views/layouts/_google_analytics.html', 'app/views/cgu/index.html.haml' ] diff --git a/config/environments/development.rb b/config/environments/development.rb index acb324db7..a03dda8e0 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -42,6 +42,7 @@ Rails.application.configure do # Action Mailer settings config.action_mailer.delivery_method = :smtp config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + config.action_mailer.asset_host = 'http://localhost:3000' # Config for mailcatcher https://mailcatcher.me/ config.action_mailer.smtp_settings = { :address => "localhost", diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 882590de7..c20bc1259 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -8,4 +8,4 @@ Rails.application.config.assets.version = '1.0' # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. -Rails.application.config.assets.precompile += %w(print.css new_application.css) +Rails.application.config.assets.precompile += %w(print.css new_design/new_application.css) diff --git a/config/initializers/chartkick.rb b/config/initializers/chartkick.rb new file mode 100644 index 000000000..3fa55389c --- /dev/null +++ b/config/initializers/chartkick.rb @@ -0,0 +1,4 @@ +Chartkick.options = { + content_for: :charts_js, + colors: ["rgba(191, 220, 249, 1)", "rgba(113, 176, 239, 1)", "rgba(61, 149, 236, 1)"] +} diff --git a/config/initializers/features.yml b/config/initializers/features.yml index e91fb5346..7776e7e30 100644 --- a/config/initializers/features.yml +++ b/config/initializers/features.yml @@ -1 +1,2 @@ remote_storage: false +weekly_overview: false diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index a3bd49fb8..864ba4221 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -15,6 +15,7 @@ ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.irregular 'type_de_champ', 'types_de_champ' inflect.irregular 'type_de_champ_private', 'types_de_champ_private' inflect.irregular 'assign_to', 'assign_tos' + inflect.irregular('avis', 'avis') end # These inflection rules are supported but not enabled by default: @@ -24,4 +25,5 @@ end ActiveSupport::Inflector.inflections(:fr) do |inflect| inflect.plural(/$/, 's') inflect.plural(/(hib|ch|bij|caill|p|gen|jouj)ou$/i, '\1oux') + inflect.irregular('avis', 'avis') end diff --git a/config/routes.rb b/config/routes.rb index 16b0b090a..8e2c90217 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -30,6 +30,9 @@ Rails.application.routes.draw do put '/gestionnaires' => 'gestionnaires/registrations#update', :as => 'gestionnaires_registration' end + get 'avis/:id/sign_up/email/:email' => 'backoffice/avis#sign_up', constraints: { email: /.*/ }, as: 'avis_sign_up' + post 'avis/:id/sign_up/email/:email' => 'backoffice/avis#create_gestionnaire', constraints: { email: /.*/ } + devise_scope :administrateur do get '/administrateurs/sign_in/demo' => 'administrateurs/sessions#demo' end @@ -167,6 +170,8 @@ Rails.application.routes.draw do resource :private_formulaire + get 'invitations' + resources :dossiers do post 'receive' => 'dossiers#receive' post 'refuse' => 'dossiers#refuse' @@ -179,6 +184,7 @@ Rails.application.routes.draw do post 'reopen' => 'dossiers#reopen' put 'follow' => 'dossiers#follow' resources :commentaires, only: [:index] + resources :avis, only: [:create, :update] end namespace :dossiers do diff --git a/config/schedule.yml b/config/schedule.yml index 79dbdefc9..9a2979d42 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -1,3 +1,6 @@ auto_archive_procedure: cron: "* * * * *" class: "AutoArchiveProcedureWorker" +weekly_overview_worker: + cron: "0 8 * * 0" + class: "WeeklyOverviewWorker" diff --git a/db/migrate/20170425100757_create_avis.rb b/db/migrate/20170425100757_create_avis.rb new file mode 100644 index 000000000..229be2178 --- /dev/null +++ b/db/migrate/20170425100757_create_avis.rb @@ -0,0 +1,13 @@ +class CreateAvis < ActiveRecord::Migration[5.0] + def change + create_table :avis do |t| + t.string :email + t.text :introduction + t.text :answer + t.references :gestionnaire + t.references :dossier + + t.timestamps + end + end +end diff --git a/db/migrate/20170523092900_add_claimant_to_avis.rb b/db/migrate/20170523092900_add_claimant_to_avis.rb new file mode 100644 index 000000000..c8a34e182 --- /dev/null +++ b/db/migrate/20170523092900_add_claimant_to_avis.rb @@ -0,0 +1,5 @@ +class AddClaimantToAvis < ActiveRecord::Migration[5.0] + def change + add_reference :avis, :claimant, foreign_key: { to_table: :gestionnaires }, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 140dccaf6..81476287a 100644 --- a/db/schema.rb +++ b/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: 20170414095411) do +ActiveRecord::Schema.define(version: 20170523092900) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -71,6 +71,20 @@ ActiveRecord::Schema.define(version: 20170414095411) do t.index ["procedure_id"], name: "index_assign_tos_on_procedure_id", using: :btree end + create_table "avis", force: :cascade do |t| + t.string "email" + t.text "introduction" + t.text "answer" + t.integer "gestionnaire_id" + t.integer "dossier_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "claimant_id", null: false + t.index ["claimant_id"], name: "index_avis_on_claimant_id", using: :btree + t.index ["dossier_id"], name: "index_avis_on_dossier_id", using: :btree + t.index ["gestionnaire_id"], name: "index_avis_on_gestionnaire_id", using: :btree + end + create_table "cadastres", force: :cascade do |t| t.string "surface_intersection" t.float "surface_parcelle" @@ -434,6 +448,7 @@ ActiveRecord::Schema.define(version: 20170414095411) do t.index ["procedure_id"], name: "index_without_continuation_mails_on_procedure_id", using: :btree end + add_foreign_key "avis", "gestionnaires", column: "claimant_id" add_foreign_key "cerfas", "dossiers" add_foreign_key "closed_mails", "procedures" add_foreign_key "commentaires", "dossiers" diff --git a/spec/controllers/admin/mail_templates_controller_spec.rb b/spec/controllers/admin/mail_templates_controller_spec.rb index 31adfd4ee..09db84650 100644 --- a/spec/controllers/admin/mail_templates_controller_spec.rb +++ b/spec/controllers/admin/mail_templates_controller_spec.rb @@ -25,7 +25,7 @@ describe Admin::MailTemplatesController, type: :controller do before :each do patch :update, params: { procedure_id: procedure.id, - id: initiated_mail.class.slug, + id: initiated_mail.class.const_get(:SLUG), mail_template: { object: object, body: body } } end @@ -33,7 +33,7 @@ describe Admin::MailTemplatesController, type: :controller do it { expect(response).to redirect_to admin_procedure_mail_templates_path(procedure) } context 'the mail template' do - subject { procedure.reload ; procedure.initiated_mail } + subject { procedure.reload ; procedure.initiated_mail_template } it { expect(subject.object).to eq(object) } it { expect(subject.body).to eq(body) } diff --git a/spec/controllers/backoffice/avis_controller_spec.rb b/spec/controllers/backoffice/avis_controller_spec.rb new file mode 100644 index 000000000..c1af9658b --- /dev/null +++ b/spec/controllers/backoffice/avis_controller_spec.rb @@ -0,0 +1,186 @@ +require 'spec_helper' + +describe Backoffice::AvisController, type: :controller do + + describe '#POST create' do + let(:gestionnaire){ create(:gestionnaire) } + let!(:dossier){ create(:dossier, state: 'received') } + let!(:assign_to){ create(:assign_to, gestionnaire: gestionnaire, procedure: dossier.procedure )} + + subject { post :create, params: { dossier_id: dossier.id, avis: { email: gestionnaire.email, introduction: "Bonjour, regardez ce joli dossier." } } } + + context 'when gestionnaire is not authenticated' do + it { is_expected.to redirect_to new_user_session_path } + it { expect{ subject }.to_not change(Avis, :count) } + end + + context 'when gestionnaire is authenticated' do + before do + sign_in gestionnaire + end + + context 'When gestionnaire is known' do + it { is_expected.to redirect_to backoffice_dossier_path(dossier.id) } + it { expect{ subject }.to change(Avis, :count).by(1) } + it do + subject + expect(gestionnaire.avis.last).to_not eq(nil) + expect(gestionnaire.avis.last.email).to eq(nil) + expect(gestionnaire.avis.last.dossier_id).to eq(dossier.id) + end + end + end + end + + describe '#POST update' do + let(:gestionnaire){ create(:gestionnaire) } + let(:dossier){ create(:dossier, state: 'received') } + let(:avis){ create(:avis, dossier: dossier, gestionnaire: gestionnaire )} + + subject { post :update, params: { dossier_id: dossier.id, id: avis.id, avis: { answer: "Ok ce dossier est valide." } } } + + before :each do + notification = double('notification', notify: true) + allow(NotificationService).to receive(:new).and_return(notification) + end + + context 'when gestionnaire is not authenticated' do + it { is_expected.to redirect_to new_user_session_path } + it { expect(avis.answer).to be_nil } + end + + context 'when gestionnaire is authenticated' do + before do + sign_in gestionnaire + end + + context 'and is invited on dossier' do + it { is_expected.to redirect_to backoffice_dossier_path(dossier.id) } + it do + subject + expect(avis.reload.answer).to eq("Ok ce dossier est valide.") + expect(NotificationService).to have_received(:new).at_least(:once) + end + end + + context 'but is not invited on dossier' do + let(:gestionnaire2) { create(:gestionnaire) } + let(:avis){ create(:avis, dossier: dossier, gestionnaire: gestionnaire2 )} + + it { expect{ subject }.to raise_error(ActiveRecord::RecordNotFound) } + end + end + end + + describe '.sign_up' do + let(:invited_email) { 'invited@avis.com' } + let(:dossier) { create(:dossier) } + let!(:avis) { create(:avis, email: invited_email, dossier: dossier) } + let(:invitations_email) { true } + + context 'when the new gestionnaire has never signed up' do + before do + expect(Avis).to receive(:avis_exists_and_email_belongs_to_avis?) + .with(avis.id.to_s, invited_email) + .and_return(invitations_email) + get :sign_up, params: { id: avis.id, email: invited_email } + end + + context 'when the email belongs to the invitation' do + it { expect(subject.status).to eq(200) } + it { expect(assigns(:email)).to eq(invited_email) } + it { expect(assigns(:dossier)).to eq(dossier) } + end + + context 'when the email does not belong to the invitation' do + let(:invitations_email) { false } + + it { is_expected.to redirect_to root_path } + end + end + + context 'when the gestionnaire has already signed up and belongs to the invitation' do + let(:gestionnaire) { create(:gestionnaire, email: invited_email) } + let!(:avis) { create(:avis, dossier: dossier, gestionnaire: gestionnaire) } + + context 'when the gestionnaire is authenticated' do + before do + sign_in gestionnaire + get :sign_up, params: { id: avis.id, email: invited_email } + end + + it { is_expected.to redirect_to backoffice_dossier_url(avis.dossier) } + end + + context 'when the gestionnaire is not authenticated' do + before do + get :sign_up, params: { id: avis.id, email: invited_email } + end + + it { is_expected.to redirect_to new_gestionnaire_session_url } + end + end + + context 'when the gestionnaire has already signed up / is authenticated and does not belong to the invitation' do + let(:gestionnaire) { create(:gestionnaire, email: 'other@gmail.com') } + let!(:avis) { create(:avis, email: invited_email, dossier: dossier) } + + before do + sign_in gestionnaire + get :sign_up, params: { id: avis.id, email: invited_email } + end + + # redirected to dossier but then the gestionnaire gonna be banished ! + it { is_expected.to redirect_to backoffice_dossier_url(avis.dossier) } + end + end + + describe '.create_gestionnaire' do + let(:invited_email) { 'invited@avis.com' } + let(:dossier) { create(:dossier) } + let!(:avis) { create(:avis, email: invited_email, dossier: dossier) } + let(:avis_id) { avis.id } + let(:password) { '12345678' } + let(:created_gestionnaire) { Gestionnaire.find_by(email: invited_email) } + let(:invitations_email) { true } + + before do + allow(Avis).to receive(:link_avis_to_gestionnaire) + expect(Avis).to receive(:avis_exists_and_email_belongs_to_avis?) + .with(avis_id.to_s, invited_email) + .and_return(invitations_email) + + post :create_gestionnaire, params: { id: avis_id, + email: invited_email, + gestionnaire: { + password: password + } } + end + + context 'when the email does not belong to the invitation' do + let(:invitations_email) { false } + + it { is_expected.to redirect_to root_path } + end + + context 'when the email belongs to the invitation' do + context 'when the gestionnaire creation succeeds' do + it { expect(created_gestionnaire).to be_present } + it { expect(created_gestionnaire.valid_password?(password)).to be true } + + it { expect(Avis).to have_received(:link_avis_to_gestionnaire) } + + it { expect(subject.current_gestionnaire).to eq(created_gestionnaire) } + it { is_expected.to redirect_to backoffice_dossier_path(dossier) } + end + + context 'when the gestionnaire creation fails' do + let(:password) { '' } + + it { expect(created_gestionnaire).to be_nil } + it { is_expected.to redirect_to avis_sign_up_path(avis_id, invited_email) } + it { expect(flash.alert).to eq('Password : Le mot de passe est vide') } + end + end + end +end diff --git a/spec/controllers/backoffice/dossiers_controller_spec.rb b/spec/controllers/backoffice/dossiers_controller_spec.rb index 7ba92db57..a5889ec9f 100644 --- a/spec/controllers/backoffice/dossiers_controller_spec.rb +++ b/spec/controllers/backoffice/dossiers_controller_spec.rb @@ -97,7 +97,7 @@ describe Backoffice::DossiersController, type: :controller do 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).to receive(:where).with(dossier_id: dossier_id.to_s).and_return(Notification::ActiveRecord_Relation) expect(Notification::ActiveRecord_Relation).to receive(:update_all).with(already_read: true).and_return(true) subject @@ -109,6 +109,32 @@ describe Backoffice::DossiersController, type: :controller do it { expect(subject).to redirect_to('/backoffice') } end + + describe 'he can invite somebody for avis' do + render_views + + it { expect(subject.body).to include("Invitez une personne externe à consulter le dossier et à vous donner un avis sur celui ci.") } + end + + context 'and is invited on a dossier' do + let(:dossier_invited){ create(:dossier, procedure: create(:procedure)) } + let!(:avis){ create(:avis, dossier: dossier_invited, gestionnaire: gestionnaire) } + + subject { get :show, params: { id: dossier_invited.id } } + + render_views + + it { expect(subject.status).to eq(200) } + it { expect(subject.body).to include("Votre avis est sollicité sur le dossier") } + it { expect(subject.body).to_not include("Invitez une personne externe à consulter le dossier et à vous donner un avis sur celui ci.") } + + describe 'the notifications are not marked as read' do + it do + expect(Notification).not_to receive(:where) + subject + end + end + end end context 'gestionnaire does not connected but dossier id is correct' do diff --git a/spec/controllers/backoffice_controller_spec.rb b/spec/controllers/backoffice_controller_spec.rb index 00db207ae..540f8db82 100644 --- a/spec/controllers/backoffice_controller_spec.rb +++ b/spec/controllers/backoffice_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe BackofficeController, type: :controller do describe 'GET #index' do - context 'when gestionnaire is not connected'do + context 'when gestionnaire is not connected' do before do get :index end @@ -10,7 +10,7 @@ describe BackofficeController, type: :controller do it { expect(response).to redirect_to :new_gestionnaire_session } end - context 'when gestionnaire is connected'do + context 'when gestionnaire is connected' do before do sign_in create(:gestionnaire) get :index @@ -19,4 +19,45 @@ describe BackofficeController, type: :controller do it { expect(response).to redirect_to :backoffice_dossiers } end end + + describe 'GET #invitations' do + context 'when gestionnaire is not invited on any dossiers' do + render_views + + before do + sign_in create(:gestionnaire) + get :invitations + end + + it { expect(response.status).to eq(200) } + it { expect(response.body).to include("INVITATIONS") } + it { expect(response.body).to include("0 avis à rendre") } + it { expect(response.body).to include("0 avis rendus") } + end + + context 'when gestionnaire is invited on a dossier' do + let(:dossier){ create(:dossier) } + let(:gestionnaire){ create(:gestionnaire) } + let!(:avis){ create(:avis, dossier: dossier, gestionnaire: gestionnaire) } + render_views + + before do + sign_in gestionnaire + get :invitations + end + + it { expect(response.status).to eq(200) } + it { expect(response.body).to include("1 avis à rendre") } + it { expect(response.body).to include("0 avis rendus") } + it { expect(response.body).to include(dossier.procedure.libelle) } + + context 'when avis is already sent' do + let!(:avis){ create(:avis, dossier: dossier, gestionnaire: gestionnaire, answer: "Voici mon avis.") } + + it { expect(response.body).to include("0 avis à rendre") } + it { expect(response.body).to include("1 avis rendu") } + it { expect(response.body).to include(dossier.procedure.libelle) } + end + end + end end diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb index 474f03dfd..9b0b7ed56 100644 --- a/spec/controllers/root_controller_spec.rb +++ b/spec/controllers/root_controller_spec.rb @@ -45,6 +45,14 @@ describe RootController, type: :controller do it { expect(subject).to redirect_to(admin_procedures_path) } end + context 'when Administration is connected' do + before do + sign_in create(:administration) + end + + it { expect(subject).to redirect_to(administrations_path) } + end + context 'when nobody is connected' do render_views diff --git a/spec/controllers/stats_controller_spec.rb b/spec/controllers/stats_controller_spec.rb index 6cbaf91bd..d574fde07 100644 --- a/spec/controllers/stats_controller_spec.rb +++ b/spec/controllers/stats_controller_spec.rb @@ -1,51 +1,41 @@ require 'spec_helper' describe StatsController, type: :controller do - describe '#thirty_days_flow_hash' do - context "without a date_attribut" do + describe "#last_four_months_hash" do + context "without a date attribute" do before do + FactoryGirl.create(:procedure, :created_at => 6.months.ago) FactoryGirl.create(:procedure, :created_at => 45.days.ago) - FactoryGirl.create(:procedure, :created_at => 15.days.ago) - FactoryGirl.create(:procedure, :created_at => 1.day.ago) - - @expected_hash = {} - (30.days.ago.to_date..Time.now.to_date).each do |day| - if [15.days.ago.to_date, 1.day.ago.to_date].include?(day) - @expected_hash[day] = 1 - else - @expected_hash[day] = 0 - end - end + FactoryGirl.create(:procedure, :created_at => 1.days.ago) + FactoryGirl.create(:procedure, :created_at => 1.days.ago) end let (:association) { Procedure.all } - subject { StatsController.new.send(:thirty_days_flow_hash, association) } + subject { StatsController.new.send(:last_four_months_hash, association) } - it { expect(subject).to eq(@expected_hash) } + it { expect(subject).to eq([ + [I18n.l(45.days.ago.beginning_of_month, format: "%B %Y"), 1], + [I18n.l(1.days.ago.beginning_of_month, format: "%B %Y"), 2] + ] ) } end - context "with a date_attribut" do + context "with a date attribute" do before do - FactoryGirl.create(:procedure, :created_at => 45.days.ago, :updated_at => 50.days.ago) - FactoryGirl.create(:procedure, :created_at => 15.days.ago, :updated_at => 10.days.ago) - FactoryGirl.create(:procedure, :created_at => 1.day.ago, :updated_at => 3.days.ago) - - @expected_hash = {} - (30.days.ago.to_date..Time.now.to_date).each do |day| - if [10.days.ago.to_date, 3.day.ago.to_date].include?(day) - @expected_hash[day] = 1 - else - @expected_hash[day] = 0 - end - end + FactoryGirl.create(:procedure, :created_at => 6.months.ago, :updated_at => 6.months.ago) + FactoryGirl.create(:procedure, :created_at => 2.months.ago, :updated_at => 45.days.ago) + FactoryGirl.create(:procedure, :created_at => 2.months.ago, :updated_at => 45.days.ago) + FactoryGirl.create(:procedure, :created_at => 2.months.ago, :updated_at => 1.days.ago) end let (:association) { Procedure.all } - subject { StatsController.new.send(:thirty_days_flow_hash, association, :updated_at) } + subject { StatsController.new.send(:last_four_months_hash, association, :updated_at) } - it { expect(subject).to eq(@expected_hash) } + it { expect(subject).to eq([ + [I18n.l(45.days.ago.beginning_of_month, format: "%B %Y"), 2], + [I18n.l(1.days.ago.beginning_of_month, format: "%B %Y"), 1] + ] ) } end end @@ -66,22 +56,211 @@ describe StatsController, type: :controller do 15.days.ago.beginning_of_month => 3 }) } end + + context "with a date attribute" do + before do + FactoryGirl.create(:procedure, :created_at => 45.days.ago, :updated_at => 20.days.ago) + FactoryGirl.create(:procedure, :created_at => 15.days.ago, :updated_at => 20.days.ago) + FactoryGirl.create(:procedure, :created_at => 15.days.ago, :updated_at => 10.days.ago) + end + + let (:association) { Procedure.all } + + subject { StatsController.new.send(:cumulative_hash, association, :updated_at) } + + it { expect(subject).to eq({ + 20.days.ago.beginning_of_month => 2, + 10.days.ago.beginning_of_month => 3 + }) } + end end - context "with a date attribute" do + describe "#procedures_count_per_administrateur" do + let!(:administrateur_1) { create(:administrateur) } + let!(:administrateur_2) { create(:administrateur) } + let!(:administrateur_3) { create(:administrateur) } + let!(:administrateur_4) { create(:administrateur) } + let!(:administrateur_5) { create(:administrateur) } + before do - FactoryGirl.create(:procedure, :created_at => 45.days.ago, :updated_at => 20.days.ago) - FactoryGirl.create(:procedure, :created_at => 15.days.ago, :updated_at => 20.days.ago) - FactoryGirl.create(:procedure, :created_at => 15.days.ago, :updated_at => 10.days.ago) + 3.times do + create(:procedure, published: true, administrateur: administrateur_1) + end + + 2.times do + create(:procedure, published: true, administrateur: administrateur_2) + end + + 8.times do + create(:procedure, published: true, administrateur: administrateur_3) + end + + create(:procedure, published: true, administrateur: administrateur_4) end - let (:association) { Procedure.all } + let(:association){ Procedure.all } - subject { StatsController.new.send(:cumulative_hash, association, :updated_at) } + subject { StatsController.new.send(:procedures_count_per_administrateur, association) } - it { expect(subject).to eq({ - 20.days.ago.beginning_of_month => 2, - 10.days.ago.beginning_of_month => 3 - }) } + it do + is_expected.to eq({ + 'Une procédure' => 1, + 'Entre deux et cinq procédures' => 2, + 'Plus de cinq procédures' => 1 + }) + end + end + + describe "#dossier_instruction_mean_time" do + # Month-2: mean 3 days + # procedure_1: mean 2 days + # dossier_p1_a: 3 days + # dossier_p1_b: 1 days + # procedure_2: mean 4 days + # dossier_p2_a: 4 days + # + # Month-1: mean 5 days + # procedure_1: mean 5 days + # dossier_p1_c: 5 days + + before do + procedure_1 = FactoryGirl.create(:procedure) + procedure_2 = FactoryGirl.create(:procedure) + dossier_p1_a = FactoryGirl.create(:dossier, + :procedure => procedure_1, + :initiated_at => 2.months.ago.beginning_of_month, + :processed_at => 2.months.ago.beginning_of_month + 3.days) + dossier_p1_b = FactoryGirl.create(:dossier, + :procedure => procedure_1, + :initiated_at => 2.months.ago.beginning_of_month, + :processed_at => 2.months.ago.beginning_of_month + 1.days) + dossier_p1_c = FactoryGirl.create(:dossier, + :procedure => procedure_1, + :initiated_at => 1.months.ago.beginning_of_month, + :processed_at => 1.months.ago.beginning_of_month + 5.days) + dossier_p2_a = FactoryGirl.create(:dossier, + :procedure => procedure_2, + :initiated_at => 2.month.ago.beginning_of_month, + :processed_at => 2.month.ago.beginning_of_month + 4.days) + + # Write directly in the DB to avoid the before_validation hook + Dossier.update_all(state: "closed") + + @expected_hash = { + "#{2.months.ago.beginning_of_month}" => 3.0, + "#{1.months.ago.beginning_of_month}" => 5.0 + } + end + + let (:association) { Dossier.where.not(:state => :draft) } + + subject { StatsController.new.send(:dossier_instruction_mean_time, association) } + + it { expect(subject).to eq(@expected_hash) } + end + + describe "#dossier_filling_mean_time" do + # Month-2: mean 30 minutes + # procedure_1: mean 20 minutes + # dossier_p1_a: 30 minutes + # dossier_p1_b: 10 minutes + # procedure_2: mean 40 minutes + # dossier_p2_a: 80 minutes, for twice the fields + # + # Month-1: mean 50 minutes + # procedure_1: mean 50 minutes + # dossier_p1_c: 50 minutes + + before do + procedure_1 = FactoryGirl.create(:procedure, :with_type_de_champ, :types_de_champ_count => 24) + procedure_2 = FactoryGirl.create(:procedure, :with_type_de_champ, :types_de_champ_count => 48) + dossier_p1_a = FactoryGirl.create(:dossier, + :procedure => procedure_1, + :created_at => 2.months.ago.beginning_of_month, + :initiated_at => 2.months.ago.beginning_of_month + 30.minutes, + :processed_at => 2.months.ago.beginning_of_month + 1.day) + dossier_p1_b = FactoryGirl.create(:dossier, + :procedure => procedure_1, + :created_at => 2.months.ago.beginning_of_month, + :initiated_at => 2.months.ago.beginning_of_month + 10.minutes, + :processed_at => 2.months.ago.beginning_of_month + 1.day) + dossier_p1_c = FactoryGirl.create(:dossier, + :procedure => procedure_1, + :created_at => 1.months.ago.beginning_of_month, + :initiated_at => 1.months.ago.beginning_of_month + 50.minutes, + :processed_at => 1.months.ago.beginning_of_month + 1.day) + dossier_p2_a = FactoryGirl.create(:dossier, + :procedure => procedure_2, + :created_at => 2.month.ago.beginning_of_month, + :initiated_at => 2.month.ago.beginning_of_month + 80.minutes, + :processed_at => 2.month.ago.beginning_of_month + 1.day) + + # Write directly in the DB to avoid the before_validation hook + Dossier.update_all(state: "closed") + + @expected_hash = { + "#{2.months.ago.beginning_of_month}" => 30.0, + "#{1.months.ago.beginning_of_month}" => 50.0 + } + end + + let (:association) { Dossier.where.not(:state => :draft) } + + subject { StatsController.new.send(:dossier_filling_mean_time, association) } + + it { expect(subject).to eq(@expected_hash) } + end + + describe '#avis_usage' do + let!(:dossier) { create(:dossier) } + let!(:avis_with_dossier) { create(:avis) } + let!(:dossier2) { create(:dossier) } + + before do + Timecop.freeze(Time.now) + end + + subject { StatsController.new.send(:avis_usage) } + + it { expect(subject).to match([[3.week.ago.to_i, 0], [2.week.ago.to_i, 0], [1.week.ago.to_i, 33.33]]) } + end + + describe "#avis_average_answer_time" do + before do + # 1 week ago + create(:avis, answer: "voila ma réponse", created_at: 1.week.ago + 1.day, updated_at: 1.week.ago + 2.days) # 1 day + create(:avis, created_at: 1.week.ago + 2.days) + + # 2 weeks ago + create(:avis, answer: "voila ma réponse", created_at: 2.week.ago + 1.day, updated_at: 2.week.ago + 2.days) # 1 day + create(:avis, answer: "voila ma réponse2", created_at: 2.week.ago + 3.days, updated_at: 1.week.ago + 6.days) # 10 days + create(:avis, answer: "voila ma réponse2", created_at: 2.week.ago + 2.days, updated_at: 1.week.ago + 6.days) # 11 days + create(:avis, created_at: 2.week.ago + 1.day, updated_at: 2.week.ago + 2.days) + + # 3 weeks ago + create(:avis, answer: "voila ma réponse2", created_at: 3.weeks.ago + 1.day, updated_at: 3.weeks.ago + 2.days) # 1 day + create(:avis, answer: "voila ma réponse2", created_at: 3.weeks.ago + 1.day, updated_at: 1.week.ago + 5.days) # 18 day + end + + subject { StatsController.new.send(:avis_average_answer_time) } + + it { expect(subject.count).to eq(3) } + it { is_expected.to include [1.week.ago.to_i, 1.0] } + it { is_expected.to include [2.week.ago.to_i, 7.33] } + it { is_expected.to include [3.week.ago.to_i, 9.5] } + end + + describe '#avis_answer_percentages' do + let!(:avis) { create(:avis, created_at: 2.days.ago) } + let!(:avis2) { create(:avis, answer: 'answer', created_at: 2.days.ago) } + let!(:avis3) { create(:avis, answer: 'answer', created_at: 2.days.ago) } + + subject { StatsController.new.send(:avis_answer_percentages) } + + before do + Timecop.freeze(Time.now) + end + + it { is_expected.to match [[3.week.ago.to_i, 0], [2.week.ago.to_i, 0], [1.week.ago.to_i, 66.67]] } end end diff --git a/spec/decorators/champ_decorator_spec.rb b/spec/decorators/champ_decorator_spec.rb index c8b15056c..a5a2554a4 100644 --- a/spec/decorators/champ_decorator_spec.rb +++ b/spec/decorators/champ_decorator_spec.rb @@ -34,5 +34,19 @@ describe ChampDecorator do it { is_expected.to eq '' } end end + + describe "for a date" do + let(:type_champ) { :date } + + context "when value is an ISO date" do + before { champ.update value: "2017-12-31" } + it { is_expected.to eq "31/12/2017" } + end + + context "when value is empty" do + before { champ.update value: nil } + it { is_expected.to eq nil } + end + end end end diff --git a/spec/factories/administrateur.rb b/spec/factories/administrateur.rb index 05cfc88a2..f81e5a6a9 100644 --- a/spec/factories/administrateur.rb +++ b/spec/factories/administrateur.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - sequence(:administrateur_email) { |n| "plop#{n}@plop.com" } + sequence(:administrateur_email) { |n| "admin#{n}@admin.com" } factory :administrateur do email { generate(:administrateur_email) } password 'password' diff --git a/spec/factories/avis.rb b/spec/factories/avis.rb new file mode 100644 index 000000000..7568cdfe9 --- /dev/null +++ b/spec/factories/avis.rb @@ -0,0 +1,23 @@ +FactoryGirl.define do + factory :avis do + introduction 'Bonjour, merci de me donner votre avis sur ce dossier' + + before(:create) do |avis, _evaluator| + unless avis.gestionnaire + avis.gestionnaire = create :gestionnaire + end + end + + before(:create) do |avis, _evaluator| + unless avis.dossier + avis.dossier = create :dossier + end + end + + before(:create) do |avis, _evaluator| + unless avis.claimant + avis.claimant = create :gestionnaire + end + end + end +end diff --git a/spec/factories/champ.rb b/spec/factories/champ.rb index 96620cb80..3a2302f4c 100644 --- a/spec/factories/champ.rb +++ b/spec/factories/champ.rb @@ -1,4 +1,6 @@ FactoryGirl.define do factory :champ do + + type_de_champ { FactoryGirl.create(:type_de_champ_public) } end end diff --git a/spec/factories/gestionnaire.rb b/spec/factories/gestionnaire.rb index 88043eecf..02ee8d5eb 100644 --- a/spec/factories/gestionnaire.rb +++ b/spec/factories/gestionnaire.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - sequence(:gestionnaire_email) { |n| "gest#{n}@plop.com" } + sequence(:gestionnaire_email) { |n| "gest#{n}@gest.com" } factory :gestionnaire do email { generate(:gestionnaire_email) } password 'password' diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb index f38629fc7..9e0b76e7b 100644 --- a/spec/factories/procedure.rb +++ b/spec/factories/procedure.rb @@ -30,10 +30,16 @@ FactoryGirl.define do end trait :with_type_de_champ do - after(:build) do |procedure, _evaluator| - type_de_champ = create(:type_de_champ_public) + transient do + types_de_champ_count 1 + end - procedure.types_de_champ << type_de_champ + after(:build) do |procedure, evaluator| + evaluator.types_de_champ_count.times do + type_de_champ = create(:type_de_champ_public) + + procedure.types_de_champ << type_de_champ + end end end diff --git a/spec/factories/user.rb b/spec/factories/user.rb index 184da4b0a..c2d09396b 100644 --- a/spec/factories/user.rb +++ b/spec/factories/user.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - sequence(:user_email) { |n| "plop#{n}@plop.com" } + sequence(:user_email) { |n| "user#{n}@user.com" } factory :user do email { generate(:user_email) } password 'password' diff --git a/spec/mailers/avis_mailer_spec.rb b/spec/mailers/avis_mailer_spec.rb new file mode 100644 index 000000000..83673e899 --- /dev/null +++ b/spec/mailers/avis_mailer_spec.rb @@ -0,0 +1,14 @@ +require "rails_helper" + +RSpec.describe AvisMailer, type: :mailer do + describe '.avis_invitation' do + let(:avis) { create(:avis) } + + subject { described_class.avis_invitation(avis) } + + it { expect(subject.subject).to eq("Donnez votre avis sur le dossier nº #{avis.dossier.id} (#{avis.dossier.procedure.libelle})") } + it { expect(subject.body).to include("Vous avez été invité par #{avis.claimant.email} à donner votre avis sur le dossier nº #{avis.dossier.id} de la procédure : #{avis.dossier.procedure.libelle}.") } + it { expect(subject.body).to include(avis.introduction) } + + end +end diff --git a/spec/mailers/previews/avis_mailer_preview.rb b/spec/mailers/previews/avis_mailer_preview.rb new file mode 100644 index 000000000..55d153cd1 --- /dev/null +++ b/spec/mailers/previews/avis_mailer_preview.rb @@ -0,0 +1,8 @@ +# Preview all emails at http://localhost:3000/rails/mailers/avis_mailer +class AvisMailerPreview < ActionMailer::Preview + + def avis_invitation + AvisMailer.avis_invitation(Avis.last) + end + +end diff --git a/spec/mailers/previews/gestionnaire_mailer_preview.rb b/spec/mailers/previews/gestionnaire_mailer_preview.rb new file mode 100644 index 000000000..2e62b4f5c --- /dev/null +++ b/spec/mailers/previews/gestionnaire_mailer_preview.rb @@ -0,0 +1,6 @@ +class GestionnaireMailerPreview < ActionMailer::Preview + def last_week_overview + gestionnaire = Gestionnaire.first + GestionnaireMailer.last_week_overview(gestionnaire, gestionnaire.last_week_overview) + end +end diff --git a/spec/mailers/previews/notification_mailer_preview.rb b/spec/mailers/previews/notification_mailer_preview.rb index 25fa735c2..3cb6079a2 100644 --- a/spec/mailers/previews/notification_mailer_preview.rb +++ b/spec/mailers/previews/notification_mailer_preview.rb @@ -1,7 +1,7 @@ class NotificationMailerPreview < ActionMailer::Preview def send_notification - NotificationMailer.send_notification(Dossier.last, Dossier.last.procedure.initiated_mail) + NotificationMailer.send_notification(Dossier.last, Dossier.last.procedure.initiated_mail_template) end end diff --git a/spec/models/avis_spec.rb b/spec/models/avis_spec.rb new file mode 100644 index 000000000..231210158 --- /dev/null +++ b/spec/models/avis_spec.rb @@ -0,0 +1,89 @@ +require 'rails_helper' + +RSpec.describe Avis, type: :model do + let(:claimant) { create(:gestionnaire) } + + describe '.email_to_display' do + let(:invited_email) { 'invited@avis.com' } + let!(:avis) do + avis = create(:avis, email: invited_email, dossier: create(:dossier)) + avis.gestionnaire = nil + avis + end + + subject { avis.email_to_display } + + context 'when gestionnaire is not known' do + it{ is_expected.to eq(invited_email) } + end + + context 'when gestionnaire is known' do + let!(:avis) { create(:avis, email: nil, gestionnaire: create(:gestionnaire), dossier: create(:dossier)) } + + it{ is_expected.to eq(avis.gestionnaire.email) } + end + end + + describe '.by_latest' do + context 'with 3 avis' do + let!(:avis){ create(:avis) } + let!(:avis2){ create(:avis, updated_at: 4.hours.ago) } + let!(:avis3){ create(:avis, updated_at: 3.hours.ago) } + + subject { Avis.by_latest } + + it { expect(subject).to eq([avis, avis3, avis2])} + end + end + + describe ".link_avis_to_gestionnaire" do + let(:gestionnaire){ create(:gestionnaire) } + + subject{ Avis.link_avis_to_gestionnaire(gestionnaire) } + + context 'when there are 2 avis linked by email to a gestionnaire' do + let!(:avis){ create(:avis, email: gestionnaire.email, gestionnaire: nil) } + let!(:avis2){ create(:avis, email: gestionnaire.email, gestionnaire: nil) } + + before do + subject + avis.reload + avis2.reload + end + + it { expect(avis.email).to be_nil } + it { expect(avis.gestionnaire).to eq(gestionnaire) } + it { expect(avis2.email).to be_nil } + it { expect(avis2.gestionnaire).to eq(gestionnaire) } + end + end + + describe '.avis_exists_and_email_belongs_to_avis' do + let(:dossier) { create(:dossier) } + let(:invited_email) { 'invited@avis.com' } + let!(:avis) { create(:avis, email: invited_email, dossier: dossier) } + + subject { Avis.avis_exists_and_email_belongs_to_avis?(avis_id, email) } + + context 'when the avis is unknown' do + let(:avis_id) { 666 } + let(:email) { 'unknown@mystery.com' } + + it { is_expected.to be false } + end + + context 'when the avis is known' do + let(:avis_id) { avis.id } + + context 'when the email belongs to the invitation' do + let(:email) { invited_email } + it { is_expected.to be true } + end + + context 'when the email is unknown' do + let(:email) { 'unknown@mystery.com' } + it { is_expected.to be false } + end + end + end +end diff --git a/spec/models/champ_shared_example.rb b/spec/models/champ_shared_example.rb index b404154df..7dfe6bb1b 100644 --- a/spec/models/champ_shared_example.rb +++ b/spec/models/champ_shared_example.rb @@ -68,4 +68,30 @@ shared_examples 'champ_spec' do it { expect(subject).to include '99 - Étranger' } end + + context "when type_champ=date" do + let(:type_de_champ) { create(:type_de_champ_public, type_champ: "date")} + let(:champ) { create(:champ, type_de_champ: type_de_champ) } + + it "should convert %d/%m/%Y format to ISO" do + champ.value = "31/12/2017" + champ.save + champ.reload + expect(champ.value).to eq("2017-12-31") + end + + it "should convert to nil if date parse failed" do + champ.value = "bla" + champ.save + champ.reload + expect(champ.value).to be(nil) + end + + it "should convert empty string to nil" do + champ.value = "" + champ.save + champ.reload + expect(champ.value).to be(nil) + end + end end diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index e964e4b0d..ac668ee64 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -409,59 +409,6 @@ describe Dossier do end end end - - describe 'gestionnaire backoffice methods' do - let(:admin) { create(:administrateur) } - let(:admin_2) { create(:administrateur) } - - let(:gestionnaire) { create(:gestionnaire, administrateurs: [admin]) } - let(:procedure_admin) { create(:procedure, administrateur: admin) } - let(:procedure_admin_2) { create(:procedure, administrateur: admin_2) } - - let!(:dossier) { create(:dossier, procedure: procedure_admin, state: 'draft') } - let!(:dossier2) { create(:dossier, procedure: procedure_admin, state: 'initiated') } #nouveaux - let!(:dossier3) { create(:dossier, procedure: procedure_admin, state: 'initiated') } #nouveaux - let!(:dossier4) { create(:dossier, procedure: procedure_admin, state: 'replied') } #en_attente - let!(:dossier5) { create(:dossier, procedure: procedure_admin, state: 'updated') } #a_traiter - let!(:dossier6) { create(:dossier, procedure: procedure_admin, state: 'received') } #a_instruire - let!(:dossier7) { create(:dossier, procedure: procedure_admin, state: 'received') } #a_instruire - let!(:dossier8) { create(:dossier, procedure: procedure_admin, state: 'closed') } #termine - let!(:dossier9) { create(:dossier, procedure: procedure_admin, state: 'refused') } #termine - let!(:dossier10) { create(:dossier, procedure: procedure_admin, state: 'without_continuation') } #termine - let!(:dossier11) { create(:dossier, procedure: procedure_admin_2, state: 'closed') } #termine - let!(:dossier12) { create(:dossier, procedure: procedure_admin, state: 'initiated', archived: true) } #a_traiter #archived - let!(:dossier13) { create(:dossier, procedure: procedure_admin, state: 'replied', archived: true) } #en_attente #archived - let!(:dossier14) { create(:dossier, procedure: procedure_admin, state: 'closed', archived: true) } #termine #archived - - before do - create :assign_to, gestionnaire: gestionnaire, procedure: procedure_admin - end - - describe '#nouveaux' do - subject { gestionnaire.dossiers.nouveaux } - - it { expect(subject.size).to eq(2) } - end - - describe '#waiting_for_gestionnaire' do - subject { gestionnaire.dossiers.waiting_for_gestionnaire } - - it { expect(subject.size).to eq(1) } - end - - describe '#waiting_for_user' do - subject { gestionnaire.dossiers.waiting_for_user } - - it { expect(subject.size).to eq(1) } - end - - describe '#en_instruction' do - subject { gestionnaire.dossiers.en_instruction } - - it { expect(subject.size).to eq(2) } - it { expect(subject).to include(dossier6, dossier7) } - end - end end describe '#cerfa_available?' do @@ -911,11 +858,36 @@ describe Dossier do let!(:dossier) { create(:dossier, :with_entreprise, procedure: procedure, state: :draft) } let!(:dossier2) { create(:dossier, :with_entreprise, procedure: procedure, state: :initiated) } let!(:dossier3) { create(:dossier, :with_entreprise, procedure: procedure, state: :received) } + let!(:dossier4) { create(:dossier, :with_entreprise, procedure: procedure, state: :received, archived: true) } subject { procedure.dossiers.downloadable } it { is_expected.not_to include(dossier)} it { is_expected.to include(dossier2)} it { is_expected.to include(dossier3)} + it { is_expected.to include(dossier4)} + end + + describe "#send_notification_email" do + let(:procedure) { create(:procedure) } + let(:dossier) { create(:dossier, procedure: procedure, state: :initiated) } + + before do + ActionMailer::Base.deliveries.clear + end + + it "sends an email when the dossier becomes received" do + dossier.received! + + mail = ActionMailer::Base.deliveries.last + + expect(mail.subject).to eq("Votre dossier TPS nº #{dossier.id} va être instruit") + end + + it "does not an email when the dossier becomes closed" do + dossier.closed! + + expect(ActionMailer::Base.deliveries.size).to eq(0) + end end end diff --git a/spec/models/gestionnaire_spec.rb b/spec/models/gestionnaire_spec.rb index 8525b2e86..fbfbfd76f 100644 --- a/spec/models/gestionnaire_spec.rb +++ b/spec/models/gestionnaire_spec.rb @@ -347,4 +347,75 @@ describe Gestionnaire, type: :model do end end end + + describe 'last_week_overview' do + let!(:gestionnaire2) { create(:gestionnaire) } + subject { gestionnaire2.last_week_overview } + let(:friday) { DateTime.new(2017, 5, 12) } + let(:monday) { DateTime.now.beginning_of_week } + + before :each do + Timecop.freeze(friday) + end + + context 'when no procedure published was active last week' do + let!(:procedure) { create(:procedure, gestionnaires: [gestionnaire2], libelle: 'procedure', published: true) } + context 'when the gestionnaire has no notifications' do + it { is_expected.to eq(nil) } + end + + context 'when the gestionnaire has one notification' do + before :each do + expect(gestionnaire2).to receive(:notifications).twice.and_return([1]) + end + + it { is_expected.to eq({ start_date: monday, procedure_overviews: [], notifications: [1] }) } + end + end + + context 'when a procedure published was active' do + let!(:procedure) { create(:procedure, gestionnaires: [gestionnaire2], libelle: 'procedure', published: true) } + let(:procedure_overview) { double('procedure_overview', 'had_some_activities?'.to_sym => true) } + + before :each do + expect_any_instance_of(Procedure).to receive(:procedure_overview).and_return(procedure_overview) + end + + it { expect(gestionnaire.last_week_overview[:procedure_overviews]).to match([procedure_overview]) } + end + + context 'when a procedure not published was active with no notifications' do + let!(:procedure) { create(:procedure, gestionnaires: [gestionnaire2], libelle: 'procedure', published: false) } + let(:procedure_overview) { double('procedure_overview', 'had_some_activities?'.to_sym => true) } + + before :each do + allow_any_instance_of(Procedure).to receive(:procedure_overview).and_return(procedure_overview) + end + + it { is_expected.to eq(nil) } + end + end + + describe '.can_view_dossier?' do + subject{ gestionnaire.can_view_dossier?(dossier.id) } + + context 'when gestionnaire is assigned on dossier' do + let!(:dossier){ create(:dossier, procedure: procedure, state: 'received') } + + it { expect(subject).to be true } + end + + context 'when gestionnaire is invited on dossier' do + let(:dossier){ create(:dossier) } + let!(:avis){ create(:avis, dossier: dossier, gestionnaire: gestionnaire) } + + it { expect(subject).to be true } + end + + context 'when gestionnaire is neither assigned nor invited on dossier' do + let(:dossier){ create(:dossier) } + + it { expect(subject).to be false } + end + end end diff --git a/spec/models/procedure_overview_spec.rb b/spec/models/procedure_overview_spec.rb new file mode 100644 index 000000000..defe51345 --- /dev/null +++ b/spec/models/procedure_overview_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe ProcedureOverview, type: :model do + let(:procedure) { create(:procedure, libelle: 'libelle') } + let(:friday) { DateTime.new(2017, 5, 12) } # vendredi 12 mai 2017, de la semaine du 8 mai + let(:monday) { DateTime.new(2017, 5, 8) } + + before :each do + Timecop.freeze(friday) + end + + let(:procedure_overview) { ProcedureOverview.new(procedure, monday, 0) } + + describe 'received_dossiers_count' do + let!(:received_dossier) do + dossier = create(:dossier, procedure: procedure, state: :received, created_at: monday) + end + + it { expect(procedure_overview.received_dossiers_count).to eq(1) } + end + + describe 'created_dossiers_count' do + let!(:created_dossier_during_the_week) do + create(:dossier, procedure: procedure, created_at: monday, state: :received) + end + + let!(:created_dossier_during_the_week_but_in_draft) do + create(:dossier, procedure: procedure, created_at: monday, state: :draft) + end + + let!(:created_dossier_before_the_week) do + create(:dossier, procedure: procedure, created_at: (monday - 1.week), state: :received) + end + + it { expect(procedure_overview.created_dossiers_count).to eq(1) } + end + + describe 'processed_dossiers_count' do + let!(:processed_dossier_during_the_week) do + create(:dossier, procedure: procedure, created_at: monday, processed_at: monday) + end + + let!(:processed_dossier_before_the_week) do + create(:dossier, procedure: procedure, created_at: (monday - 1.week), processed_at: (monday - 1.week)) + end + + it { expect(procedure_overview.processed_dossiers_count).to eq(1) } + end + + describe 'to_html' do + subject { procedure_overview.to_html } + + context 'when the different count are equal to 0' do + it { is_expected.to match(/^libelle<\/strong><\/a>$/) } + end + + context 'when the different counts are equal to 1' do + before :each do + procedure_overview.notifications_count = 1 + procedure_overview.received_dossiers_count = 1 + procedure_overview.created_dossiers_count = 1 + procedure_overview.processed_dossiers_count = 1 + end + + it { is_expected.to match(/^libelle<\/strong><\/a>/) } + it { is_expected.to include("1 dossier est en cours d'instruction") } + it { is_expected.to include('1 nouveau dossier a été déposé') } + it { is_expected.to include('1 dossier a été instruit') } + it { is_expected.to include('1 notification en attente sur les dossiers que vous suivez') } + end + + context 'when the different counts are equal to 2' do + before :each do + procedure_overview.notifications_count = 2 + procedure_overview.received_dossiers_count = 3 + procedure_overview.created_dossiers_count = 4 + procedure_overview.processed_dossiers_count = 5 + end + + it { is_expected.to match(/^libelle<\/strong><\/a>/) } + it { is_expected.to include("3 dossiers sont en cours d'instruction") } + it { is_expected.to include('4 nouveaux dossiers ont été déposés') } + it { is_expected.to include('5 dossiers ont été instruits') } + it { is_expected.to include('2 notifications en attente sur les dossiers que vous suivez') } + end + end +end diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index 7a5717d99..a9e3f2185 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -1,20 +1,21 @@ require 'spec_helper' describe Procedure do + describe 'mail templates' do + subject { create(:procedure) } - describe 'mails' do - it { expect(subject.initiated_mail).to be_a(Mails::InitiatedMail) } - it { expect(subject.received_mail).to be_a(Mails::ReceivedMail) } - it { expect(subject.closed_mail).to be_a(Mails::ClosedMail) } - it { expect(subject.refused_mail).to be_a(Mails::RefusedMail) } - it { expect(subject.without_continuation_mail).to be_a(Mails::WithoutContinuationMail) } + it { expect(subject.initiated_mail_template).to be_a(Mails::InitiatedMail) } + it { expect(subject.received_mail_template).to be_a(Mails::ReceivedMail) } + it { expect(subject.closed_mail_template).to be_a(Mails::ClosedMail) } + it { expect(subject.refused_mail_template).to be_a(Mails::RefusedMail) } + it { expect(subject.without_continuation_mail_template).to be_a(Mails::WithoutContinuationMail) } end describe 'initiated_mail' do subject { create(:procedure) } context 'when initiated_mail is not customize' do - it { expect(subject.initiated_mail.body).to eq(Mails::InitiatedMail.default.body) } + it { expect(subject.initiated_mail_template.body).to eq(Mails::InitiatedMail.default.body) } end context 'when initiated_mail is customize' do @@ -23,7 +24,7 @@ describe Procedure do subject.save subject.reload end - it { expect(subject.initiated_mail.body).to eq('sisi') } + it { expect(subject.initiated_mail_template.body).to eq('sisi') } end context 'when initiated_mail is customize ... again' do @@ -32,7 +33,7 @@ describe Procedure do subject.save subject.reload end - it { expect(subject.initiated_mail.body).to eq('toto') } + it { expect(subject.initiated_mail_template.body).to eq('toto') } it { expect(Mails::InitiatedMail.count).to eq(1) } end @@ -186,7 +187,7 @@ describe Procedure do end it 'should not duplicate default mail_template' do - expect(subject.initiated_mail.attributes).to eq Mails::InitiatedMail.default.attributes + expect(subject.initiated_mail_template.attributes).to eq Mails::InitiatedMail.default.attributes end it 'should not duplicate specific related objects' do diff --git a/spec/views/admin/gestionnaires/index.html.haml_spec.rb b/spec/views/admin/gestionnaires/index.html.haml_spec.rb index 0ec5c815b..7f8820abd 100644 --- a/spec/views/admin/gestionnaires/index.html.haml_spec.rb +++ b/spec/views/admin/gestionnaires/index.html.haml_spec.rb @@ -29,6 +29,6 @@ describe 'admin/gestionnaires/index.html.haml', type: :view do array: true)) render end - it { expect(rendered).to match(/gest\d+@plop.com/) } + it { expect(rendered).to match(/gest\d+@gest.com/) } end end diff --git a/spec/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_show_spec.rb b/spec/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_show_spec.rb index dd61a7578..308acb09c 100644 --- a/spec/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_show_spec.rb +++ b/spec/views/layouts/left_panels/_left_panel_backoffice_dossierscontroller_show_spec.rb @@ -6,6 +6,7 @@ describe 'layouts/left_panels/_left_panel_backoffice_dossierscontroller_show.htm let(:state) { 'draft' } let(:archived) { false } let(:gestionnaire) { create(:gestionnaire) } + let!(:assign_to) { create(:assign_to, gestionnaire: gestionnaire, procedure: dossier.procedure) } before do sign_in gestionnaire diff --git a/spec/views/users/dossiers/index_html.haml_spec.rb b/spec/views/users/dossiers/index_html.haml_spec.rb index 09e787e99..3813456f8 100644 --- a/spec/views/users/dossiers/index_html.haml_spec.rb +++ b/spec/views/users/dossiers/index_html.haml_spec.rb @@ -43,7 +43,7 @@ describe 'users/dossiers/index.html.haml', type: :view do describe 'on tab en construction' do let(:total_dossiers) { 3 } let(:active_class) { '.active .text-danger' } - let(:dossiers_to_display) { user.dossiers.en_construction } + let(:dossiers_to_display) { user.dossiers.state_en_construction } let(:liste) { 'a_traiter' } it_behaves_like 'check_tab_content' do @@ -59,11 +59,10 @@ describe 'users/dossiers/index.html.haml', type: :view do end end - describe 'on tab etude en examen' do let(:total_dossiers) { 1 } let(:active_class) { '.active .text-default' } - let(:dossiers_to_display) { user.dossiers.en_instruction } + let(:dossiers_to_display) { user.dossiers.state_en_instruction } let(:liste) { 'en_instruction' } it_behaves_like 'check_tab_content' do @@ -74,7 +73,7 @@ describe 'users/dossiers/index.html.haml', type: :view do describe 'on tab etude termine' do let(:total_dossiers) { 3 } let(:active_class) { '.active .text-success' } - let(:dossiers_to_display) { user.dossiers.termine } + let(:dossiers_to_display) { user.dossiers.state_termine } let(:liste) { 'termine' } it_behaves_like 'check_tab_content' do diff --git a/spec/workers/auto_archive_procedure_worker_spec.rb b/spec/workers/auto_archive_procedure_worker_spec.rb index e67a19133..2a30fa764 100644 --- a/spec/workers/auto_archive_procedure_worker_spec.rb +++ b/spec/workers/auto_archive_procedure_worker_spec.rb @@ -1,7 +1,6 @@ require 'rails_helper' RSpec.describe AutoArchiveProcedureWorker, type: :worker do - let!(:procedure) { create(:procedure, archived: false, auto_archive_on: nil )} let!(:procedure_hier) { create(:procedure, archived: false, auto_archive_on: 1.day.ago )} let!(:procedure_aujourdhui) { create(:procedure, archived: false, auto_archive_on: Date.today )} @@ -17,11 +16,9 @@ RSpec.describe AutoArchiveProcedureWorker, type: :worker do end it { expect(procedure.archived).to eq false } - end context "when procedures have auto_archive_on set on yesterday or today" do - describe "titi" do before do subject @@ -31,12 +28,9 @@ RSpec.describe AutoArchiveProcedureWorker, type: :worker do it { expect(procedure_hier.archived).to eq true } it { expect(procedure_aujourdhui.archived).to eq true } - end - context "with dossiers" do - let!(:dossier1) { create(:dossier, procedure: procedure_hier, state: 'draft', archived: false)} let!(:dossier2) { create(:dossier, procedure: procedure_hier, state: 'initiated', archived: false)} let!(:dossier3) { create(:dossier, procedure: procedure_hier, state: 'replied', archived: false)} @@ -61,18 +55,14 @@ RSpec.describe AutoArchiveProcedureWorker, type: :worker do it { expect(dossier6.state).to eq 'closed' } it { expect(dossier7.state).to eq 'refused' } it { expect(dossier8.state).to eq 'without_continuation' } - end end context "when procedures have auto_archive_on set on future" do - before do subject end it { expect(procedure_demain.archived).to eq false } - end - end diff --git a/spec/workers/weekly_overview_worker_spec.rb b/spec/workers/weekly_overview_worker_spec.rb new file mode 100644 index 000000000..e2bff3e80 --- /dev/null +++ b/spec/workers/weekly_overview_worker_spec.rb @@ -0,0 +1,44 @@ +require 'rails_helper' + +RSpec.describe WeeklyOverviewWorker, type: :worker do + describe 'perform' do + let!(:gestionnaire) { create(:gestionnaire) } + let(:overview) { double('overview') } + let(:mailer_double) { double('mailer', deliver_now: true) } + + context 'if the feature is enabled' do + before { allow(Features).to receive(:weekly_overview).and_return(true) } + + context 'with one gestionnaire with one overview' do + before :each do + expect_any_instance_of(Gestionnaire).to receive(:last_week_overview).and_return(overview) + allow(GestionnaireMailer).to receive(:last_week_overview).and_return(mailer_double) + WeeklyOverviewWorker.new.perform + end + + it { expect(GestionnaireMailer).to have_received(:last_week_overview).with(gestionnaire, overview) } + it { expect(mailer_double).to have_received(:deliver_now) } + end + + context 'with one gestionnaire with no overviews' do + before :each do + expect_any_instance_of(Gestionnaire).to receive(:last_week_overview).and_return(nil) + allow(GestionnaireMailer).to receive(:last_week_overview) + WeeklyOverviewWorker.new.perform + end + + it { expect(GestionnaireMailer).not_to have_received(:last_week_overview) } + end + end + + context 'if the feature is disabled' do + before { allow(Features).to receive(:weekly_overview).and_return(false) } + before :each do + allow(Gestionnaire).to receive(:all) + WeeklyOverviewWorker.new.perform + end + + it { expect(Gestionnaire).not_to receive(:all) } + end + end +end