Merge pull request #4336 from betagouv/dev

2019-09-18-01
This commit is contained in:
Paul Chavard 2019-09-18 14:48:40 +02:00 committed by GitHub
commit 10e64b2f92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 690 additions and 783 deletions

View file

@ -1,70 +0,0 @@
/* globals $ */
$(document).on('turbolinks:load', init_path_modal);
var PROCEDURE_PATH_SELECTOR = 'input[data-autocomplete=path]';
function init_path_modal() {
path_modal_action();
path_validation_action();
path_type_init();
path_validation($(PROCEDURE_PATH_SELECTOR));
}
function path_modal_action() {
$('#publish-modal').on('show.bs.modal', function(event) {
$('#publish-modal .modal-body .table .tr-content').hide();
var button = $(event.relatedTarget); // Button that triggered the modal
var modal_title = button.data('modal_title'); // Extract info from data-* attributes
var modal_index = button.data('modal_index'); // Extract info from data-* attributes
var modal = $(this);
modal.find('#publish-modal-title').html(modal_title);
$('#publish-modal .modal-body .table #' + modal_index).show();
});
}
function path_validation_action() {
$(PROCEDURE_PATH_SELECTOR).keyup(function(key) {
if (key.keyCode != 13) path_validation(this);
});
}
function togglePathMessage(valid, mine) {
$('#path-messages .message').hide();
if (valid === true && mine === true) {
$('#path_is_mine').show();
} else if (valid === true && mine === false) {
$('#path_is_not_mine').show();
} else if (valid === false && mine === null) {
$('#path_is_invalid').show();
}
if ((valid && mine === null) || mine === true)
$('#publish-modal #publish').removeAttr('disabled');
else $('#publish-modal #publish').attr('disabled', 'disabled');
}
function path_validation(el) {
var valid = validatePath($(el).val());
toggleErrorClass(el, valid);
togglePathMessage(valid, null);
}
function toggleErrorClass(node, boolean) {
if (boolean) $(node).removeClass('input-error');
else $(node).addClass('input-error');
}
function validatePath(path) {
var re = /^[a-z0-9_-]{3,50}$/;
return re.test(path);
}
function path_type_init() {
$(PROCEDURE_PATH_SELECTOR).on('autocomplete:select', function(event) {
togglePathMessage(true, event.detail['mine']);
});
}

View file

@ -1,24 +0,0 @@
.path-mine-false {
color: #FF0000;
}
#path-messages {
.message {
display: none;
}
}
#publish-modal {
.algolia-autocomplete {
width: 300px;
}
.aa-dropdown-menu {
width: 300px;
}
// Fix the input not being displayed on the same line than the text before
.aa-input {
vertical-align: initial !important;
}
}

View file

@ -13,7 +13,6 @@
// = require _card // = require _card
// = require _helpers // = require _helpers
// = require _turbolinks // = require _turbolinks
// = require admin_procedures_modal
// = require admin_type_de_champ // = require admin_type_de_champ
// = require carte // = require carte
// = require custom_mails // = require custom_mails

View file

@ -45,4 +45,51 @@
padding: 20px; padding: 20px;
border-radius: 4px; border-radius: 4px;
} }
.send-dossier-actions-bar {
// scss-lint:disable VendorPrefix
position: -webkit-sticky; // This is needed on Safari (tested on 12.1)
// scss-lint:enable VendorPrefix
position: sticky;
bottom: 0;
display: flex;
flex-direction: row;
align-items: center;
margin-top: $default-padding;
margin-left: -$default-padding;
margin-right: -$default-padding;
margin-bottom: 0;
padding-top: 0;
padding-bottom: $default-spacer;
padding-right: $default-padding;
padding-left: $default-padding;
background: #FFFFFF;
border: 1px solid #CCCCCC;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom: none;
.button {
min-height: 38px;
line-height: 16px;
}
// If there are more than one button, align the "Send" button to the right
.button:not(:first-of-type).send {
margin-left: auto;
}
// Normal layout
@media (min-width: 500px) {
padding-top: $default-spacer * 2;
padding-bottom: $default-spacer * 2;
}
// Compact layout
@media (max-width: 500px) {
padding-top: $default-spacer;
padding-bottom: $default-spacer;
}
}
} }

View file

@ -108,10 +108,6 @@ footer {
.footer-row { .footer-row {
margin-bottom: 30px; margin-bottom: 30px;
&:last-child {
margin-bottom: 0;
}
// In this case, the bottom margin is defined directly on each individual column // In this case, the bottom margin is defined directly on each individual column
&.footer-columns { &.footer-columns {
margin-bottom: 0; margin-bottom: 0;

View file

@ -2,6 +2,13 @@
@import "common"; @import "common";
@import "constants"; @import "constants";
.procedure-header {
a.header-link {
display: inline-block;
margin-bottom: 1 * $default-padding;
}
}
#procedure-show { #procedure-show {
h1 { h1 {
color: $black; color: $black;
@ -9,11 +16,6 @@
margin-bottom: 1 * $default-padding; margin-bottom: 1 * $default-padding;
} }
a.notifications {
display: inline-block;
margin-bottom: 1 * $default-padding;
}
.dossiers-table { .dossiers-table {
margin-top: $default-spacer; margin-top: $default-spacer;
margin-bottom: 3 * $default-spacer; margin-bottom: 3 * $default-spacer;

View file

@ -53,7 +53,7 @@ $stat-card-half-horizontal-spacing: 4 * $default-space;
.stat-card-half { .stat-card-half {
width: calc((100% - #{$stat-card-half-horizontal-spacing}) / 2); width: calc((100% - #{$stat-card-half-horizontal-spacing}) / 2);
margin-right: 3 * $default-space; margin-right: $stat-card-half-horizontal-spacing;
} }
.stat-card-title { .stat-card-title {

View file

@ -40,33 +40,20 @@ class Admin::AttestationTemplatesController < AdminController
end end
def preview def preview
@title = activated_attestation_params[:title] @attestation = (@procedure.attestation_template || AttestationTemplate.new).render_attributes_for(activated_attestation_params)
@body = activated_attestation_params[:body]
@footer = activated_attestation_params[:footer]
@created_at = Time.zone.now
# In a case of a preview, when the user does not change its images,
# the images are not uploaded and thus should be retrieved from previous
# attestation_template
@logo = activated_attestation_params[:logo] || @procedure.attestation_template&.proxy_logo
@signature = activated_attestation_params[:signature] || @procedure.attestation_template&.proxy_signature
render 'admin/attestation_templates/show', formats: [:pdf] render 'admin/attestation_templates/show', formats: [:pdf]
end end
def delete_logo def delete_logo
attestation_template = @procedure.attestation_template @procedure.attestation_template.logo.purge_later
attestation_template.logo.purge_later
attestation_template.logo_active_storage.purge_later
flash.notice = 'le logo a bien été supprimée' flash.notice = 'le logo a bien été supprimée'
redirect_to edit_admin_procedure_attestation_template_path(@procedure) redirect_to edit_admin_procedure_attestation_template_path(@procedure)
end end
def delete_signature def delete_signature
attestation_template = @procedure.attestation_template @procedure.attestation_template.signature.purge_later
attestation_template.signature.purge_later
attestation_template.signature_active_storage.purge_later
flash.notice = 'la signature a bien été supprimée' flash.notice = 'la signature a bien été supprimée'
redirect_to edit_admin_procedure_attestation_template_path(@procedure) redirect_to edit_admin_procedure_attestation_template_path(@procedure)

View file

@ -2,7 +2,7 @@ class Admin::ProceduresController < AdminController
include SmartListing::Helper::ControllerExtensions include SmartListing::Helper::ControllerExtensions
helper SmartListing::Helper helper SmartListing::Helper
before_action :retrieve_procedure, only: [:show, :edit, :delete_logo, :delete_deliberation, :delete_notice, :monavis, :update_monavis] before_action :retrieve_procedure, only: [:show, :edit, :delete_logo, :delete_deliberation, :delete_notice, :monavis, :update_monavis, :publish_validate, :publish]
def index def index
if current_administrateur.procedures.count != 0 if current_administrateur.procedures.count != 0
@ -40,11 +40,16 @@ class Admin::ProceduresController < AdminController
end end
def show def show
if @procedure.brouillon?
@procedure_lien = commencer_test_url(path: @procedure.path)
else
@procedure_lien = commencer_url(path: @procedure.path)
end
@procedure.path = @procedure.suggested_path(current_administrateur)
@current_administrateur = current_administrateur
end end
def edit def edit
@path = @procedure.path || @procedure.default_path
@availability = @procedure.path_availability(current_administrateur, @path)
end end
def destroy def destroy
@ -63,13 +68,10 @@ class Admin::ProceduresController < AdminController
def new def new
@procedure ||= Procedure.new(for_individual: true) @procedure ||= Procedure.new(for_individual: true)
@availability = Procedure::PATH_AVAILABLE
end end
def create def create
@procedure = Procedure.new(procedure_params.merge(administrateurs: [current_administrateur])) @procedure = Procedure.new(procedure_params.merge(administrateurs: [current_administrateur]))
@path = @procedure.path
@availability = Procedure.path_availability(current_administrateur, @procedure.path)
if !@procedure.save if !@procedure.save
flash.now.alert = @procedure.errors.full_messages flash.now.alert = @procedure.errors.full_messages
@ -87,10 +89,6 @@ class Admin::ProceduresController < AdminController
if !@procedure.update(procedure_params) if !@procedure.update(procedure_params)
flash.now.alert = @procedure.errors.full_messages flash.now.alert = @procedure.errors.full_messages
@path = procedure_params[:path]
if @path.present?
@availability = @procedure.path_availability(current_administrateur, @path)
end
render 'edit' render 'edit'
elsif @procedure.brouillon? elsif @procedure.brouillon?
reset_procedure reset_procedure
@ -102,31 +100,19 @@ class Admin::ProceduresController < AdminController
end end
end end
def publish def publish_validate
path = params[:path] @procedure.assign_attributes(publish_params)
lien_site_web = params[:lien_site_web]
procedure = current_administrateur.procedures.find(params[:procedure_id])
procedure.path = path
procedure.lien_site_web = lien_site_web
if !procedure.validate
flash.alert = 'Lien de la démarche invalide ou lien vers la démarche manquant'
return redirect_to admin_procedures_path
else
procedure.path = nil
end end
if procedure.publish_or_reopen!(current_administrateur, path, lien_site_web) def publish
@procedure.assign_attributes(publish_params)
@procedure.publish_or_reopen!(current_administrateur)
flash.notice = "Démarche publiée" flash.notice = "Démarche publiée"
redirect_to admin_procedures_path redirect_to admin_procedures_path
else rescue ActiveRecord::RecordInvalid
@mine = false render 'publish_validate', formats: :js
render '/admin/procedures/publish', formats: 'js'
end
rescue ActiveRecord::RecordNotFound
flash.alert = 'Démarche inexistante'
redirect_to admin_procedures_path
end end
def transfer def transfer
@ -220,35 +206,8 @@ class Admin::ProceduresController < AdminController
@draft_class = 'active' @draft_class = 'active'
end end
def path_list
json_path_list = Procedure
.find_with_path(params[:request])
.order(:id)
.map do |procedure|
{
label: procedure.path,
mine: current_administrateur.owns?(procedure)
}
end.to_json
render json: json_path_list
end
def check_availability
path = params[:procedure][:path]
procedure_id = params[:procedure][:id]
if procedure_id.present?
procedure = current_administrateur.procedures.find(procedure_id)
@availability = procedure.path_availability(current_administrateur, path)
else
@availability = Procedure.path_availability(current_administrateur, path)
end
end
def delete_logo def delete_logo
@procedure.logo.purge_later @procedure.logo.purge_later
@procedure.logo_active_storage.purge_later
flash.notice = 'le logo a bien été supprimé' flash.notice = 'le logo a bien été supprimé'
redirect_to edit_admin_procedure_path(@procedure) redirect_to edit_admin_procedure_path(@procedure)
@ -274,6 +233,10 @@ class Admin::ProceduresController < AdminController
params[:from_new_from_existing].present? params[:from_new_from_existing].present?
end end
def publish_params
params.permit(:path, :lien_site_web)
end
def procedure_params def procedure_params
editable_params = [:libelle, :description, :organisation, :direction, :lien_site_web, :cadre_juridique, :deliberation, :notice, :web_hook_url, :euro_flag, :logo, :auto_archive_on, :monavis_embed] editable_params = [:libelle, :description, :organisation, :direction, :lien_site_web, :cadre_juridique, :deliberation, :notice, :web_hook_url, :euro_flag, :logo, :auto_archive_on, :monavis_embed]
permited_params = if @procedure&.locked? permited_params = if @procedure&.locked?

View file

@ -25,7 +25,7 @@ class AdminController < ApplicationController
end end
def reset_procedure def reset_procedure
if @procedure.brouillon_avec_lien? if @procedure.brouillon?
@procedure.reset! @procedure.reset!
end end
end end

View file

@ -16,18 +16,11 @@ module Instructeurs
def attestation def attestation
if dossier.attestation.pdf.attached? if dossier.attestation.pdf.attached?
redirect_to url_for(dossier.attestation.pdf) redirect_to url_for(dossier.attestation.pdf)
elsif dossier.attestation.pdf_active_storage.attached?
redirect_to url_for(dossier.attestation.pdf_active_storage)
end end
end end
def apercu_attestation def apercu_attestation
@title = dossier.procedure.attestation_template.title_for_dossier(dossier) @attestation = dossier.procedure.attestation_template.render_attributes_for(dossier: dossier)
@body = dossier.procedure.attestation_template.body_for_dossier(dossier)
@footer = dossier.procedure.attestation_template.footer
@created_at = Time.zone.now
@logo = dossier.procedure.attestation_template&.proxy_logo
@signature = dossier.procedure.attestation_template&.proxy_signature
render 'admin/attestation_templates/show', formats: [:pdf] render 'admin/attestation_templates/show', formats: [:pdf]
end end

View file

@ -7,24 +7,25 @@ module Instructeurs
def index def index
@procedures = current_instructeur @procedures = current_instructeur
.visible_procedures .procedures
.with_attached_logo .with_attached_logo
.includes(:defaut_groupe_instructeur) .includes(:defaut_groupe_instructeur)
.order(archived_at: :desc, published_at: :desc, created_at: :desc) .order(archived_at: :desc, published_at: :desc, created_at: :desc)
groupe_instructeurs = current_instructeur.groupe_instructeurs.where(procedure: @procedures) dossiers = current_instructeur.dossiers.joins(:groupe_instructeur)
@dossiers_count_per_procedure = dossiers.all_state.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_a_suivre_count_per_procedure = dossiers.without_followers.en_cours.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count
@dossiers_termines_count_per_procedure = dossiers.termine.group('groupe_instructeurs.procedure_id').reorder(nil).count
dossiers = current_instructeur.dossiers groupe_ids = current_instructeur.groupe_instructeurs.pluck(:id)
@dossiers_count_per_groupe_instructeur = dossiers.all_state.group(:groupe_instructeur_id).reorder(nil).count
@dossiers_a_suivre_count_per_groupe_instructeur = dossiers.without_followers.en_cours.group(:groupe_instructeur_id).reorder(nil).count
@dossiers_archived_count_per_groupe_instructeur = dossiers.archived.group(:groupe_instructeur_id).count
@dossiers_termines_count_per_groupe_instructeur = dossiers.termine.group(:groupe_instructeur_id).reorder(nil).count
@followed_dossiers_count_per_groupe_instructeur = current_instructeur @followed_dossiers_count_per_procedure = current_instructeur
.followed_dossiers .followed_dossiers
.joins(:groupe_instructeur)
.en_cours .en_cours
.where(groupe_instructeur: groupe_instructeurs) .where(groupe_instructeur_id: groupe_ids)
.group(:groupe_instructeur_id) .group('groupe_instructeurs.procedure_id')
.reorder(nil) .reorder(nil)
.count .count
end end
@ -205,6 +206,13 @@ module Instructeurs
redirect_to instructeur_procedure_path(procedure) redirect_to instructeur_procedure_path(procedure)
end end
def stats
@procedure = procedure
@usual_traitement_time = @procedure.stats_usual_traitement_time
@dossiers_funnel = @procedure.stats_dossiers_funnel
@termines_states = @procedure.stats_termines_states
end
private private
def find_field(table, column) def find_field(table, column)
@ -235,7 +243,7 @@ module Instructeurs
end end
def redirect_to_avis_if_needed def redirect_to_avis_if_needed
if current_instructeur.visible_procedures.count == 0 && current_instructeur.avis.count > 0 if current_instructeur.procedures.count == 0 && current_instructeur.avis.count > 0
redirect_to instructeur_avis_index_path redirect_to instructeur_avis_index_path
end end
end end

View file

@ -14,7 +14,7 @@ module Manager
end end
def reinvite def reinvite
Administrateur.find_inactive_by_id(params[:id]).invite!(current_administration.id) Administrateur.find_inactive_by_id(params[:id]).user.invite_administrateur!(current_administration.id)
flash.notice = "Invitation renvoyée" flash.notice = "Invitation renvoyée"
redirect_to manager_administrateur_path(params[:id]) redirect_to manager_administrateur_path(params[:id])
end end

View file

@ -20,7 +20,7 @@ module NewAdministrateur
end end
def reset_procedure def reset_procedure
if @procedure.brouillon_avec_lien? if @procedure.brouillon?
@procedure.reset! @procedure.reset!
end end
end end

View file

@ -3,21 +3,21 @@ module Users
layout 'procedure_context' layout 'procedure_context'
def commencer def commencer
@procedure = Procedure.publiees.find_by(path: params[:path]) @procedure = retrieve_procedure
return procedure_not_found if @procedure.blank? return procedure_not_found if @procedure.blank? || @procedure.brouillon?
render 'commencer/show' render 'commencer/show'
end end
def commencer_test def commencer_test
@procedure = Procedure.brouillons.find_by(path: params[:path]) @procedure = retrieve_procedure
return procedure_not_found if @procedure.blank? return procedure_not_found if @procedure.blank? || @procedure.publiee?
render 'commencer/show' render 'commencer/show'
end end
def sign_in def sign_in
@procedure = Procedure.find_by(path: params[:path]) @procedure = retrieve_procedure
return procedure_not_found if @procedure.blank? return procedure_not_found if @procedure.blank?
store_user_location!(@procedure) store_user_location!(@procedure)
@ -25,7 +25,7 @@ module Users
end end
def sign_up def sign_up
@procedure = Procedure.find_by(path: params[:path]) @procedure = retrieve_procedure
return procedure_not_found if @procedure.blank? return procedure_not_found if @procedure.blank?
store_user_location!(@procedure) store_user_location!(@procedure)
@ -33,7 +33,7 @@ module Users
end end
def france_connect def france_connect
@procedure = Procedure.find_by(path: params[:path]) @procedure = retrieve_procedure
return procedure_not_found if @procedure.blank? return procedure_not_found if @procedure.blank?
store_user_location!(@procedure) store_user_location!(@procedure)
@ -41,23 +41,25 @@ module Users
end end
def procedure_for_help def procedure_for_help
Procedure.publiees.find_by(path: params[:path]) || Procedure.brouillons.find_by(path: params[:path]) retrieve_procedure
end end
private private
def retrieve_procedure
Procedure.publiees.or(Procedure.brouillons).find_by(path: params[:path])
end
def procedure_not_found def procedure_not_found
procedure = Procedure.find_by(path: params[:path]) procedure = Procedure.find_by(path: params[:path])
if procedure&.archivee? if procedure&.archivee?
flash.alert = t('errors.messages.procedure_archived') flash.alert = t('errors.messages.procedure_archived')
elsif procedure&.publiee?
flash.alert = t('errors.messages.procedure_not_draft')
else else
flash.alert = t('errors.messages.procedure_not_found') flash.alert = t('errors.messages.procedure_not_found')
end end
return redirect_to root_path redirect_to root_path
end end
def store_user_location!(procedure) def store_user_location!(procedure)

View file

@ -50,8 +50,6 @@ module Users
def attestation def attestation
if dossier.attestation.pdf.attached? if dossier.attestation.pdf.attached?
redirect_to url_for(dossier.attestation.pdf) redirect_to url_for(dossier.attestation.pdf)
elsif dossier.attestation.pdf_active_storage.attached?
redirect_to url_for(dossier.attestation.pdf_active_storage)
end end
end end

View file

@ -1,13 +1,11 @@
module ProcedureHelper module ProcedureHelper
def procedure_lien(procedure) def procedure_lien(procedure)
if procedure.path.present? if procedure.brouillon?
if procedure.brouillon_avec_lien?
commencer_test_url(path: procedure.path) commencer_test_url(path: procedure.path)
else else
commencer_url(path: procedure.path) commencer_url(path: procedure.path)
end end
end end
end
def procedure_libelle(procedure) def procedure_libelle(procedure)
parts = procedure.brouillon? ? [content_tag(:span, 'démarche en test', class: 'badge')] : [] parts = procedure.brouillon? ? [content_tag(:span, 'démarche en test', class: 'badge')] : []

View file

@ -5,10 +5,6 @@ const sources = [
{ {
type: 'address', type: 'address',
url: '/address/suggestions' url: '/address/suggestions'
},
{
type: 'path',
url: '/admin/procedures/path_list'
} }
]; ];

View file

@ -1,6 +1,7 @@
module ActiveStorage module ActiveStorage
# Wraps an ActiveStorage::Service to route direct upload and direct download URLs through our proxy, # Wraps an ActiveStorage::Service to route direct upload and direct download URLs through our proxy,
# thus avoiding exposing the storage providers URL to our end-users. # thus avoiding exposing the storage providers URL to our end-users. It also overrides upload and
# object_for methods to fetch and put blobs through encryption proxy.
class Service::DsProxyService < SimpleDelegator class Service::DsProxyService < SimpleDelegator
attr_reader :wrapped attr_reader :wrapped
@ -23,8 +24,37 @@ module ActiveStorage
publicize(url) publicize(url)
end end
# This method is responsible for writing to object storage. We directly use direct upload
# url to ensure we upload through encryption proxy.
def upload(key, io, checksum: nil, **)
wrapped.send(:instrument, :upload, key: key, checksum: checksum) do
url = url_for_direct_upload(key, expires_in: 1.hour)
data = Fog::Storage.parse_data(io)
headers = { 'Content-Type' => wrapped.send(:guess_content_type, io) }.merge(data[:headers])
if checksum
headers['ETag'] = wrapped.send(:convert_base64digest_to_hexdigest, checksum)
end
response = Typhoeus::Request.new(
url,
method: :put,
body: data[:body].read,
headers: headers
).run
if response.success?
response
else
raise ActiveStorage::IntegrityError
end
end
end
private private
# This method is responsible for reading from object storage. We use url method
# on the service to ensure we download through encryption proxy.
def object_for(key, &block) def object_for(key, &block)
blob_url = url(key) blob_url = url(key)
if block_given? if block_given?

View file

@ -6,17 +6,12 @@ class ApplicationMailer < ActionMailer::Base
# Attach the procedure logo to the email (if any). # Attach the procedure logo to the email (if any).
# Returns the attachment url. # Returns the attachment url.
def attach_logo(procedure) def attach_logo(procedure)
return nil if !procedure.logo?
if procedure.logo.attached? if procedure.logo.attached?
logo_filename = procedure.logo.filename.to_s logo_filename = procedure.logo.filename.to_s
attachments.inline[logo_filename] = procedure.logo.download attachments.inline[logo_filename] = procedure.logo.download
elsif procedure.logo_active_storage.attached? attachments[logo_filename].url
logo_filename = procedure.logo_active_storage.filename.to_s
attachments.inline[logo_filename] = procedure.logo_active_storage.download
end end
attachments[logo_filename].url
rescue StandardError => e rescue StandardError => e
# A problem occured when reading logo, maybe the logo is missing and we should clean the procedure to remove logo reference ? # A problem occured when reading logo, maybe the logo is missing and we should clean the procedure to remove logo reference ?
Raven.extra_context(procedure_id: procedure.id) Raven.extra_context(procedure_id: procedure.id)

View file

@ -4,13 +4,10 @@ class Attestation < ApplicationRecord
belongs_to :dossier belongs_to :dossier
has_one_attached :pdf has_one_attached :pdf
has_one_attached :pdf_active_storage
def pdf_url def pdf_url
if pdf.attached? if pdf.attached?
Rails.application.routes.url_helpers.url_for(pdf) Rails.application.routes.url_helpers.url_for(pdf)
elsif pdf_active_storage.attached?
Rails.application.routes.url_helpers.url_for(pdf_active_storage)
end end
end end
end end

View file

@ -7,9 +7,7 @@ class AttestationTemplate < ApplicationRecord
belongs_to :procedure belongs_to :procedure
has_one_attached :logo has_one_attached :logo
has_one_attached :logo_active_storage
has_one_attached :signature has_one_attached :signature
has_one_attached :signature_active_storage
validates :footer, length: { maximum: 190 } validates :footer, length: { maximum: 190 }
@ -54,14 +52,6 @@ class AttestationTemplate < ApplicationRecord
# we don't want to run virus scanner on duplicated file # we don't want to run virus scanner on duplicated file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE } metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
) )
elsif logo_active_storage.attached?
attestation_template.logo.attach(
io: StringIO.new(logo_active_storage.download),
filename: logo_active_storage.filename.to_s,
content_type: logo_active_storage.content_type,
# we don't want to run virus scanner on duplicated file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
end end
if signature.attached? if signature.attached?
@ -72,65 +62,34 @@ class AttestationTemplate < ApplicationRecord
# we don't want to run virus scanner on duplicated file # we don't want to run virus scanner on duplicated file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE } metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
) )
elsif signature_active_storage.attached?
attestation_template.signature.attach(
io: StringIO.new(signature_active_storage.download),
filename: signature_active_storage.filename.to_s,
content_type: signature_active_storage.content_type,
# we don't want to run virus scanner on duplicated file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
end end
attestation_template attestation_template
end end
def logo?
logo.attached? || logo_active_storage.attached?
end
def signature?
signature.attached? || signature_active_storage.attached?
end
def logo_url def logo_url
if logo.attached? if logo.attached?
Rails.application.routes.url_helpers.url_for(logo) Rails.application.routes.url_helpers.url_for(logo)
elsif logo_active_storage.attached?
Rails.application.routes.url_helpers.url_for(logo_active_storage)
end end
end end
def signature_url def signature_url
if signature.attached? if signature.attached?
Rails.application.routes.url_helpers.url_for(signature) Rails.application.routes.url_helpers.url_for(signature)
elsif signature_active_storage.attached?
Rails.application.routes.url_helpers.url_for(signature_active_storage)
end end
end end
def proxy_logo def render_attributes_for(params = {})
if logo.attached? dossier = params.fetch(:dossier, false)
logo
elsif logo_active_storage.attached?
logo_active_storage
end
end
def proxy_signature {
if signature.attached? created_at: Time.zone.now,
signature title: dossier ? replace_tags(title, dossier) : params.fetch(:title, ''),
elsif signature_active_storage.attached? body: dossier ? replace_tags(body, dossier) : params.fetch(:body, ''),
signature_active_storage footer: params.fetch(:footer, footer),
end logo: params.fetch(:logo, logo.attached? ? logo : nil),
end signature: params.fetch(:signature, signature.attached? ? signature : nil)
}
def title_for_dossier(dossier)
replace_tags(title, dossier)
end
def body_for_dossier(dossier)
replace_tags(body, dossier)
end end
private private
@ -147,14 +106,8 @@ class AttestationTemplate < ApplicationRecord
end end
def build_pdf(dossier) def build_pdf(dossier)
action_view = ActionView::Base.new(ActionController::Base.view_paths, attestation = render_attributes_for(dossier: dossier)
logo: proxy_logo, action_view = ActionView::Base.new(ActionController::Base.view_paths, attestation: attestation)
title: title_for_dossier(dossier),
body: body_for_dossier(dossier),
signature: proxy_signature,
footer: footer,
created_at: Time.zone.now)
attestation_view = action_view.render(file: 'admin/attestation_templates/show', formats: [:pdf]) attestation_view = action_view.render(file: 'admin/attestation_templates/show', formats: [:pdf])
StringIO.new(attestation_view) StringIO.new(attestation_view)

View file

@ -0,0 +1,30 @@
module ProcedureStatsConcern
extend ActiveSupport::Concern
def stats_usual_traitement_time
Rails.cache.fetch("#{cache_key_with_version}/stats_usual_traitement_time", expires_in: 12.hours) do
usual_traitement_time
end
end
def stats_dossiers_funnel
Rails.cache.fetch("#{cache_key_with_version}/stats_dossiers_funnel", expires_in: 12.hours) do
[
['Démarrés', dossiers.count],
['Déposés', dossiers.state_not_brouillon.count],
['Instruction débutée', dossiers.state_instruction_commencee.count],
['Traités', dossiers.state_termine.count]
]
end
end
def stats_termines_states
Rails.cache.fetch("#{cache_key_with_version}/stats_termines_states", expires_in: 12.hours) do
[
['Acceptés', dossiers.where(state: :accepte).count],
['Refusés', dossiers.where(state: :refuse).count],
['Classés sans suite', dossiers.where(state: :sans_suite).count]
]
end
end
end

View file

@ -24,10 +24,6 @@ class Instructeur < ApplicationRecord
has_one :user has_one :user
def visible_procedures
procedures.merge(Procedure.avec_lien.or(Procedure.archivees))
end
def follow(dossier) def follow(dossier)
begin begin
followed_dossiers << dossier followed_dossiers << dossier

View file

@ -3,6 +3,8 @@ require Rails.root.join('lib', 'percentile')
class Procedure < ApplicationRecord class Procedure < ApplicationRecord
self.ignored_columns = ['logo', 'logo_secure_token'] self.ignored_columns = ['logo', 'logo_secure_token']
include ProcedureStatsConcern
MAX_DUREE_CONSERVATION = 36 MAX_DUREE_CONSERVATION = 36
has_many :types_de_champ, -> { root.public_only.ordered }, inverse_of: :procedure, dependent: :destroy has_many :types_de_champ, -> { root.public_only.ordered }, inverse_of: :procedure, dependent: :destroy
@ -30,7 +32,6 @@ class Procedure < ApplicationRecord
has_one :defaut_groupe_instructeur, -> { where(label: GroupeInstructeur::DEFAULT_LABEL) }, class_name: 'GroupeInstructeur', inverse_of: :procedure has_one :defaut_groupe_instructeur, -> { where(label: GroupeInstructeur::DEFAULT_LABEL) }, class_name: 'GroupeInstructeur', inverse_of: :procedure
has_one_attached :logo has_one_attached :logo
has_one_attached :logo_active_storage
has_one_attached :notice has_one_attached :notice
has_one_attached :deliberation has_one_attached :deliberation
@ -45,7 +46,6 @@ class Procedure < ApplicationRecord
scope :by_libelle, -> { order(libelle: :asc) } scope :by_libelle, -> { order(libelle: :asc) }
scope :created_during, -> (range) { where(created_at: range) } scope :created_during, -> (range) { where(created_at: range) }
scope :cloned_from_library, -> { where(cloned_from_library: true) } scope :cloned_from_library, -> { where(cloned_from_library: true) }
scope :avec_lien, -> { where.not(path: nil) }
scope :declarative, -> { where.not(declarative_with_state: nil) } scope :declarative, -> { where.not(declarative_with_state: nil) }
scope :for_api, -> { scope :for_api, -> {
@ -65,8 +65,10 @@ class Procedure < ApplicationRecord
validates :libelle, presence: true, allow_blank: false, allow_nil: false validates :libelle, presence: true, allow_blank: false, allow_nil: false
validates :description, presence: true, allow_blank: false, allow_nil: false validates :description, presence: true, allow_blank: false, allow_nil: false
validates :administrateurs, presence: true validates :administrateurs, presence: true
validates :lien_site_web, presence: true, if: :publiee?
validate :validate_for_publication, on: :publication
validate :check_juridique validate :check_juridique
validates :path, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: { scope: :aasm_state, case_sensitive: false }, presence: true, allow_blank: false, allow_nil: true validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: { scope: [:path, :archived_at, :hidden_at], case_sensitive: false }
# FIXME: remove duree_conservation_required flag once all procedures are converted to the new style # FIXME: remove duree_conservation_required flag once all procedures are converted to the new style
validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }, if: :durees_conservation_required validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }, if: :durees_conservation_required
validates :duree_conservation_dossiers_hors_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :durees_conservation_required validates :duree_conservation_dossiers_hors_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :durees_conservation_required
@ -75,7 +77,8 @@ class Procedure < ApplicationRecord
validates_with MonAvisEmbedValidator validates_with MonAvisEmbedValidator
before_save :update_juridique_required before_save :update_juridique_required
before_save :update_durees_conservation_required before_save :update_durees_conservation_required
before_create :ensure_path_exists after_initialize :ensure_path_exists
before_save :ensure_path_exists
after_create :ensure_default_groupe_instructeur after_create :ensure_default_groupe_instructeur
include AASM include AASM
@ -86,11 +89,8 @@ class Procedure < ApplicationRecord
state :archivee state :archivee
state :hidden state :hidden
event :publish, after: :after_publish, guard: :can_publish? do event :publish, before: :before_publish, after: :after_publish do
transitions from: :brouillon, to: :publiee transitions from: :brouillon, to: :publiee
end
event :reopen, after: :after_reopen, guard: :can_publish? do
transitions from: :archivee, to: :publiee transitions from: :archivee, to: :publiee
end end
@ -109,12 +109,18 @@ class Procedure < ApplicationRecord
end end
end end
def publish_or_reopen!(administrateur, path, lien_site_web) def publish_or_reopen!(administrateur)
if archivee? && may_reopen?(administrateur, path, lien_site_web) Procedure.transaction do
reopen!(administrateur, path, lien_site_web) if brouillon?
elsif may_publish?(administrateur, path, lien_site_web)
reset! reset!
publish!(administrateur, path, lien_site_web) end
other_procedure = other_procedure_with_path(path)
if other_procedure.present? && administrateur.owns?(other_procedure)
other_procedure.archive!
end
publish!
end end
end end
@ -126,6 +132,44 @@ class Procedure < ApplicationRecord
end end
end end
def validate_for_publication
old_attributes = self.slice(:aasm_state, :archived_at)
self.aasm_state = :publiee
self.archived_at = nil
is_valid = validate
self.attributes = old_attributes
is_valid
end
def suggested_path(administrateur)
if path_customized?
return path
end
slug = libelle&.parameterize&.first(50)
suggestion = slug
counter = 1
while !path_available?(administrateur, suggestion)
counter = counter + 1
suggestion = "#{slug}-#{counter}"
end
suggestion
end
def other_procedure_with_path(path)
Procedure.publiees
.where.not(id: self.id)
.find_by(path: path)
end
def path_available?(administrateur, path)
procedure = other_procedure_with_path(path)
procedure.blank? || administrateur.owns?(procedure)
end
def locked? def locked?
publiee_ou_archivee? publiee_ou_archivee?
end end
@ -134,11 +178,6 @@ class Procedure < ApplicationRecord
!archivee? !archivee?
end end
# This method is needed for transition. Eventually this will be the same as brouillon?.
def brouillon_avec_lien?
brouillon? && path.present?
end
def publiee_ou_archivee? def publiee_ou_archivee?
publiee? || archivee? publiee? || archivee?
end end
@ -174,8 +213,8 @@ class Procedure < ApplicationRecord
types_de_champ_private.map(&:build_champ) types_de_champ_private.map(&:build_champ)
end end
def default_path def path_customized?
libelle&.parameterize&.first(50) !path.match?(/[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}/)
end end
def organisation_name def organisation_name
@ -264,7 +303,6 @@ class Procedure < ApplicationRecord
clone_attachment(:piece_justificative_template, original, kopy) clone_attachment(:piece_justificative_template, original, kopy)
elsif original.is_a?(Procedure) elsif original.is_a?(Procedure)
clone_attachment(:logo, original, kopy) clone_attachment(:logo, original, kopy)
clone_attachment(:logo_active_storage, original, kopy)
clone_attachment(:notice, original, kopy) clone_attachment(:notice, original, kopy)
clone_attachment(:deliberation, original, kopy) clone_attachment(:deliberation, original, kopy)
end end
@ -370,40 +408,6 @@ class Procedure < ApplicationRecord
percentile_time(:en_construction_at, :processed_at, 90) percentile_time(:en_construction_at, :processed_at, 90)
end end
PATH_AVAILABLE = :available
PATH_AVAILABLE_PUBLIEE = :available_publiee
PATH_NOT_AVAILABLE = :not_available
PATH_NOT_AVAILABLE_BROUILLON = :not_available_brouillon
PATH_CAN_PUBLISH = [PATH_AVAILABLE, PATH_AVAILABLE_PUBLIEE]
def path_availability(administrateur, path)
Procedure.path_availability(administrateur, path, id)
end
def self.path_availability(administrateur, path, exclude_id = nil)
if exclude_id.present?
procedure = where.not(id: exclude_id).find_by(path: path)
else
procedure = find_by(path: path)
end
if procedure.blank?
PATH_AVAILABLE
elsif administrateur.owns?(procedure)
if procedure.brouillon?
PATH_NOT_AVAILABLE_BROUILLON
else
PATH_AVAILABLE_PUBLIEE
end
else
PATH_NOT_AVAILABLE
end
end
def self.find_with_path(path)
where.not(aasm_state: :archivee).where("path LIKE ?", "%#{path}%")
end
def populate_champ_stable_ids def populate_champ_stable_ids
TypeDeChamp.where(procedure: self, stable_id: nil).find_each do |type_de_champ| TypeDeChamp.where(procedure: self, stable_id: nil).find_each do |type_de_champ|
type_de_champ.update_column(:stable_id, type_de_champ.id) type_de_champ.update_column(:stable_id, type_de_champ.id)
@ -465,15 +469,9 @@ class Procedure < ApplicationRecord
end end
end end
def logo?
logo.attached? || logo_active_storage.attached?
end
def logo_url def logo_url
if logo.attached? if logo.attached?
Rails.application.routes.url_helpers.url_for(logo) Rails.application.routes.url_helpers.url_for(logo)
elsif logo_active_storage.attached?
Rails.application.routes.url_helpers.url_for(logo_active_storage)
else else
ActionController::Base.helpers.image_url("marianne.svg") ActionController::Base.helpers.image_url("marianne.svg")
end end
@ -497,41 +495,21 @@ class Procedure < ApplicationRecord
end end
end end
def claim_path_ownership!(path) def before_publish
procedure = Procedure.joins(:administrateurs) update!(archived_at: nil)
.where(administrateurs: { id: administrateur_ids })
.find_by(path: path)
if procedure&.publiee? && procedure != self
procedure.archive!
end end
update!(path: path) def after_publish
end
def can_publish?(administrateur, path, lien_site_web)
path_availability(administrateur, path).in?(PATH_CAN_PUBLISH) && lien_site_web.present?
end
def after_publish(administrateur, path, lien_site_web)
update!(published_at: Time.zone.now) update!(published_at: Time.zone.now)
claim_path_ownership!(path)
end
def after_reopen(administrateur, path, lien_site_web)
update!(published_at: Time.zone.now, archived_at: nil)
claim_path_ownership!(path)
end end
def after_archive def after_archive
update!(archived_at: Time.zone.now, path: SecureRandom.uuid) update!(archived_at: Time.zone.now)
end end
def after_hide def after_hide
now = Time.zone.now now = Time.zone.now
update!(hidden_at: now, path: SecureRandom.uuid) update!(hidden_at: now)
dossiers.update_all(hidden_at: now) dossiers.update_all(hidden_at: now)
end end
@ -568,7 +546,7 @@ class Procedure < ApplicationRecord
end end
def ensure_path_exists def ensure_path_exists
if self.path.nil? if self.path.blank?
self.path = SecureRandom.uuid self.path = SecureRandom.uuid
end end
end end

View file

@ -24,14 +24,12 @@ class ProcedureSerializer < ActiveModel::Serializer
end end
def link def link
if object.path.present? if object.brouillon?
if object.brouillon_avec_lien?
commencer_test_url(path: object.path) commencer_test_url(path: object.path)
else else
commencer_url(path: object.path) commencer_url(path: object.path)
end end
end end
end
def state def state
object.aasm_state object.aasm_state

View file

@ -1,7 +1,7 @@
class MonAvisEmbedValidator < ActiveModel::Validator class MonAvisEmbedValidator < ActiveModel::Validator
def validate(record) def validate(record)
# We need to ensure the embed code is not any random string in order to avoid injections # We need to ensure the embed code is not any random string in order to avoid injections
r = Regexp.new('<a href="https://monavis.numerique.gouv.fr/Demarches/\d+.*key=[[:alnum:]]+.*">\s*<img src="https://monavis.numerique.gouv.fr/monavis-static/bouton-blanc|bleu.png" alt="Je donne mon avis" title="Je donne mon avis sur cette démarche" />\s*</a>', Regexp::MULTILINE) r = Regexp.new('<a href="https://monavis|voxusagers.numerique.gouv.fr/Demarches/\d+.*key=[[:alnum:]]+.*">\s*<img src="(https://monavis.numerique.gouv.fr/monavis-static/bouton-blanc|bleu.png)|(https://voxusagers.numerique.gouv.fr/static/bouton-(bleu|blanc).svg)" alt="Je donne mon avis" title="Je donne mon avis sur cette démarche" />\s*</a>', Regexp::MULTILINE)
if record.monavis_embed.present? && !r.match?(record.monavis_embed) if record.monavis_embed.present? && !r.match?(record.monavis_embed)
record.errors[:base] << "Le code fourni ne correspond pas au format des codes MonAvis reconnus par la plateforme." record.errors[:base] << "Le code fourni ne correspond pas au format des codes MonAvis reconnus par la plateforme."
end end

View file

@ -17,11 +17,11 @@
celle-ci est également disponible au téléchargement depuis lespace personnel de lusager. celle-ci est également disponible au téléchargement depuis lespace personnel de lusager.
.image-upload .image-upload
- if @attestation_template.logo? - if @attestation_template.logo.attached?
= image_tag @attestation_template.logo_url, class: 'thumbnail' = image_tag @attestation_template.logo_url, class: 'thumbnail'
.form-group .form-group
= f.label :logo, "Logo de l'attestation" = f.label :logo, "Logo de l'attestation"
- if @attestation_template.logo? - if @attestation_template.logo.attached?
= link_to 'Supprimer le logo', admin_procedure_attestation_template_logo_path(@procedure), method: :delete = link_to 'Supprimer le logo', admin_procedure_attestation_template_logo_path(@procedure), method: :delete
= f.file_field :logo, accept: 'image/png, image/jpg, image/jpeg' = f.file_field :logo, accept: 'image/png, image/jpg, image/jpeg'
%p.help-block %p.help-block
@ -54,11 +54,11 @@
= tag[:description] = tag[:description]
.image-upload .image-upload
- if @attestation_template.signature? - if @attestation_template.signature.attached?
= image_tag @attestation_template.signature_url, class: 'thumbnail' = image_tag @attestation_template.signature_url, class: 'thumbnail'
.form-group .form-group
= f.label :signature, "Tampon de l'attestation" = f.label :signature, "Tampon de l'attestation"
- if @attestation_template.signature? - if @attestation_template.signature.attached?
= link_to 'Supprimer le tampon', admin_procedure_attestation_template_signature_path(@procedure), method: :delete = link_to 'Supprimer le tampon', admin_procedure_attestation_template_signature_path(@procedure), method: :delete
= f.file_field :signature, accept: 'image/png, image/jpg, image/jpeg' = f.file_field :signature, accept: 'image/png, image/jpg, image/jpeg'
%p.help-block %p.help-block

View file

@ -19,6 +19,14 @@ max_logo_width = body_width
max_logo_height = 50.mm max_logo_height = 50.mm
max_signature_size = 50.mm max_signature_size = 50.mm
title = @attestation.fetch(:title)
body = @attestation.fetch(:body)
footer = @attestation.fetch(:footer)
created_at = @attestation.fetch(:created_at)
logo = @attestation[:logo]
signature = @attestation[:signature]
prawn_document(margin: [top_margin, right_margin, bottom_margin, left_margin], page_size: page_size) do |pdf| prawn_document(margin: [top_margin, right_margin, bottom_margin, left_margin], page_size: page_size) do |pdf|
pdf.font_families.update( 'liberation serif' => { normal: Rails.root.join('lib/prawn/fonts/liberation_serif/LiberationSerif-Regular.ttf' )}) pdf.font_families.update( 'liberation serif' => { normal: Rails.root.join('lib/prawn/fonts/liberation_serif/LiberationSerif-Regular.ttf' )})
pdf.font 'liberation serif' pdf.font 'liberation serif'
@ -29,30 +37,30 @@ prawn_document(margin: [top_margin, right_margin, bottom_margin, left_margin], p
body_height = pdf.cursor - footer_height body_height = pdf.cursor - footer_height
pdf.bounding_box([0, pdf.cursor], width: body_width, height: body_height) do pdf.bounding_box([0, pdf.cursor], width: body_width, height: body_height) do
if @logo.present? if logo.present?
logo_file = if @logo.is_a?(ActiveStorage::Attached::One) logo_file = if logo.is_a?(ActiveStorage::Attached::One)
@logo.download logo.download
else else
@logo.read logo.read
end end
pdf.image StringIO.new(logo_file), fit: [max_logo_width , max_logo_height], position: :center pdf.image StringIO.new(logo_file), fit: [max_logo_width , max_logo_height], position: :center
end end
pdf.fill_color grey pdf.fill_color grey
pdf.pad_top(40) { pdf.text "le #{l(@created_at, format: '%e %B %Y')}", size: 10, align: :right, character_spacing: -0.5 } pdf.pad_top(40) { pdf.text "le #{l(created_at, format: '%e %B %Y')}", size: 10, align: :right, character_spacing: -0.5 }
pdf.fill_color black pdf.fill_color black
pdf.pad_top(40) { pdf.text @title, size: 18, character_spacing: -0.2 } pdf.pad_top(40) { pdf.text title, size: 18, character_spacing: -0.2 }
pdf.fill_color grey pdf.fill_color grey
pdf.pad_top(30) { pdf.text @body, size: 10, character_spacing: -0.2, align: :justify } pdf.pad_top(30) { pdf.text body, size: 10, character_spacing: -0.2, align: :justify }
if @signature.present? if signature.present?
pdf.pad_top(40) do pdf.pad_top(40) do
signature_file = if @signature.is_a?(ActiveStorage::Attached::One) signature_file = if signature.is_a?(ActiveStorage::Attached::One)
@signature.download signature.download
else else
@signature.read signature.read
end end
pdf.image StringIO.new(signature_file), fit: [max_signature_size , max_signature_size], position: :right pdf.image StringIO.new(signature_file), fit: [max_signature_size , max_signature_size], position: :right
end end
@ -62,6 +70,6 @@ prawn_document(margin: [top_margin, right_margin, bottom_margin, left_margin], p
pdf.repeat(:all) do pdf.repeat(:all) do
pdf.move_cursor_to footer_height - 10 pdf.move_cursor_to footer_height - 10
pdf.fill_color grey pdf.fill_color grey
pdf.text @footer, align: :center, size: 8 pdf.text footer, align: :center, size: 8
end end
end end

View file

@ -77,7 +77,7 @@
.row .row
.col-md-6 .col-md-6
%h4 Logo de la démarche %h4 Logo de la démarche
- if @procedure.logo? - if @procedure.logo.attached?
= image_tag @procedure.logo_url, { style: 'height: 40px; display: inline; margin-right: 6px;', id: 'preview_procedure_logo' } = image_tag @procedure.logo_url, { style: 'height: 40px; display: inline; margin-right: 6px;', id: 'preview_procedure_logo' }
\- \-

View file

@ -1,63 +1,49 @@
#publish-modal.modal{ "aria-labelledby" => "myModalLabel", :role => "dialog", :tabindex => "-1" } #publish-modal.modal{ "aria-labelledby" => "myModalLabel", :role => "dialog", :tabindex => "-1" }
.modal-dialog.modal-lg{ :role => "document" } .modal-dialog.modal-lg{ :role => "document" }
= form_tag admin_procedure_publish_path(procedure_id: @procedure.id), method: :put, remote: true do = form_tag admin_procedure_publish_path(procedure_id: procedure.id), method: :put, remote: true do
.modal-content .modal-content
.modal-header .modal-header
%button.close{ "aria-label" => "Close", "data-dismiss" => "modal", :type => "button" } %button.close{ "aria-label" => "Close", "data-dismiss" => "modal", :type => "button" }
%span{ "aria-hidden" => "true" } × %span{ "aria-hidden" => "true" } ×
%h4#myModalLabel.modal-title %h4#myModalLabel.modal-title
= procedure_modal_text(@procedure, :title) = procedure_modal_text(procedure, :title)
%span#publish-modal-title %span#publish-modal-title
.modal-body .modal-body
= procedure_modal_text(@procedure, :body) .text-info
- if !@procedure.archivee? %p
%b = procedure_modal_text(procedure, :body)
Elle ne pourra plus être modifiée à lissue de cette publication. %b Elle ne pourra plus être modifiée à lissue de cette publication.
%br
Afin de faciliter laccès à la démarche, vous êtes invité à personnaliser ladresse d'accès si vous le souhaitez.
%br
.form-group .form-group
%br %h4 Adresse de la démarche
%h4 Lien de la démarche %p Vous pouvez personnaliser le lien public de la démarche pour en faciliter laccès.
%p.center %p
= commencer_url(path: '') = commencer_url(path: '')
= text_field_tag(:path, @procedure.path || @procedure.default_path, = text_field_tag(:path, procedure.path,
id: 'procedure_path', id: 'procedure_path',
placeholder: 'Chemin vers la démarche', placeholder: 'chemin-de-la-démarche',
data: { autocomplete: 'path' },
class: 'form-control',
maxlength: 50,
style: 'width: 300px; display: inline;')
%br
.alert.alert-info
Attention, diffusez toujours le <strong>lien complet</strong> affiché ci-dessus, et non pas un lien générique vers demarches-simplifiees.fr. Ne dites pas non plus aux usagers de se rendre sur le site générique demarches-simplifiees.fr, donnez-leur toujours le lien complet.
%br
%br
Prenez quelques minutes pour savoir comment établir une bonne relation avec les usagers de votre démarche :
= link_to "Regarder la vidéo de 5 minutes",
"https://vimeo.com/334463514",
target: "_blank"
.form-group
%h4 Où les usagers trouveront-ils le lien vers cette démarche ? *
%p.center
= text_field_tag(:lien_site_web, @procedure.lien_site_web,
required: true, required: true,
class: 'form-control', class: 'form-control',
pattern: '^[a-z0-9_-]{3,50}$',
title: "De 3 à 50 caractères; minuscules, chiffres et tiret seulement",
data: { remote: true, debounce: true, url: admin_procedure_publish_validate_path(procedure) },
autocomplete: 'off',
style: 'width: 300px; display: inline;')
= render 'publish_path_message', procedure: procedure, administrateur: administrateur
.text-info
Attention, diffusez toujours le <strong>lien complet</strong> affiché ci-dessus, et non pas un lien générique vers demarches-simplifiees.fr. Ne dites pas non plus aux usagers de se rendre sur le site générique demarches-simplifiees.fr, donnez-leur toujours le lien complet.
.form-group
%h4 Diffusion de la démarche
%p Où les utilisateurs trouveront-ils le lien de la démarche ? Typiquement, il sagit dune page de votre site web.
%p.center
= text_field_tag(:lien_site_web, procedure.lien_site_web,
required: true,
class: 'form-control',
autocomplete: 'off',
placeholder: 'https://exemple.gouv.fr/ma_demarche') placeholder: 'https://exemple.gouv.fr/ma_demarche')
.text-info
#path-messages Prenez quelques minutes pour savoir comment établir une bonne relation avec les usagers de votre démarche :
#path_is_mine.text-warning.center.message = link_to "Regarder la vidéo de 5 minutes.",
Ce lien est déjà utilisé par une de vos démarche. "https://vimeo.com/334463514",
%br target: "_blank"
Si vous voulez lutiliser, lancienne démarche sera archivée (plus accessible du public).
#path_is_not_mine.text-danger.center.message
Ce lien est déjà utilisé par une démarche.
%br
Vous ne pouvez pas lutiliser car il appartient à un autre administrateur.
#path_is_invalid.text-danger.center.message
Ce lien
= t('activerecord.errors.models.procedure.attributes.path.invalid')
.modal-footer .modal-footer
= submit_tag procedure_modal_text(@procedure, :submit), class: %w(btn btn btn-success), disabled: :disabled, id: 'publish' = render 'publish_buttons', procedure: procedure, administrateur: administrateur
= button_tag "Annuler", class: %w(btn btn btn-default), data: { dismiss: :modal }, id: 'cancel'

View file

@ -0,0 +1,13 @@
#publish-buttons
= button_tag "Annuler", class: %w(btn btn-default), data: { dismiss: :modal }
- procedure.validate(:publication)
- errors = procedure.errors
-# Ignore the :taken error if the path can be claimed
- if errors.details[:path]&.pluck(:error)&.include?(:taken) && procedure.path_available?(administrateur, procedure.path)
- errors.delete(:path)
- options = { class: %w(btn btn-success), id: 'publish' }
- if errors.details[:path].present?
- options[:disabled] = :disabled
= submit_tag procedure_modal_text(@procedure, :submit), options

View file

@ -0,0 +1,11 @@
#publish-path-message
- procedure.validate(:publication)
- errors = procedure.errors
-# Ignore the :taken error if the path can be claimed, and instead display the :taken_can_be_claimed error message.
- if errors.details[:path]&.pluck(:error)&.include?(:taken) && procedure.path_available?(administrateur, procedure.path)
.alert.alert-warning
= errors.full_message('Le lien public', errors.generate_message(:path, :taken_can_be_claimed))
- elsif errors.messages[:path].present?
-# Display the actual errors for :path
.alert.alert-danger
= errors.full_message('Le lien public', errors.messages[:path].first)

View file

@ -1,11 +0,0 @@
- case availability
- when Procedure::PATH_AVAILABLE_PUBLIEE
Ce lien est déjà utilisé par une de vos démarche.
%br
Si vous voulez lutiliser, lancienne démarche sera archivée lors de la publication de la démarche (plus accessible du public).
- when Procedure::PATH_NOT_AVAILABLE_BROUILLON
Une démarche en test existe déjà avec ce lien.
- when Procedure::PATH_NOT_AVAILABLE
Ce lien est déjà utilisé par une démarche.
%br
Vous ne pouvez pas lutiliser car il appartient à un autre administrateur.

View file

@ -1,11 +0,0 @@
<% if @availability == Procedure::PATH_AVAILABLE %>
<%= remove_element('.unavailable-path-message', inner: true) %>
<% else %>
<%= render_to_element('.unavailable-path-message', partial: 'unavailable', locals: { availability: @availability }) %>
<% end %>
<% if @availability.in?(Procedure::PATH_CAN_PUBLISH) %>
<%= enable_element('button[type=submit]') %>
<% else %>
<%= disable_element('button[type=submit]') %>
<% end %>

View file

@ -1 +0,0 @@
<%= "togglePathMessage(true, #{@mine})" %>

View file

@ -0,0 +1,4 @@
= render_to_element("#publish-path-message", partial: 'publish_path_message', outer: true,
locals: { procedure: @procedure, administrateur: current_administrateur })
= render_to_element("#publish-buttons", partial: 'publish_buttons', outer: true,
locals: { procedure: @procedure, administrateur: current_administrateur })

View file

@ -1,7 +1,7 @@
= render partial: 'admin/closed_mail_template_attestation_inconsistency_alert' = render partial: 'admin/closed_mail_template_attestation_inconsistency_alert'
.row.white-back .row.white-back
#procedure_show #procedure_show
= render partial: '/admin/procedures/modal_publish' = render 'modal_publish', procedure: @procedure, administrateur: @current_administrateur
= render partial: '/admin/procedures/modal_transfer' = render partial: '/admin/procedures/modal_transfer'
- if @procedure.brouillon? - if @procedure.brouillon?
@ -44,7 +44,7 @@
.lien-demarche .lien-demarche
%h3 %h3
- if @procedure.brouillon_avec_lien? - if @procedure.brouillon?
Test et publication Test et publication
- else - else
Publication Publication
@ -54,16 +54,16 @@
- elsif @procedure.publiee? - elsif @procedure.publiee?
Cette démarche est <strong>publiée</strong>, certains éléments ne peuvent plus être modifiés. Cette démarche est <strong>publiée</strong>, certains éléments ne peuvent plus être modifiés.
Pour y accéder vous pouvez utiliser le lien : Pour y accéder vous pouvez utiliser le lien :
= link_to procedure_lien(@procedure), sanitize_url(procedure_lien(@procedure)), target: :blank, rel: :noopener = link_to @procedure_lien, sanitize_url(@procedure_lien), target: :blank, rel: :noopener
%br %br
%br %br
Attention, diffusez toujours le <strong>lien complet</strong> affiché ci-dessus, et non pas un lien générique vers demarches-simplifiees.fr. Ne dites pas non plus aux usagers de se rendre sur le site générique demarches-simplifiees.fr, donnez-leur toujours le lien complet. Attention, diffusez toujours le <strong>lien complet</strong> affiché ci-dessus, et non pas un lien générique vers demarches-simplifiees.fr. Ne dites pas non plus aux usagers de se rendre sur le site générique demarches-simplifiees.fr, donnez-leur toujours le lien complet.
- elsif @procedure.brouillon_avec_lien? - elsif @procedure.brouillon?
- if @procedure.missing_steps.empty? - if @procedure.missing_steps.empty?
%p %p
Cette démarche est actuellement <strong>en test</strong>, Cette démarche est actuellement <strong>en test</strong>,
pour y accéder vous pouvez utiliser le lien : pour y accéder vous pouvez utiliser le lien :
= link_to procedure_lien(@procedure), sanitize_url(procedure_lien(@procedure)), target: :blank, rel: :noopener = link_to @procedure_lien, sanitize_url(@procedure_lien), target: :blank, rel: :noopener
%p %p
Toute personne ayant la connaissance de ce lien pourra ainsi remplir des dossiers de test sur votre démarche. Toute personne ayant la connaissance de ce lien pourra ainsi remplir des dossiers de test sur votre démarche.
%br %br
@ -117,7 +117,7 @@
- if @procedure.missing_steps.include?(:service) - if @procedure.missing_steps.include?(:service)
%p.alert.alert-danger %p.alert.alert-danger
Vous devez renseigner les coordonnées de votre Service administratif avant de pouvoir publier votre démarche. Vous devez renseigner les coordonnées de votre Service administratif avant de pouvoir publier votre démarche.
= link_to 'Cliquez ici.', (current_administrateur.services.present? ? url_for(services_path(procedure_id: @procedure.id)) : url_for(new_service_path(procedure_id: @procedure.id))) = link_to 'Cliquez ici.', (@current_administrateur.services.present? ? url_for(services_path(procedure_id: @procedure.id)) : url_for(new_service_path(procedure_id: @procedure.id)))
- if @procedure.missing_steps.include?(:instructeurs) - if @procedure.missing_steps.include?(:instructeurs)
%p.alert.alert-danger %p.alert.alert-danger

View file

@ -5,7 +5,7 @@
%strong Logo %strong Logo
%p %p
- if field.data.logo? - if field.data.logo.attached?
= image_tag field.data.logo_url = image_tag field.data.logo_url
- else - else
Aucun Aucun
@ -20,7 +20,7 @@
%strong Signature %strong Signature
%p %p
- if field.data.signature? - if field.data.signature.attached?
= image_tag field.data.signature_url = image_tag field.data.signature_url
- else - else
Aucun Aucun

View file

@ -19,7 +19,7 @@
%li %li
%object %object
= link_to(instructeur_procedure_path(p, statut: 'a-suivre')) do = link_to(instructeur_procedure_path(p, statut: 'a-suivre')) do
- a_suivre_count = @dossiers_a_suivre_count_per_groupe_instructeur[p.defaut_groupe_instructeur.id] || 0 - a_suivre_count = @dossiers_a_suivre_count_per_procedure[p.id] || 0
.stats-number .stats-number
= a_suivre_count = a_suivre_count
.stats-legend .stats-legend
@ -29,7 +29,7 @@
= link_to(instructeur_procedure_path(p, statut: 'suivis')) do = link_to(instructeur_procedure_path(p, statut: 'suivis')) do
- if current_instructeur.notifications_per_procedure[p.id].present? - if current_instructeur.notifications_per_procedure[p.id].present?
%span.notifications{ 'aria-label': "notifications" } %span.notifications{ 'aria-label': "notifications" }
- followed_count = @followed_dossiers_count_per_groupe_instructeur[p.defaut_groupe_instructeur.id] || 0 - followed_count = @followed_dossiers_count_per_procedure[p.id] || 0
.stats-number .stats-number
= followed_count = followed_count
.stats-legend .stats-legend
@ -39,7 +39,7 @@
= link_to(instructeur_procedure_path(p, statut: 'traites')) do = link_to(instructeur_procedure_path(p, statut: 'traites')) do
- if current_instructeur.notifications_per_procedure(:termine)[p.id].present? - if current_instructeur.notifications_per_procedure(:termine)[p.id].present?
%span.notifications{ 'aria-label': "notifications" } %span.notifications{ 'aria-label': "notifications" }
- termines_count = @dossiers_termines_count_per_groupe_instructeur[p.defaut_groupe_instructeur.id] || 0 - termines_count = @dossiers_termines_count_per_procedure[p.id] || 0
.stats-number .stats-number
= termines_count = termines_count
.stats-legend .stats-legend
@ -47,7 +47,7 @@
%li %li
%object %object
= link_to(instructeur_procedure_path(p, statut: 'tous')) do = link_to(instructeur_procedure_path(p, statut: 'tous')) do
- dossier_count = @dossiers_count_per_groupe_instructeur[p.defaut_groupe_instructeur.id] || 0 - dossier_count = @dossiers_count_per_procedure[p.id] || 0
.stats-number .stats-number
= dossier_count = dossier_count
.stats-legend .stats-legend
@ -55,7 +55,7 @@
%li %li
%object %object
= link_to(instructeur_procedure_path(p, statut: 'archives')) do = link_to(instructeur_procedure_path(p, statut: 'archives')) do
- archived_count = @dossiers_archived_count_per_groupe_instructeur[p.defaut_groupe_instructeur.id] || 0 - archived_count = @dossiers_archived_count_per_procedure[p.id] || 0
.stats-number .stats-number
= archived_count = archived_count
.stats-legend .stats-legend

View file

@ -9,7 +9,9 @@
.procedure-header .procedure-header
%h1= procedure_libelle @procedure %h1= procedure_libelle @procedure
= link_to 'configurez vos notifications', email_notifications_instructeur_procedure_path(@procedure), class: 'notifications' = link_to 'gestion des notifications', email_notifications_instructeur_procedure_path(@procedure), class: 'header-link'
|
= link_to 'statistiques', stats_instructeur_procedure_path(@procedure), class: 'header-link'
%ul.tabs %ul.tabs

View file

@ -0,0 +1,32 @@
- title = "Statistiques · #{@procedure.libelle}"
- content_for(:title, title)
= render partial: 'new_administrateur/breadcrumbs',
locals: { steps: [link_to(@procedure.libelle, procedure_path(@procedure)),
'Statistiques'] }
.statistiques
%h1.new-h1= title
.stat-cards
- if @usual_traitement_time.present?
.stat-card.big-number-card
%span.big-number-card-title TEMPS DE TRAITEMENT USUEL
%span.big-number-card-number
= distance_of_time_in_words(@usual_traitement_time)
%span.big-number-card-detail
90% des demandes du mois dernier ont été traitées en moins de #{distance_of_time_in_words(@usual_traitement_time)}.
.stat-cards
.stat-card.stat-card-half.pull-left
%span.stat-card-title AVANCÉE DES DOSSIERS
.chart-container
.chart
= area_chart @dossiers_funnel
.stat-card.stat-card-half.pull-left
%span.stat-card-title TAUX DACCEPTATION
.chart-container
.chart
- colors = %w(#C3D9FF #0069CC #1C7EC9) # from _colors.scss
= pie_chart @termines_states, colors: colors

View file

@ -15,7 +15,7 @@
- if nav_bar_profile == :instructeur && instructeur_signed_in? - if nav_bar_profile == :instructeur && instructeur_signed_in?
- current_url = request.path_info - current_url = request.path_info
%ul.header-tabs %ul.header-tabs
- if current_instructeur.visible_procedures.count > 0 - if current_instructeur.procedures.count > 0
%li %li
= active_link_to "Démarches", instructeur_procedures_path, active: :inclusive, class: 'tab-link' = active_link_to "Démarches", instructeur_procedures_path, active: :inclusive, class: 'tab-link'
- if current_instructeur.avis.count > 0 - if current_instructeur.avis.count > 0

View file

@ -11,7 +11,7 @@
#procedure-list #procedure-list
%a#onglet-infos{ href: url_for(admin_procedure_path(@procedure)) } %a#onglet-infos{ href: url_for(admin_procedure_path(@procedure)) }
.procedure-list-element{ class: ('active' if active == 'Informations') } .procedure-list-element{ class: ('active' if active == 'Informations') }
- if @procedure.brouillon_avec_lien? - if @procedure.brouillon?
Test et publication Test et publication
- else - else
Publication Publication

View file

@ -32,7 +32,7 @@
locals: { champ: champ, form: champ_form } locals: { champ: champ, form: champ_form }
- if !apercu - if !apercu
.send-wrapper .send-dossier-actions-bar
- if dossier.brouillon? - if dossier.brouillon?
= f.button 'Enregistrer le brouillon', = f.button 'Enregistrer le brouillon',
formnovalidate: true, formnovalidate: true,

View file

@ -163,6 +163,8 @@ fr:
procedure: procedure:
attributes: attributes:
path: path:
taken: est déjà utilisé par une démarche. Vous ne pouvez pas lutiliser car il appartient à un autre administrateur.
taken_can_be_claimed: est identique à celui dune autre de vos démarches publiées. Si vous publiez cette démarche, lancienne sera archivée et ne sera plus accessible au public.
invalid: n'est pas valide. Il doit comporter au moins 3 caractères, au plus 50 caractères et seuls les caractères a-z, 0-9, '_' et '-' sont autorisés. invalid: n'est pas valide. Il doit comporter au moins 3 caractères, au plus 50 caractères et seuls les caractères a-z, 0-9, '_' et '-' sont autorisés.
errors: errors:

View file

@ -159,8 +159,6 @@ Rails.application.routes.draw do
patch 'activate' => '/administrateurs/activate#create' patch 'activate' => '/administrateurs/activate#create'
get 'procedures/archived' => 'procedures#archived' get 'procedures/archived' => 'procedures#archived'
get 'procedures/draft' => 'procedures#draft' get 'procedures/draft' => 'procedures#draft'
get 'procedures/path_list' => 'procedures#path_list'
get 'procedures/available' => 'procedures#check_availability'
resources :procedures do resources :procedures do
collection do collection do
@ -176,6 +174,7 @@ Rails.application.routes.draw do
resources :mail_templates, only: [:index, :edit, :update] resources :mail_templates, only: [:index, :edit, :update]
put 'archive' => 'procedures#archive', as: :archive put 'archive' => 'procedures#archive', as: :archive
get 'publish_validate' => 'procedures#publish_validate', as: :publish_validate
put 'publish' => 'procedures#publish', as: :publish put 'publish' => 'procedures#publish', as: :publish
post 'transfer' => 'procedures#transfer', as: :transfer post 'transfer' => 'procedures#transfer', as: :transfer
put 'clone' => 'procedures#clone', as: :clone put 'clone' => 'procedures#clone', as: :clone
@ -286,6 +285,7 @@ Rails.application.routes.draw do
post 'add_filter' post 'add_filter'
get 'remove_filter' => 'procedures#remove_filter', as: 'remove_filter' get 'remove_filter' => 'procedures#remove_filter', as: 'remove_filter'
get 'download_dossiers' get 'download_dossiers'
get 'stats'
get 'email_notifications' get 'email_notifications'
patch 'update_email_notifications' patch 'update_email_notifications'

View file

@ -0,0 +1,8 @@
class DropFlipflopFeatures < ActiveRecord::Migration[5.2]
def change
remove_column :administrateurs, :features
remove_column :instructeurs, :features
drop_table :flipflop_features
end
end

View file

@ -0,0 +1,8 @@
class RemoveCarrierwaveColumns < ActiveRecord::Migration[5.2]
def change
remove_columns :procedures, :logo, :logo_secure_token
remove_columns :commentaires, :file, :piece_justificative_id
remove_columns :attestations, :pdf, :content_secure_token
remove_columns :attestation_templates, :logo, :logo_secure_token, :signature, :signature_secure_token
end
end

View file

@ -0,0 +1,31 @@
class RemoveDeviseColumns < ActiveRecord::Migration[5.2]
def change
remove_columns :administrateurs,
:encrypted_password,
:reset_password_token,
:reset_password_sent_at,
:remember_created_at,
:sign_in_count,
:current_sign_in_at,
:last_sign_in_at,
:current_sign_in_ip,
:last_sign_in_ip,
:failed_attempts,
:unlock_token,
:locked_at
remove_columns :instructeurs,
:encrypted_password,
:reset_password_token,
:reset_password_sent_at,
:remember_created_at,
:sign_in_count,
:current_sign_in_at,
:last_sign_in_at,
:current_sign_in_ip,
:last_sign_in_ip,
:failed_attempts,
:unlock_token,
:locked_at
end
end

View file

@ -0,0 +1,6 @@
class RemoveUnusedColumns < ActiveRecord::Migration[5.2]
def change
remove_column :dossiers, :json_latlngs
remove_column :services, :siret
end
end

View file

@ -0,0 +1,6 @@
class MakePathNonnull < ActiveRecord::Migration[5.2]
def change
change_column_null :procedures, :path, false
add_index :procedures, [:path, :archived_at, :hidden_at], unique: true
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_08_28_073736) do ActiveRecord::Schema.define(version: 2019_09_17_151652) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -49,26 +49,11 @@ ActiveRecord::Schema.define(version: 2019_08_28_073736) do
create_table "administrateurs", id: :serial, force: :cascade do |t| create_table "administrateurs", id: :serial, force: :cascade do |t|
t.string "email", default: "", null: false t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.boolean "active", default: false t.boolean "active", default: false
t.jsonb "features", default: {}, null: false
t.string "encrypted_token" t.string "encrypted_token"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.index ["email"], name: "index_administrateurs_on_email", unique: true t.index ["email"], name: "index_administrateurs_on_email", unique: true
t.index ["reset_password_token"], name: "index_administrateurs_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_administrateurs_on_unlock_token", unique: true
end end
create_table "administrateurs_instructeurs", id: false, force: :cascade do |t| create_table "administrateurs_instructeurs", id: false, force: :cascade do |t|
@ -130,24 +115,18 @@ ActiveRecord::Schema.define(version: 2019_08_28_073736) do
t.text "title" t.text "title"
t.text "body" t.text "body"
t.text "footer" t.text "footer"
t.string "logo"
t.string "signature"
t.boolean "activated" t.boolean "activated"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "procedure_id" t.integer "procedure_id"
t.string "logo_secure_token"
t.string "signature_secure_token"
t.index ["procedure_id"], name: "index_attestation_templates_on_procedure_id", unique: true t.index ["procedure_id"], name: "index_attestation_templates_on_procedure_id", unique: true
end end
create_table "attestations", id: :serial, force: :cascade do |t| create_table "attestations", id: :serial, force: :cascade do |t|
t.string "pdf"
t.string "title" t.string "title"
t.integer "dossier_id", null: false t.integer "dossier_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.string "content_secure_token"
t.index ["dossier_id"], name: "index_attestations_on_dossier_id" t.index ["dossier_id"], name: "index_attestations_on_dossier_id"
end end
@ -205,8 +184,6 @@ ActiveRecord::Schema.define(version: 2019_08_28_073736) do
t.string "body" t.string "body"
t.integer "dossier_id" t.integer "dossier_id"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "piece_justificative_id"
t.string "file"
t.bigint "user_id" t.bigint "user_id"
t.bigint "instructeur_id" t.bigint "instructeur_id"
t.index ["dossier_id"], name: "index_commentaires_on_dossier_id" t.index ["dossier_id"], name: "index_commentaires_on_dossier_id"
@ -266,7 +243,6 @@ ActiveRecord::Schema.define(version: 2019_08_28_073736) do
t.datetime "updated_at" t.datetime "updated_at"
t.string "state" t.string "state"
t.integer "user_id" t.integer "user_id"
t.text "json_latlngs"
t.boolean "archived", default: false t.boolean "archived", default: false
t.datetime "en_construction_at" t.datetime "en_construction_at"
t.datetime "en_instruction_at" t.datetime "en_instruction_at"
@ -348,13 +324,6 @@ ActiveRecord::Schema.define(version: 2019_08_28_073736) do
t.index ["user_id"], name: "index_feedbacks_on_user_id" t.index ["user_id"], name: "index_feedbacks_on_user_id"
end end
create_table "flipflop_features", force: :cascade do |t|
t.string "key", null: false
t.boolean "enabled", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "flipper_features", force: :cascade do |t| create_table "flipper_features", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
@ -443,26 +412,11 @@ ActiveRecord::Schema.define(version: 2019_08_28_073736) do
create_table "instructeurs", id: :serial, force: :cascade do |t| create_table "instructeurs", id: :serial, force: :cascade do |t|
t.string "email", default: "", null: false t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.text "encrypted_login_token" t.text "encrypted_login_token"
t.datetime "login_token_created_at" t.datetime "login_token_created_at"
t.jsonb "features", default: {"enable_email_login_token"=>true}, null: false
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.index ["email"], name: "index_instructeurs_on_email", unique: true t.index ["email"], name: "index_instructeurs_on_email", unique: true
t.index ["reset_password_token"], name: "index_instructeurs_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_instructeurs_on_unlock_token", unique: true
end end
create_table "invites", id: :serial, force: :cascade do |t| create_table "invites", id: :serial, force: :cascade do |t|
@ -505,9 +459,7 @@ ActiveRecord::Schema.define(version: 2019_08_28_073736) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.boolean "euro_flag", default: false t.boolean "euro_flag", default: false
t.string "logo"
t.boolean "cerfa_flag", default: false t.boolean "cerfa_flag", default: false
t.string "logo_secure_token"
t.string "lien_site_web" t.string "lien_site_web"
t.string "lien_notice" t.string "lien_notice"
t.boolean "for_individual", default: false t.boolean "for_individual", default: false
@ -528,12 +480,13 @@ ActiveRecord::Schema.define(version: 2019_08_28_073736) do
t.string "cadre_juridique" t.string "cadre_juridique"
t.boolean "juridique_required", default: true t.boolean "juridique_required", default: true
t.boolean "durees_conservation_required", default: true t.boolean "durees_conservation_required", default: true
t.string "path" t.string "path", null: false
t.string "declarative_with_state" t.string "declarative_with_state"
t.text "monavis_embed" t.text "monavis_embed"
t.index ["declarative_with_state"], name: "index_procedures_on_declarative_with_state" t.index ["declarative_with_state"], name: "index_procedures_on_declarative_with_state"
t.index ["hidden_at"], name: "index_procedures_on_hidden_at" t.index ["hidden_at"], name: "index_procedures_on_hidden_at"
t.index ["parent_procedure_id"], name: "index_procedures_on_parent_procedure_id" t.index ["parent_procedure_id"], name: "index_procedures_on_parent_procedure_id"
t.index ["path", "archived_at", "hidden_at"], name: "index_procedures_on_path_and_archived_at_and_hidden_at", unique: true
t.index ["service_id"], name: "index_procedures_on_service_id" t.index ["service_id"], name: "index_procedures_on_service_id"
end end
@ -566,7 +519,6 @@ ActiveRecord::Schema.define(version: 2019_08_28_073736) do
t.string "telephone" t.string "telephone"
t.text "horaires" t.text "horaires"
t.text "adresse" t.text "adresse"
t.string "siret"
t.index ["administrateur_id", "nom"], name: "index_services_on_administrateur_id_and_nom", unique: true t.index ["administrateur_id", "nom"], name: "index_services_on_administrateur_id_and_nom", unique: true
t.index ["administrateur_id"], name: "index_services_on_administrateur_id" t.index ["administrateur_id"], name: "index_services_on_administrateur_id"
end end

View file

@ -1,38 +0,0 @@
namespace :'2019_05_29_migrate_commentaire_pj' do
task run: :environment do
commentaires = Commentaire.where
.not(file: nil)
.left_joins(:piece_jointe_attachment)
.where('active_storage_attachments.id IS NULL')
.order(:created_at)
limit = ENV['LIMIT']
if limit
commentaires.limit!(limit.to_i)
end
progress = ProgressReport.new(commentaires.count)
commentaires.find_each do |commentaire|
if commentaire.file.present?
dossier = Dossier.unscope(where: :hidden_at).find(commentaire.dossier_id)
uri = URI.parse(URI.escape(commentaire.file_url))
response = Typhoeus.get(uri)
if response.success?
filename = commentaire.file.filename || commentaire.file_identifier
updated_at = commentaire.updated_at
dossier_updated_at = dossier.updated_at
commentaire.piece_jointe.attach(
io: StringIO.new(response.body),
filename: filename,
content_type: commentaire.file.content_type,
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
commentaire.update_column(:updated_at, updated_at)
dossier.update_column(:updated_at, dossier_updated_at)
end
end
progress.inc
end
progress.finish
end
end

View file

@ -30,13 +30,13 @@ describe Admin::AttestationTemplatesController, type: :controller do
context 'with an interlaced png' do context 'with an interlaced png' do
let(:upload_params) { { logo: interlaced_logo } } let(:upload_params) { { logo: interlaced_logo } }
it { expect(assigns(:logo).read).to eq(uninterlaced_logo.read) } it { expect(assigns(:attestation)[:logo].read).to eq(uninterlaced_logo.read) }
end end
context 'if an attestation template does not exist on the procedure' do context 'if an attestation template does not exist on the procedure' do
let(:attestation_template) { nil } let(:attestation_template) { nil }
it { expect(subject.status).to eq(200) } it { expect(subject.status).to eq(200) }
it { expect(assigns).to include(upload_params.stringify_keys) } it { expect(assigns(:attestation)).to include(upload_params) }
end end
context 'if an attestation template exists on the procedure' do context 'if an attestation template exists on the procedure' do
@ -48,18 +48,18 @@ describe Admin::AttestationTemplatesController, type: :controller do
end end
it { expect(subject.status).to eq(200) } it { expect(subject.status).to eq(200) }
it { expect(assigns).to include(upload_params.stringify_keys) } it { expect(assigns(:attestation)).to include(upload_params) }
it { expect(assigns[:created_at]).to eq(Time.zone.now) } it { expect(assigns(:attestation)[:created_at]).to eq(Time.zone.now) }
it { expect(assigns(:logo).download).to eq(logo2.read) } it { expect(assigns(:attestation)[:logo].download).to eq(logo2.read) }
it { expect(assigns(:signature).download).to eq(signature2.read) } it { expect(assigns(:attestation)[:signature].download).to eq(signature2.read) }
end end
context 'with empty logo' do context 'with empty logo' do
it { expect(subject.status).to eq(200) } it { expect(subject.status).to eq(200) }
it { expect(assigns).to include(upload_params.stringify_keys) } it { expect(assigns(:attestation)).to include(upload_params) }
it { expect(assigns[:created_at]).to eq(Time.zone.now) } it { expect(assigns(:attestation)[:created_at]).to eq(Time.zone.now) }
it { expect(assigns(:logo)).to eq(nil) } it { expect(assigns(:attestation)[:logo]).to eq(nil) }
it { expect(assigns(:signature)).to eq(nil) } it { expect(assigns(:attestation)[:signature]).to eq(nil) }
end end
end end
end end

View file

@ -340,7 +340,7 @@ describe Admin::ProceduresController, type: :controller do
context 'when admin is the owner of the procedure' do context 'when admin is the owner of the procedure' do
before do before do
put :publish, params: { procedure_id: procedure.id, path: path, lien_site_web: lien_site_web } put :publish, format: :js, params: { procedure_id: procedure.id, path: path, lien_site_web: lien_site_web }
procedure.reload procedure.reload
procedure2.reload procedure2.reload
end end
@ -383,7 +383,6 @@ describe Admin::ProceduresController, type: :controller do
expect(procedure.publiee?).to be_falsey expect(procedure.publiee?).to be_falsey
expect(procedure.path).not_to match(path) expect(procedure.path).not_to match(path)
expect(procedure.lien_site_web).to match(lien_site_web) expect(procedure.lien_site_web).to match(lien_site_web)
expect(response.status).to eq 200
end end
it 'previous procedure remains published' do it 'previous procedure remains published' do
@ -401,8 +400,6 @@ describe Admin::ProceduresController, type: :controller do
expect(procedure.publiee?).to be_falsey expect(procedure.publiee?).to be_falsey
expect(procedure.path).not_to match(path) expect(procedure.path).not_to match(path)
expect(procedure.lien_site_web).to match(lien_site_web) expect(procedure.lien_site_web).to match(lien_site_web)
expect(response).to redirect_to :admin_procedures
expect(flash[:alert]).to have_content 'Lien de la démarche invalide'
end end
end end
end end
@ -419,8 +416,7 @@ describe Admin::ProceduresController, type: :controller do
end end
it 'fails' do it 'fails' do
expect(response).to redirect_to :admin_procedures expect(response).to have_http_status(404)
expect(flash[:alert]).to have_content 'Démarche inexistante'
end end
end end
@ -573,54 +569,6 @@ describe Admin::ProceduresController, type: :controller do
end end
end end
describe 'GET #path_list' do
let!(:procedure) { create(:procedure, :published, administrateur: admin) }
let(:admin2) { create(:administrateur) }
let!(:procedure2) { create(:procedure, :published, administrateur: admin2) }
let!(:procedure3) { create(:procedure, :published, administrateur: admin2) }
subject { get :path_list }
let(:body) { JSON.parse(response.body) }
describe 'when no params' do
before do
subject
end
it { expect(response.status).to eq(200) }
it { expect(body.size).to eq(3) }
it { expect(body.first['label']).to eq(procedure.path) }
it { expect(body.first['mine']).to be_truthy }
it { expect(body.second['label']).to eq(procedure2.path) }
it { expect(body.second['mine']).to be_falsy }
end
context 'filtered' do
before do
subject
end
subject { get :path_list, params: { request: URI.encode(procedure2.path) } }
it { expect(response.status).to eq(200) }
it { expect(body.size).to eq(1) }
it { expect(body.first['label']).to eq(procedure2.path) }
it { expect(body.first['mine']).to be_falsy }
end
context 'when procedure is archived' do
let!(:procedure3) { create(:procedure, :archived, administrateur: admin2) }
before do
subject
end
it 'do not return on the json' do
expect(body.size).to eq(2)
end
end
end
describe 'POST #transfer' do describe 'POST #transfer' do
let!(:procedure) { create :procedure, :with_service, administrateur: admin } let!(:procedure) { create :procedure, :with_service, administrateur: admin }
@ -716,63 +664,6 @@ describe Admin::ProceduresController, type: :controller do
end end
end end
describe "GET #check_availability" do
render_views
let(:procedure) { create(:procedure, :with_path, administrateur: admin) }
let(:params) {
{
procedure: {
path: path,
id: procedure.id
}
}
}
let(:path) { generate(:published_path) }
before do
get :check_availability, params: params, format: 'js'
end
context 'self path' do
let(:path) { procedure.path }
it { expect(response.body).to include("innerHTML = ''") }
end
context 'available path' do
it { expect(response.body).to include("innerHTML = ''") }
end
context 'my path (brouillon)' do
let(:procedure_owned) { create(:procedure, :with_path, administrateur: admin) }
let(:path) { procedure_owned.path }
it {
expect(response.body).to include('Une démarche en test existe déjà avec ce lien.')
}
end
context 'my path' do
let(:procedure_owned) { create(:procedure, :published, administrateur: admin) }
let(:path) { procedure_owned.path }
it {
expect(response.body).to include('Ce lien est déjà utilisé par une de vos démarche.')
expect(response.body).to include('Si vous voulez lutiliser, lancienne démarche sera archivée')
}
end
context 'unavailable path' do
let(:procedure_not_owned) { create(:procedure, :with_path, administrateur: create(:administrateur)) }
let(:path) { procedure_not_owned.path }
it {
expect(response.body).to include('Ce lien est déjà utilisé par une démarche.')
expect(response.body).to include('Vous ne pouvez pas lutiliser car il appartient à un autre administrateur.')
}
end
end
describe 'PATCH #monavis' do describe 'PATCH #monavis' do
let!(:procedure) { create(:procedure, administrateur: admin) } let!(:procedure) { create(:procedure, administrateur: admin) }
let(:procedure_params) { let(:procedure_params) {
@ -796,10 +687,11 @@ describe Admin::ProceduresController, type: :controller do
patch :update_monavis, params: { procedure_id: procedure.id, procedure: procedure_params } patch :update_monavis, params: { procedure_id: procedure.id, procedure: procedure_params }
procedure.reload procedure.reload
end end
let(:monavis_embed) { let(:monavis_embed) {
<<-MSG <<-MSG
<a href="https://monavis.numerique.gouv.fr/Demarches/123?&view-mode=formulaire-avis&nd_mode=en-ligne-enti%C3%A8rement&nd_source=button&key=cd4a872d475e4045666057f"> <a href="https://voxusagers.numerique.gouv.fr/Demarches/2136?&view-mode=formulaire-avis&nd_mode=en-ligne-enti%C3%A8rement&nd_source=button&key=e93e77cfd9bf7cce9d10f7a10be55730">
<img src="https://monavis.numerique.gouv.fr/monavis-static/bouton-blanc.png" alt="Je donne mon avis" title="Je donne mon avis sur cette démarche" /> <img src="https://voxusagers.numerique.gouv.fr/static/bouton-bleu.svg" alt="Je donne mon avis" title="Je donne mon avis sur cette démarche" />
</a> </a>
MSG MSG
} }
@ -820,6 +712,24 @@ describe Admin::ProceduresController, type: :controller do
it { expect(response.body).to include "MonAvis" } it { expect(response.body).to include "MonAvis" }
end end
context 'when the embed code is valid with the original format' do
let(:monavis_embed) {
<<-MSG
<a href="https://monavis.numerique.gouv.fr/Demarches/123?&view-mode=formulaire-avis&nd_mode=en-ligne-enti%C3%A8rement&nd_source=button&key=cd4a872d475e4045666057f">
<img src="https://monavis.numerique.gouv.fr/monavis-static/bouton-blanc.png" alt="Je donne mon avis" title="Je donne mon avis sur cette démarche" />
</a>
MSG
}
describe 'the monavis field is updated' do
subject { procedure }
it { expect(subject.monavis_embed).to eq(monavis_embed) }
end
it { expect(flash[:notice]).to be_present }
it { expect(response.body).to include "MonAvis" }
end
context 'when the embed code is not valid' do context 'when the embed code is not valid' do
let(:monavis_embed) { 'invalid embed code' } let(:monavis_embed) { 'invalid embed code' }

View file

@ -125,11 +125,11 @@ describe Instructeurs::ProceduresController, type: :controller do
let(:state) { Dossier.states.fetch(:brouillon) } let(:state) { Dossier.states.fetch(:brouillon) }
before { subject } before { subject }
it { expect(assigns(:dossiers_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(nil) } it { expect(assigns(:dossiers_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:dossiers_a_suivre_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(nil) } it { expect(assigns(:dossiers_a_suivre_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:dossiers_archived_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(nil) } it { expect(assigns(:dossiers_archived_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:followed_dossiers_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(nil) } it { expect(assigns(:followed_dossiers_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:dossiers_termines_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(nil) } it { expect(assigns(:dossiers_termines_count_per_procedure)[procedure.id]).to eq(nil) }
end end
context "with not draft state on multiple procedures" do context "with not draft state on multiple procedures" do
@ -149,17 +149,67 @@ describe Instructeurs::ProceduresController, type: :controller do
subject subject
end end
it { expect(assigns(:dossiers_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(3) } it { expect(assigns(:dossiers_count_per_procedure)[procedure.id]).to eq(3) }
it { expect(assigns(:dossiers_a_suivre_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(3) } it { expect(assigns(:dossiers_a_suivre_count_per_procedure)[procedure.id]).to eq(3) }
it { expect(assigns(:followed_dossiers_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(nil) } it { expect(assigns(:followed_dossiers_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:dossiers_archived_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(1) } it { expect(assigns(:dossiers_archived_count_per_procedure)[procedure.id]).to eq(1) }
it { expect(assigns(:dossiers_termines_count_per_groupe_instructeur)[procedure.defaut_groupe_instructeur.id]).to eq(nil) } it { expect(assigns(:dossiers_termines_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:dossiers_count_per_groupe_instructeur)[procedure2.defaut_groupe_instructeur.id]).to eq(3) } it { expect(assigns(:dossiers_count_per_procedure)[procedure2.id]).to eq(3) }
it { expect(assigns(:dossiers_a_suivre_count_per_groupe_instructeur)[procedure2.defaut_groupe_instructeur.id]).to eq(nil) } it { expect(assigns(:dossiers_a_suivre_count_per_procedure)[procedure2.id]).to eq(nil) }
it { expect(assigns(:followed_dossiers_count_per_groupe_instructeur)[procedure2.defaut_groupe_instructeur.id]).to eq(1) } it { expect(assigns(:followed_dossiers_count_per_procedure)[procedure2.id]).to eq(1) }
it { expect(assigns(:dossiers_archived_count_per_groupe_instructeur)[procedure2.defaut_groupe_instructeur.id]).to eq(nil) } it { expect(assigns(:dossiers_archived_count_per_procedure)[procedure2.id]).to eq(nil) }
it { expect(assigns(:dossiers_termines_count_per_groupe_instructeur)[procedure2.defaut_groupe_instructeur.id]).to eq(1) } it { expect(assigns(:dossiers_termines_count_per_procedure)[procedure2.id]).to eq(1) }
end
end
context "with a routed procedure" do
let!(:procedure) { create(:procedure, :published) }
let!(:gi_p1_1) { procedure.defaut_groupe_instructeur }
let!(:gi_p1_2) { GroupeInstructeur.create(label: '2', procedure: procedure) }
context 'with multiple dossiers en construction on each group' do
before do
alternate_gis = 0.upto(20).map { |i| i.even? ? gi_p1_1 : gi_p1_2 }
alternate_gis.take(4).each { |gi| create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction), groupe_instructeur: gi) }
alternate_gis.take(6).each do |gi|
instructeur.followed_dossiers << create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction), groupe_instructeur: gi)
end
alternate_gis.take(10).each { |gi| create(:dossier, procedure: procedure, state: Dossier.states.fetch(:sans_suite), groupe_instructeur: gi) }
alternate_gis.take(14).each { |gi| create(:dossier, procedure: procedure, state: Dossier.states.fetch(:sans_suite), archived: true, groupe_instructeur: gi) }
end
context 'when an instructeur belongs to the 2 gi' do
before do
instructeur.groupe_instructeurs << gi_p1_1 << gi_p1_2
subject
end
it { expect(assigns(:dossiers_a_suivre_count_per_procedure)[procedure.id]).to eq(4) }
it { expect(assigns(:followed_dossiers_count_per_procedure)[procedure.id]).to eq(6) }
it { expect(assigns(:dossiers_termines_count_per_procedure)[procedure.id]).to eq(10) }
it { expect(assigns(:dossiers_count_per_procedure)[procedure.id]).to eq(4 + 6 + 10) }
it { expect(assigns(:dossiers_archived_count_per_procedure)[procedure.id]).to eq(14) }
end
context 'when an instructeur only belongs to one of them gi' do
before do
instructeur.groupe_instructeurs << gi_p1_1
subject
end
it { expect(assigns(:dossiers_a_suivre_count_per_procedure)[procedure.id]).to eq(2) }
# An instructeur cannot follow a dossier which belongs to another groupe
it { expect(assigns(:followed_dossiers_count_per_procedure)[procedure.id]).to eq(3) }
it { expect(assigns(:dossiers_termines_count_per_procedure)[procedure.id]).to eq(5) }
it { expect(assigns(:dossiers_count_per_procedure)[procedure.id]).to eq(2 + 3 + 5) }
it { expect(assigns(:dossiers_archived_count_per_procedure)[procedure.id]).to eq(7) }
end
end end
end end
end end

View file

@ -14,8 +14,11 @@ FactoryBot.define do
else else
procedure = create(:procedure, :published, :with_type_de_champ, :with_type_de_champ_private) procedure = create(:procedure, :published, :with_type_de_champ, :with_type_de_champ_private)
end end
if dossier.groupe_instructeur.nil?
dossier.groupe_instructeur = procedure.defaut_groupe_instructeur dossier.groupe_instructeur = procedure.defaut_groupe_instructeur
end end
end
trait :with_entreprise do trait :with_entreprise do
after(:build) do |dossier, _evaluator| after(:build) do |dossier, _evaluator|

View file

@ -11,6 +11,7 @@ FactoryBot.define do
duree_conservation_dossiers_hors_ds { 6 } duree_conservation_dossiers_hors_ds { 6 }
ask_birthday { false } ask_birthday { false }
lien_site_web { "https://mon-site.gouv" } lien_site_web { "https://mon-site.gouv" }
path { SecureRandom.uuid }
transient do transient do
administrateur {} administrateur {}
@ -48,7 +49,8 @@ FactoryBot.define do
after(:build) do |procedure, _evaluator| after(:build) do |procedure, _evaluator|
procedure.for_individual = true procedure.for_individual = true
procedure.types_de_champ << create(:type_de_champ, libelle: 'Texte obligatoire', mandatory: true) procedure.types_de_champ << create(:type_de_champ, libelle: 'Texte obligatoire', mandatory: true)
procedure.publish!(procedure.administrateurs.first, generate(:published_path), procedure.lien_site_web) procedure.path = generate(:published_path)
procedure.publish!
end end
end end
@ -147,13 +149,15 @@ FactoryBot.define do
trait :published do trait :published do
after(:build) do |procedure, _evaluator| after(:build) do |procedure, _evaluator|
procedure.publish!(procedure.administrateurs.first, generate(:published_path), procedure.lien_site_web) procedure.path = generate(:published_path)
procedure.publish!
end end
end end
trait :archived do trait :archived do
after(:build) do |procedure, _evaluator| after(:build) do |procedure, _evaluator|
procedure.publish!(procedure.administrateurs.first, generate(:published_path), procedure.lien_site_web) procedure.path = generate(:published_path)
procedure.publish!
procedure.archive! procedure.archive!
end end
end end
@ -162,14 +166,16 @@ FactoryBot.define do
# For now the behavior is the same than :archived # For now the behavior is the same than :archived
# (it may be different in the future though) # (it may be different in the future though)
after(:build) do |procedure, _evaluator| after(:build) do |procedure, _evaluator|
procedure.publish!(procedure.administrateurs.first, generate(:published_path), procedure.lien_site_web) procedure.path = generate(:published_path)
procedure.publish!
procedure.archive! procedure.archive!
end end
end end
trait :hidden do trait :hidden do
after(:build) do |procedure, _evaluator| after(:build) do |procedure, _evaluator|
procedure.publish!(procedure.administrateurs.first, generate(:published_path), procedure.lien_site_web) procedure.path = generate(:published_path)
procedure.publish!
procedure.hide! procedure.hide!
end end
end end

View file

@ -7,23 +7,39 @@ feature 'As an administrateur I wanna clone a procedure', js: true do
let(:administrateur) { create(:administrateur) } let(:administrateur) { create(:administrateur) }
before do before do
create :procedure, :with_service,
aasm_state: :publiee, published_at: Time.zone.now,
administrateurs: [administrateur],
libelle: 'libellé de la procédure',
path: 'libelle-de-la-procedure'
login_as administrateur.user, scope: :user login_as administrateur.user, scope: :user
visit new_from_existing_admin_procedures_path
end end
context 'Cloning procedure' do context 'Cloning a procedure owned by the current admin' do
before 'Create procedure' do scenario do
page.find_by_id('from-scratch').click visit admin_procedures_path
fill_in_dummy_procedure_details
page.find_by_id('save-procedure').click
end
scenario 'Cloning' do
visit admin_procedures_draft_path
expect(page.find_by_id('procedures')['data-item-count']).to eq('1') expect(page.find_by_id('procedures')['data-item-count']).to eq('1')
page.all('.clone-btn').first.click page.all('.clone-btn').first.click
visit admin_procedures_draft_path visit admin_procedures_draft_path
expect(page.find_by_id('procedures')['data-item-count']).to eq('2') expect(page.find_by_id('procedures')['data-item-count']).to eq('1')
click_on Procedure.last.libelle
expect(page).to have_current_path(admin_procedure_path(Procedure.last))
find('#publish-procedure').click
within '#publish-modal' do
expect(find_field('procedure_path').value).to eq 'libelle-de-la-procedure'
expect(page).to have_text('ancienne sera archivée')
fill_in 'lien_site_web', with: 'http://some.website'
click_on 'publish'
end
page.refresh
visit admin_procedures_archived_path
expect(page.find_by_id('procedures')['data-item-count']).to eq('1')
visit admin_procedures_draft_path
expect(page.find_by_id('procedures')['data-item-count']).to eq('0')
end end
end end
end end

View file

@ -98,11 +98,16 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
click_on Procedure.last.libelle click_on Procedure.last.libelle
expect(page).to have_current_path(admin_procedure_path(Procedure.last)) expect(page).to have_current_path(admin_procedure_path(Procedure.last))
expect(page).to have_content('en test')
# Only check the path even though the link is the complete URL
# (Capybara runs the app on an arbitrary host/port.)
expect(page).to have_link(nil, href: /#{commencer_test_path(Procedure.last.path)}/)
expect(page).to have_selector('#publish-procedure', visible: true) expect(page).to have_selector('#publish-procedure', visible: true)
find('#publish-procedure').click find('#publish-procedure').click
within '#publish-modal' do within '#publish-modal' do
expect(page).to have_field('procedure_path') expect(find_field('procedure_path').value).to eq 'libelle-de-la-procedure'
fill_in 'lien_site_web', with: 'http://some.website' fill_in 'lien_site_web', with: 'http://some.website'
click_on 'publish' click_on 'publish'
end end

View file

@ -2,17 +2,27 @@ require 'spec_helper'
feature 'procedure locked' do feature 'procedure locked' do
let(:administrateur) { create(:administrateur) } let(:administrateur) { create(:administrateur) }
let (:published_at) { nil }
let(:procedure) { create(:procedure, administrateur: administrateur, published_at: published_at) }
before do before do
login_as administrateur, scope: :administrateur login_as administrateur.user, scope: :user
visit admin_procedure_path(procedure) visit admin_procedure_path(procedure)
end end
context 'when procedure is not published' do context 'when procedure is not published' do
let(:procedure) { create(:procedure, administrateur: administrateur) }
scenario 'info label is not present' do scenario 'info label is not present' do
expect(page).not_to have_content('Cette démarche a été publiée, certains éléments ne peuvent plus être modifiés') expect(page).to have_content('Test et publication')
expect(page).not_to have_content('Cette démarche est publiée, certains éléments ne peuvent plus être modifiés.')
end
end
context 'when procedure is published' do
let(:procedure) { create(:procedure, :published, administrateur: administrateur) }
scenario 'info label is present' do
expect(page).to have_content('Publication')
expect(page).to have_content('Cette démarche est publiée, certains éléments ne peuvent plus être modifiés.')
end end
end end
end end

View file

@ -159,9 +159,11 @@ describe AttestationTemplate, type: :model do
.update(value: 'libelle2') .update(value: 'libelle2')
end end
it { expect(view_args[:title]).to eq('title libelle1') } it do
it { expect(view_args[:body]).to eq('body libelle2') } expect(view_args[:attestation][:title]).to eq('title libelle1')
it { expect(attestation.title).to eq('title libelle1') } expect(view_args[:attestation][:body]).to eq('body libelle2')
expect(attestation.title).to eq('title libelle1')
end
end end
end end
end end

View file

@ -12,31 +12,6 @@ describe Instructeur, type: :model do
assign(procedure_2) assign(procedure_2)
end end
describe '#visible_procedures' do
let(:procedure_not_assigned) { create :procedure, administrateur: admin }
let(:procedure_with_default_path) { create :procedure, administrateur: admin }
let(:procedure_with_custom_path) { create :procedure, :with_path, administrateur: admin }
let(:procedure_archived_manually) { create :procedure, :archived, administrateur: admin }
let(:procedure_archived_automatically) { create :procedure, :archived_automatically, administrateur: admin }
before do
assign(procedure_with_default_path)
assign(procedure_with_custom_path)
assign(procedure_archived_manually)
assign(procedure_archived_automatically)
end
subject { instructeur.visible_procedures }
it do
expect(subject).not_to include(procedure_not_assigned)
expect(subject).to include(procedure_with_default_path)
expect(subject).to include(procedure_with_custom_path)
expect(subject).to include(procedure_archived_manually)
expect(subject).to include(procedure_archived_automatically)
end
end
describe 'follow' do describe 'follow' do
let(:dossier) { create :dossier } let(:dossier) { create :dossier }
let(:already_followed_dossier) { create :dossier } let(:already_followed_dossier) { create :dossier }

View file

@ -547,12 +547,12 @@ describe Procedure do
end end
describe '#publish!' do describe '#publish!' do
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure, path: 'example-path') }
let(:now) { Time.zone.now.beginning_of_minute } let(:now) { Time.zone.now.beginning_of_minute }
before do before do
Timecop.freeze(now) Timecop.freeze(now)
procedure.publish!(procedure.administrateurs.first, "example-path", procedure.lien_site_web) procedure.publish!
end end
after { Timecop.return } after { Timecop.return }
@ -616,6 +616,22 @@ describe Procedure do
it { expect(procedure.archived_at).to eq(now) } it { expect(procedure.archived_at).to eq(now) }
end end
describe 'path_customized?' do
let(:procedure) { create :procedure }
subject { procedure.path_customized? }
context 'when the path is still the default' do
it { is_expected.to be_falsey }
end
context 'when the path has been changed' do
before { procedure.path = 'custom_path' }
it { is_expected.to be_truthy }
end
end
describe 'total_dossier' do describe 'total_dossier' do
let(:procedure) { create :procedure } let(:procedure) { create :procedure }
@ -630,12 +646,45 @@ describe Procedure do
it { is_expected.to eq 2 } it { is_expected.to eq 2 }
end end
describe '#default_path' do describe 'suggested_path' do
let(:procedure) { create(:procedure, libelle: 'A long libelle with àccênts, blabla coucou hello un deux trois voila') } let(:procedure) { create :procedure, aasm_state: :publiee, libelle: 'Inscription au Collège' }
subject { procedure.default_path } subject { procedure.suggested_path(procedure.administrateurs.first) }
it { is_expected.to eq('a-long-libelle-with-accents-blabla-coucou-hello-un') } context 'when the path has been customized' do
before { procedure.path = 'custom_path' }
it { is_expected.to eq 'custom_path' }
end
context 'when the suggestion does not conflict' do
it { is_expected.to eq 'inscription-au-college' }
end
context 'when the suggestion conflicts with one procedure' do
before do
create :procedure, aasm_state: :publiee, path: 'inscription-au-college'
end
it { is_expected.to eq 'inscription-au-college-2' }
end
context 'when the suggestion conflicts with several procedures' do
before do
create :procedure, aasm_state: :publiee, path: 'inscription-au-college'
create :procedure, aasm_state: :publiee, path: 'inscription-au-college-2'
end
it { is_expected.to eq 'inscription-au-college-3' }
end
context 'when the suggestion conflicts with another procedure of the same admin' do
before do
create :procedure, aasm_state: :publiee, path: 'inscription-au-college', administrateurs: procedure.administrateurs
end
it { is_expected.to eq 'inscription-au-college' }
end
end end
describe ".default_scope" do describe ".default_scope" do

View file

@ -6,6 +6,7 @@ describe 'admin/procedures/show.html.haml', type: :view do
before do before do
assign(:procedure, procedure) assign(:procedure, procedure)
assign(:procedure_lien, commencer_url(path: procedure.path))
end end
describe 'procedure is draft' do describe 'procedure is draft' do
@ -41,7 +42,7 @@ describe 'admin/procedures/show.html.haml', type: :view do
describe 'procedure is published' do describe 'procedure is published' do
before do before do
procedure.publish!(procedure.administrateurs.first, 'fake_path', procedure.lien_site_web) procedure.publish!
procedure.reload procedure.reload
render render
end end
@ -59,7 +60,7 @@ describe 'admin/procedures/show.html.haml', type: :view do
describe 'procedure is archived' do describe 'procedure is archived' do
before do before do
procedure.publish!(procedure.administrateurs.first, 'fake_path', procedure.lien_site_web) procedure.publish!
procedure.archive! procedure.archive!
procedure.reload procedure.reload
render render

View file

@ -21,7 +21,7 @@ describe 'users/dossiers/brouillon.html.haml', type: :view do
end end
it 'affiche les boutons de validation' do it 'affiche les boutons de validation' do
expect(rendered).to have_selector('.send-wrapper') expect(rendered).to have_selector('.send-dossier-actions-bar')
end end
it 'prépare le footer' do it 'prépare le footer' do