commit
585e6fbc74
19 changed files with 577 additions and 84 deletions
|
@ -61,3 +61,7 @@
|
|||
.mb-2 {
|
||||
margin-bottom: 2 * $default-spacer;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: $default-spacer;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,13 @@ module NewAdministrateur
|
|||
end
|
||||
end
|
||||
|
||||
def procedure_revisable?
|
||||
if @procedure.locked? && !@procedure.feature_enabled?(:procedure_revisions)
|
||||
flash.alert = 'Démarche verrouillée'
|
||||
redirect_to admin_procedure_path(@procedure)
|
||||
end
|
||||
end
|
||||
|
||||
def reset_procedure
|
||||
if @procedure.brouillon?
|
||||
@procedure.reset!
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module NewAdministrateur
|
||||
class ProceduresController < AdministrateurController
|
||||
before_action :retrieve_procedure, only: [:champs, :annotations, :edit, :monavis, :update_monavis, :jeton, :update_jeton, :publication, :publish, :transfert, :allow_expert_review, :experts_require_administrateur_invitation]
|
||||
before_action :procedure_locked?, only: [:champs, :annotations]
|
||||
before_action :procedure_revisable?, only: [:champs, :annotations]
|
||||
|
||||
ITEMS_PER_PAGE = 25
|
||||
|
||||
|
@ -154,12 +154,16 @@ module NewAdministrateur
|
|||
def publish
|
||||
@procedure.assign_attributes(publish_params)
|
||||
|
||||
if @procedure.publish_or_reopen!(current_administrateur)
|
||||
if @procedure.draft_changed?
|
||||
@procedure.publish_revision!
|
||||
flash.notice = "Nouvelle version de la démarche publiée"
|
||||
redirect_to admin_procedure_path(@procedure)
|
||||
elsif @procedure.publish_or_reopen!(current_administrateur)
|
||||
flash.notice = "Démarche publiée"
|
||||
else
|
||||
redirect_to admin_procedure_path(@procedure)
|
||||
else
|
||||
flash.alert = @procedure.errors.full_messages
|
||||
redirect_to admin_procedure_path(@procedure)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module NewAdministrateur
|
||||
class TypesDeChampController < AdministrateurController
|
||||
before_action :retrieve_procedure, only: [:create, :update, :move, :destroy]
|
||||
before_action :procedure_locked?, only: [:create, :update, :move, :destroy]
|
||||
before_action :procedure_revisable?, only: [:create, :update, :move, :destroy]
|
||||
|
||||
def create
|
||||
type_de_champ = @procedure.draft_revision.add_type_de_champ(type_de_champ_create_params)
|
||||
|
|
|
@ -764,19 +764,19 @@ class Dossier < ApplicationRecord
|
|||
log_dossier_operation(avis.claimant, :demander_un_avis, avis)
|
||||
end
|
||||
|
||||
def spreadsheet_columns_csv(types_de_champ:, types_de_champ_private:)
|
||||
spreadsheet_columns(with_etablissement: true, types_de_champ: types_de_champ, types_de_champ_private: types_de_champ_private)
|
||||
def spreadsheet_columns_csv(types_de_champ:)
|
||||
spreadsheet_columns(with_etablissement: true, types_de_champ: types_de_champ)
|
||||
end
|
||||
|
||||
def spreadsheet_columns_xlsx(types_de_champ:, types_de_champ_private:)
|
||||
spreadsheet_columns(types_de_champ: types_de_champ, types_de_champ_private: types_de_champ_private)
|
||||
def spreadsheet_columns_xlsx(types_de_champ:)
|
||||
spreadsheet_columns(types_de_champ: types_de_champ)
|
||||
end
|
||||
|
||||
def spreadsheet_columns_ods(types_de_champ:, types_de_champ_private:)
|
||||
spreadsheet_columns(types_de_champ: types_de_champ, types_de_champ_private: types_de_champ_private)
|
||||
def spreadsheet_columns_ods(types_de_champ:)
|
||||
spreadsheet_columns(types_de_champ: types_de_champ)
|
||||
end
|
||||
|
||||
def spreadsheet_columns(with_etablissement: false, types_de_champ:, types_de_champ_private:)
|
||||
def spreadsheet_columns(with_etablissement: false, types_de_champ:)
|
||||
columns = [
|
||||
['ID', id.to_s],
|
||||
['Email', user_email_for(:display)]
|
||||
|
@ -843,26 +843,12 @@ class Dossier < ApplicationRecord
|
|||
columns << ['Groupe instructeur', groupe_instructeur.label]
|
||||
end
|
||||
|
||||
columns + champs_for_export(types_de_champ) + champs_private_for_export(types_de_champ_private)
|
||||
columns + champs_for_export(types_de_champ)
|
||||
end
|
||||
|
||||
def champs_for_export(types_de_champ)
|
||||
# Index values by stable_id
|
||||
values = champs.reject(&:exclude_from_export?).reduce({}) do |champs, champ|
|
||||
champs[champ.stable_id] = champ.for_export
|
||||
champs
|
||||
end
|
||||
|
||||
# Get all the champs values for the types de champ in the final list.
|
||||
# Dossier might not have corresponding champ – display nil.
|
||||
types_de_champ.map do |type_de_champ|
|
||||
[type_de_champ.libelle, values[type_de_champ.stable_id]]
|
||||
end
|
||||
end
|
||||
|
||||
def champs_private_for_export(types_de_champ)
|
||||
# Index values by stable_id
|
||||
values = champs_private.reject(&:exclude_from_export?).reduce({}) do |champs, champ|
|
||||
values = (champs + champs_private).reject(&:exclude_from_export?).reduce({}) do |champs, champ|
|
||||
champs[champ.stable_id] = champ.for_export
|
||||
champs
|
||||
end
|
||||
|
|
|
@ -87,6 +87,41 @@ class Procedure < ApplicationRecord
|
|||
brouillon? ? draft_types_de_champ_private : published_types_de_champ_private
|
||||
end
|
||||
|
||||
def types_de_champ_for_procedure_presentation
|
||||
explanatory_types_de_champ = [:header_section, :explication, :repetition].map { |k| TypeDeChamp.type_champs.fetch(k) }
|
||||
|
||||
if brouillon?
|
||||
TypeDeChamp
|
||||
.joins(:revisions)
|
||||
.where.not(type_champ: explanatory_types_de_champ)
|
||||
.where(procedure_revisions: { id: draft_revision_id })
|
||||
.order(:private, :position)
|
||||
else
|
||||
# fetch all type_de_champ.stable_id for all the revisions expect draft
|
||||
# and for each stable_id take the bigger (more recent) type_de_champ.id
|
||||
recent_ids = TypeDeChamp
|
||||
.joins(:revisions)
|
||||
.where.not(type_champ: explanatory_types_de_champ)
|
||||
.where(procedure_revisions: { procedure_id: id })
|
||||
.where.not(procedure_revisions: { id: draft_revision_id })
|
||||
.group(:stable_id)
|
||||
.select('MAX(types_de_champ.id)')
|
||||
|
||||
# fetch the more recent procedure_revision_types_de_champ
|
||||
# which includes recents_ids
|
||||
recents_prtdc = ProcedureRevisionTypeDeChamp
|
||||
.where(type_de_champ_id: recent_ids)
|
||||
.where.not(revision_id: draft_revision_id)
|
||||
.group(:type_de_champ_id)
|
||||
.select('MAX(id)')
|
||||
|
||||
TypeDeChamp
|
||||
.joins(:revision_types_de_champ)
|
||||
.where(revision_types_de_champ: { id: recents_prtdc })
|
||||
.order(:private, :position, 'revision_types_de_champ.revision_id': :desc)
|
||||
end
|
||||
end
|
||||
|
||||
def types_de_champ_for_tags
|
||||
if brouillon?
|
||||
draft_types_de_champ
|
||||
|
@ -111,14 +146,6 @@ class Procedure < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def types_de_champ_for_export
|
||||
types_de_champ.reject(&:exclude_from_export?)
|
||||
end
|
||||
|
||||
def types_de_champ_private_for_export
|
||||
types_de_champ_private.reject(&:exclude_from_export?)
|
||||
end
|
||||
|
||||
has_many :administrateurs_procedures
|
||||
has_many :administrateurs, through: :administrateurs_procedures, after_remove: -> (procedure, _admin) { procedure.validate! }
|
||||
has_many :groupe_instructeurs, dependent: :destroy
|
||||
|
@ -333,6 +360,22 @@ class Procedure < ApplicationRecord
|
|||
publiee? || close? || depubliee?
|
||||
end
|
||||
|
||||
def draft_changed?
|
||||
publiee? && published_revision.changed?(draft_revision)
|
||||
end
|
||||
|
||||
def revision_changes
|
||||
published_revision.compare(draft_revision)
|
||||
end
|
||||
|
||||
def revision_types_de_champ_private_changes
|
||||
revision_changes.filter { |change| change[:private] }
|
||||
end
|
||||
|
||||
def revision_types_de_champ_changes
|
||||
revision_changes.filter { |change| !change[:private] }
|
||||
end
|
||||
|
||||
def accepts_new_dossiers?
|
||||
publiee? || brouillon?
|
||||
end
|
||||
|
@ -689,6 +732,11 @@ class Procedure < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def publish_revision!
|
||||
update!(draft_revision: create_new_revision, published_revision: draft_revision)
|
||||
published_revision.touch(:published_at)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def before_publish
|
||||
|
|
|
@ -66,19 +66,9 @@ class ProcedurePresentation < ApplicationRecord
|
|||
)
|
||||
end
|
||||
|
||||
explanatory_types_de_champ = [:header_section, :explication].map { |k| TypeDeChamp.type_champs.fetch(k) }
|
||||
|
||||
fields.concat procedure.types_de_champ
|
||||
.where.not(type_champ: explanatory_types_de_champ)
|
||||
.order(:id)
|
||||
.pluck(:libelle, :stable_id)
|
||||
.map { |(libelle, stable_id)| field_hash(libelle, TYPE_DE_CHAMP, stable_id.to_s) }
|
||||
|
||||
fields.concat procedure.types_de_champ_private
|
||||
.where.not(type_champ: explanatory_types_de_champ)
|
||||
.order(:id)
|
||||
.pluck(:libelle, :stable_id)
|
||||
.map { |(libelle, stable_id)| field_hash(libelle, TYPE_DE_CHAMP_PRIVATE, stable_id.to_s) }
|
||||
fields.concat procedure.types_de_champ_for_procedure_presentation
|
||||
.pluck(:libelle, :private, :stable_id)
|
||||
.map { |(libelle, is_private, stable_id)| field_hash(libelle, is_private ? TYPE_DE_CHAMP_PRIVATE : TYPE_DE_CHAMP, stable_id.to_s) }
|
||||
|
||||
fields
|
||||
end
|
||||
|
|
|
@ -101,8 +101,131 @@ class ProcedureRevision < ApplicationRecord
|
|||
!draft?
|
||||
end
|
||||
|
||||
def changed?(revision)
|
||||
types_de_champ != revision.types_de_champ || types_de_champ_private != revision.types_de_champ_private
|
||||
end
|
||||
|
||||
def compare(revision)
|
||||
changes = []
|
||||
changes += compare_types_de_champ(types_de_champ, revision.types_de_champ)
|
||||
changes += compare_types_de_champ(types_de_champ_private, revision.types_de_champ_private)
|
||||
changes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compare_types_de_champ(from_tdc, to_tdc)
|
||||
if from_tdc == to_tdc
|
||||
[]
|
||||
else
|
||||
from_h = from_tdc.index_by(&:stable_id)
|
||||
to_h = to_tdc.index_by(&:stable_id)
|
||||
|
||||
from_sids = from_h.keys
|
||||
to_sids = to_h.keys
|
||||
|
||||
removed = (from_sids - to_sids).map do |sid|
|
||||
{ op: :remove, label: from_h[sid].libelle, private: from_h[sid].private?, position: from_sids.index(sid) }
|
||||
end
|
||||
|
||||
added = (to_sids - from_sids).map do |sid|
|
||||
{ op: :add, label: to_h[sid].libelle, private: to_h[sid].private?, position: to_sids.index(sid) }
|
||||
end
|
||||
|
||||
kept = from_sids.intersection(to_sids)
|
||||
|
||||
moved = kept
|
||||
.map { |sid| [sid, from_sids.index(sid), to_sids.index(sid)] }
|
||||
.filter { |_, from_index, to_index| from_index != to_index }
|
||||
.map do |sid, from_index, to_index|
|
||||
{ op: :move, label: from_h[sid].libelle, private: from_h[sid].private?, from: from_index, to: to_index, position: to_index }
|
||||
end
|
||||
|
||||
changed = kept
|
||||
.map { |sid| [sid, from_h[sid], to_h[sid]] }
|
||||
.flat_map do |sid, from, to|
|
||||
compare_type_de_champ(from, to)
|
||||
.each { |h| h[:position] = to_sids.index(sid) }
|
||||
end
|
||||
|
||||
(removed + added + moved + changed)
|
||||
.sort_by { |h| h[:position] }
|
||||
.each { |h| h.delete(:position) }
|
||||
end
|
||||
end
|
||||
|
||||
def compare_type_de_champ(from_type_de_champ, to_type_de_champ)
|
||||
changes = []
|
||||
if from_type_de_champ.type_champ != to_type_de_champ.type_champ
|
||||
changes << {
|
||||
op: :update,
|
||||
attribute: :type_champ,
|
||||
label: from_type_de_champ.libelle,
|
||||
private: from_type_de_champ.private?,
|
||||
from: from_type_de_champ.type_champ,
|
||||
to: to_type_de_champ.type_champ
|
||||
}
|
||||
end
|
||||
if from_type_de_champ.libelle != to_type_de_champ.libelle
|
||||
changes << {
|
||||
op: :update,
|
||||
attribute: :libelle,
|
||||
label: from_type_de_champ.libelle,
|
||||
private: from_type_de_champ.private?,
|
||||
from: from_type_de_champ.libelle,
|
||||
to: to_type_de_champ.libelle
|
||||
}
|
||||
end
|
||||
if from_type_de_champ.description != to_type_de_champ.description
|
||||
changes << {
|
||||
op: :update,
|
||||
attribute: :description,
|
||||
label: from_type_de_champ.libelle,
|
||||
private: from_type_de_champ.private?,
|
||||
from: from_type_de_champ.description,
|
||||
to: to_type_de_champ.description
|
||||
}
|
||||
end
|
||||
if from_type_de_champ.mandatory? != to_type_de_champ.mandatory?
|
||||
changes << {
|
||||
op: :update,
|
||||
attribute: :mandatory,
|
||||
label: from_type_de_champ.libelle,
|
||||
private: from_type_de_champ.private?,
|
||||
from: from_type_de_champ.mandatory?,
|
||||
to: to_type_de_champ.mandatory?
|
||||
}
|
||||
end
|
||||
if to_type_de_champ.drop_down_list?
|
||||
if from_type_de_champ.drop_down_list_options != to_type_de_champ.drop_down_list_options
|
||||
changes << {
|
||||
op: :update,
|
||||
attribute: :drop_down_options,
|
||||
label: from_type_de_champ.libelle,
|
||||
private: from_type_de_champ.private?,
|
||||
from: from_type_de_champ.drop_down_list_options,
|
||||
to: to_type_de_champ.drop_down_list_options
|
||||
}
|
||||
end
|
||||
elsif to_type_de_champ.piece_justificative?
|
||||
if from_type_de_champ.piece_justificative_template_checksum != to_type_de_champ.piece_justificative_template_checksum
|
||||
changes << {
|
||||
op: :update,
|
||||
attribute: :piece_justificative_template,
|
||||
label: from_type_de_champ.libelle,
|
||||
private: from_type_de_champ.private?,
|
||||
from: from_type_de_champ.piece_justificative_template_filename,
|
||||
to: to_type_de_champ.piece_justificative_template_filename
|
||||
}
|
||||
end
|
||||
elsif to_type_de_champ.repetition?
|
||||
if from_type_de_champ.types_de_champ != to_type_de_champ.types_de_champ
|
||||
changes += compare_types_de_champ(from_type_de_champ.types_de_champ, to_type_de_champ.types_de_champ)
|
||||
end
|
||||
end
|
||||
changes
|
||||
end
|
||||
|
||||
def revise_type_de_champ(type_de_champ)
|
||||
types_de_champ_association = type_de_champ.private? ? :revision_types_de_champ_private : :revision_types_de_champ
|
||||
association = send(types_de_champ_association).find_by!(type_de_champ: type_de_champ)
|
||||
|
|
|
@ -226,6 +226,12 @@ class TypeDeChamp < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def piece_justificative_template_checksum
|
||||
if piece_justificative_template.attached?
|
||||
piece_justificative_template.checksum
|
||||
end
|
||||
end
|
||||
|
||||
def drop_down_list_value
|
||||
if drop_down_list_options.present?
|
||||
drop_down_list_options.reject(&:empty?).join("\r\n")
|
||||
|
|
|
@ -84,11 +84,10 @@ class ProcedureExportService
|
|||
end
|
||||
|
||||
def spreadsheet_columns(format)
|
||||
types_de_champ = @procedure.types_de_champ_for_export
|
||||
types_de_champ_private = @procedure.types_de_champ_private_for_export
|
||||
types_de_champ = @procedure.types_de_champ_for_procedure_presentation.to_a
|
||||
|
||||
Proc.new do |instance|
|
||||
instance.send(:"spreadsheet_columns_#{format}", types_de_champ: types_de_champ, types_de_champ_private: types_de_champ_private)
|
||||
instance.send(:"spreadsheet_columns_#{format}", types_de_champ: types_de_champ)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,40 +1,44 @@
|
|||
.card.mb-4
|
||||
%h2.card-title Publiez votre démarche
|
||||
= form_tag admin_procedure_publish_path(procedure_id: procedure.id), method: :put, class: 'form' do
|
||||
%p.mb-4 Publiez votre démarche, et partagez la à vos usagers. Aucune modification ne sera possible.
|
||||
%p Personnalisez le lien public de la démarche pour en faciliter l’accès (<strong>obligatoire pour publier votre démarche</strong>) :
|
||||
%p.empty-text
|
||||
= commencer_url(path: '')
|
||||
= text_field_tag(:path, procedure.path,
|
||||
id: 'procedure_path',
|
||||
label: 'Adresse de diffusion',
|
||||
placeholder: 'chemin-de-la-démarche',
|
||||
required: true,
|
||||
class: 'form',
|
||||
pattern: '^[a-z0-9_-]{3,200}$',
|
||||
title: "De 3 à 200 caractères; minuscules, chiffres et tiret seulement",
|
||||
data: { debounce: true, url: admin_procedure_publish_validate_path(procedure)},
|
||||
autocomplete: 'off',
|
||||
style: 'width: 300px; display: inline;')
|
||||
.text-info.mb-4
|
||||
Attention, diffusez toujours le <strong>lien complet</strong> affiché ci-dessus, et non pas un lien générique vers #{APPLICATION_NAME}. Ne dites pas non plus aux usagers de se rendre sur le site générique #{APPLICATION_NAME}, donnez-leur toujours le lien complet.
|
||||
%h2.card-title Diffusion de la démarche
|
||||
%p Où les utilisateurs trouveront-ils le lien de la démarche ? Typiquement, il s’agit d’une page de votre site web.
|
||||
%p.center
|
||||
= text_field_tag(:lien_site_web, procedure.lien_site_web,
|
||||
- if procedure.draft_changed?
|
||||
%p.mb-4 Publiez une nouvelle version de votre démarche. Les changements suivants seront appliqués :
|
||||
= render partial: 'revision_changes', locals: { changes: procedure.revision_changes }
|
||||
- else
|
||||
%p.mb-4 Publiez votre démarche, et partagez la à vos usagers. Aucune modification ne sera possible.
|
||||
%p Personnalisez le lien public de la démarche pour en faciliter l’accès (<strong>obligatoire pour publier votre démarche</strong>) :
|
||||
%p.empty-text
|
||||
= commencer_url(path: '')
|
||||
= text_field_tag(:path, procedure.path,
|
||||
id: 'procedure_path',
|
||||
label: 'Adresse de diffusion',
|
||||
placeholder: 'chemin-de-la-démarche',
|
||||
required: true,
|
||||
class: 'form-control',
|
||||
class: 'form',
|
||||
pattern: '^[a-z0-9_-]{3,200}$',
|
||||
title: "De 3 à 200 caractères; minuscules, chiffres et tiret seulement",
|
||||
data: { debounce: true, url: admin_procedure_publish_validate_path(procedure)},
|
||||
autocomplete: 'off',
|
||||
placeholder: 'https://exemple.gouv.fr/ma_demarche')
|
||||
style: 'width: 300px; display: inline;')
|
||||
.text-info.mb-4
|
||||
Attention, diffusez toujours le <strong>lien complet</strong> affiché ci-dessus, et non pas un lien générique vers #{APPLICATION_NAME}. Ne dites pas non plus aux usagers de se rendre sur le site générique #{APPLICATION_NAME}, donnez-leur toujours le lien complet.
|
||||
%h2.card-title Diffusion de la démarche
|
||||
%p Où les utilisateurs trouveront-ils le lien de la démarche ? Typiquement, il s’agit d’une 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')
|
||||
|
||||
- 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)
|
||||
- if errors.details[:path]&.pluck(:error)&.include?(:taken) && procedure.path_available?(administrateur, procedure.path)
|
||||
- errors.delete(:path)
|
||||
|
||||
- options = { class: "button primary", id: 'publish' }
|
||||
- if errors.details[:path].present?
|
||||
- options[:disabled] = :disabled
|
||||
.flex.justify-end
|
||||
= submit_tag procedure_publish_text(@procedure, :submit), options
|
||||
= submit_tag procedure_publish_text(procedure, :submit), options
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
%ul
|
||||
- changes.each do |change|
|
||||
- case change[:op]
|
||||
- when :add
|
||||
%li.mb-1= "Le champ « #{change[:label]} » a été ajouté."
|
||||
- when :remove
|
||||
%li.mb-1= "Le champ « #{change[:label]} » a été supprimé."
|
||||
- when :update
|
||||
- case change[:attribute]
|
||||
- when :libelle
|
||||
%li.mb-1= "Le libellé du champ « #{change[:label]} » a changé en « #{change[:to]} »."
|
||||
- when :type_champ
|
||||
%li.mb-1= "Le type du champ « #{change[:label]} » a changé. Il est maintenant de type « #{t("activerecord.attributes.type_de_champ.type_champs.#{change[:to]}")} »."
|
||||
- when :description
|
||||
%li.mb-1= "La description du champ « #{change[:label]} » a changé. La nouvelle description est « #{change[:to]} »."
|
||||
- when :mandatory
|
||||
- if change[:from] == false
|
||||
%li.mb-1= "Le champ « #{change[:label]} » est maintenant obligatoire."
|
||||
- else
|
||||
%li.mb-1= "Le champ « #{change[:label]} » n'est plus obligatoire."
|
||||
- when :piece_justificative_template
|
||||
%li.mb-1= "Le champ « #{change[:label]} » a changé de modèle de pièce justificative."
|
||||
- when :drop_down_options
|
||||
- added = change[:to].sort - change[:from].sort
|
||||
- removed = change[:from].sort - change[:to].sort
|
||||
%li.mb-1
|
||||
= "Les options de sélection du champ « #{change[:label]} » ont changé."
|
||||
%ul
|
||||
- if added.present?
|
||||
%li= "Valeurs ajoutés : #{added.map{ |term| "« #{term.strip} »" }.join(", ")}."
|
||||
- if removed.present?
|
||||
%li= "Valeurs supprimés : #{removed.map{ |term| "« #{term.strip} »" }.join(", ")}."
|
||||
- move_changes = changes.filter { |change| change[:op] == :move }.size
|
||||
- if move_changes != 0
|
||||
%li.mb-1= t(:has_move_changes, count: move_changes, scope: [:new_administrateur, :revision_changes])
|
|
@ -14,6 +14,9 @@
|
|||
- if @procedure.close? || @procedure.depubliee?
|
||||
%p.mb-4 Cette démarche est <strong>close</strong> et n’est donc plus accessible par le public. Vous pouvez la réactiver :
|
||||
= render partial: 'publication_form', locals: { procedure: @procedure, administrateur: @current_administrateur }
|
||||
- elsif @procedure.draft_changed?
|
||||
%p.mb-4 Cette démarche est déjà <strong>publiée</strong>. Elle a été <strong>modifiée</strong> depuis sa publication. Vous pouvez publier les changements effectués dans une nouvelle version de cette démarche :
|
||||
= render partial: 'publication_form', locals: { procedure: @procedure, administrateur: @current_administrateur }
|
||||
- elsif @procedure.publiee?
|
||||
%p 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 :
|
||||
|
|
|
@ -29,6 +29,27 @@
|
|||
%span.icon.archive
|
||||
Clore
|
||||
|
||||
- if @procedure.draft_changed?
|
||||
= link_to 'Publier les modifications', admin_procedure_publication_path(@procedure), class: 'button primary', id: 'publish-procedure-link', data: { disable_with: "Publication..." }
|
||||
|
||||
- if @procedure.draft_changed?
|
||||
- types_de_champ_changes = @procedure.revision_types_de_champ_changes
|
||||
- types_de_champ_private_changes = @procedure.revision_types_de_champ_private_changes
|
||||
|
||||
- if types_de_champ_changes.present?
|
||||
.container
|
||||
.card.featured
|
||||
.card-title
|
||||
= t(:has_changes, count: types_de_champ_changes.size, scope: [:new_administrateur, :revision_changes])
|
||||
= render partial: 'revision_changes', locals: { changes: types_de_champ_changes }
|
||||
|
||||
- if types_de_champ_private_changes.present?
|
||||
.container
|
||||
.card.featured
|
||||
.card-title
|
||||
= t(:has_private_changes, count: types_de_champ_private_changes.size, scope: [:new_administrateur, :revision_changes])
|
||||
= render partial: 'revision_changes', locals: { changes: types_de_champ_private_changes }
|
||||
|
||||
.container
|
||||
%h2.procedure-admin-explanation Indispensable avant publication
|
||||
.procedure-grid
|
||||
|
@ -41,7 +62,7 @@
|
|||
%p.card-admin-subtitle Logo, nom, description
|
||||
%p.button Modifier
|
||||
|
||||
- if !@procedure.locked?
|
||||
- if !@procedure.locked? || @procedure.feature_enabled?(:procedure_revisions)
|
||||
= link_to champs_admin_procedure_path(@procedure), class: 'card-admin' do
|
||||
- if @procedure.draft_types_de_champ.count > 0
|
||||
%div
|
||||
|
@ -159,7 +180,7 @@
|
|||
%p.card-admin-subtitle Notifications automatiques
|
||||
%p.button Modifier
|
||||
|
||||
- if !@procedure.locked?
|
||||
- if !@procedure.locked? || @procedure.feature_enabled?(:procedure_revisions)
|
||||
= link_to annotations_admin_procedure_path(@procedure), class: 'card-admin' do
|
||||
- if @procedure.draft_types_de_champ_private.present?
|
||||
%div
|
||||
|
|
|
@ -32,6 +32,7 @@ features = [
|
|||
:hide_instructeur_email,
|
||||
:instructeur_bypass_email_login_token,
|
||||
:make_experts_notifiable,
|
||||
:procedure_revisions,
|
||||
:procedure_routage_api
|
||||
]
|
||||
|
||||
|
|
|
@ -27,3 +27,13 @@ fr:
|
|||
existing_groupe:
|
||||
one: "%{count} groupe existe"
|
||||
other: "%{count} groupes existent"
|
||||
revision_changes:
|
||||
has_changes:
|
||||
one: Un champ a été changé
|
||||
other: "%{count} champs ont été changés"
|
||||
has_private_changes:
|
||||
one: Une annotation privée a été changée
|
||||
other: "%{count} deux annotations privées ont été changées"
|
||||
has_move_changes:
|
||||
one: Un champ a changé de position
|
||||
other: "%{count} champs ont changé de position"
|
||||
|
|
|
@ -1364,8 +1364,8 @@ describe Dossier do
|
|||
it "should have champs from all revisions" do
|
||||
expect(dossier.types_de_champ.map(&:libelle)).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Yes/no", explication_type_de_champ.libelle])
|
||||
expect(dossier_second_revision.types_de_champ.map(&:libelle)).to eq([datetime_type_de_champ.libelle, "Updated yes/no", explication_type_de_champ.libelle, "New text field"])
|
||||
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_export).map { |(libelle)| libelle }).to eq([datetime_type_de_champ.libelle, "Updated yes/no", "New text field"])
|
||||
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_export)).to eq(dossier_second_revision.champs_for_export(dossier_second_revision.procedure.types_de_champ_for_export))
|
||||
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation).map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Updated yes/no", "New text field"])
|
||||
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation)).to eq(dossier_second_revision.champs_for_export(dossier_second_revision.procedure.types_de_champ_for_procedure_presentation))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1373,7 +1373,7 @@ describe Dossier do
|
|||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_explication) }
|
||||
|
||||
it "should not contain non-exportable types de champ" do
|
||||
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_export).map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle])
|
||||
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation).map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1451,6 +1451,6 @@ describe Dossier do
|
|||
describe "#spreadsheet_columns" do
|
||||
let(:dossier) { create(:dossier) }
|
||||
|
||||
it { expect(dossier.spreadsheet_columns(types_de_champ: [], types_de_champ_private: [])).to include(["État du dossier", "Brouillon"]) }
|
||||
it { expect(dossier.spreadsheet_columns(types_de_champ: [])).to include(["État du dossier", "Brouillon"]) }
|
||||
end
|
||||
end
|
||||
|
|
90
spec/models/procedure_presentation_and_revisions_spec.rb
Normal file
90
spec/models/procedure_presentation_and_revisions_spec.rb
Normal file
|
@ -0,0 +1,90 @@
|
|||
describe ProcedurePresentation do
|
||||
describe "#types_de_champ_for_procedure_presentation" do
|
||||
subject { procedure.types_de_champ_for_procedure_presentation.pluck(:libelle) }
|
||||
|
||||
context 'for a draft procedure' do
|
||||
let(:procedure) { create(:procedure) }
|
||||
|
||||
context 'when there are one tdc on a published revision' do
|
||||
let!(:tdc) { { type_champ: :number, libelle: 'libelle 1' } }
|
||||
|
||||
before { procedure.draft_revision.add_type_de_champ(tdc) }
|
||||
|
||||
it { is_expected.to match(['libelle 1']) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a published procedure' do
|
||||
let(:procedure) { create(:procedure, :published) }
|
||||
let!(:tdc) { { type_champ: :number, libelle: 'libelle 1' } }
|
||||
|
||||
before do
|
||||
procedure.draft_revision.add_type_de_champ(tdc)
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it { is_expected.to match(['libelle 1']) }
|
||||
|
||||
context 'when there is another published revision with an added tdc' do
|
||||
let!(:added_tdc) { { type_champ: :number, libelle: 'libelle 2' } }
|
||||
|
||||
before do
|
||||
procedure.draft_revision.add_type_de_champ(added_tdc)
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it { is_expected.to match(['libelle 1', 'libelle 2']) }
|
||||
end
|
||||
|
||||
context 'add one tdc above the first one' do
|
||||
let!(:tdc2) { { type_champ: :number, libelle: 'libelle 2' } }
|
||||
|
||||
before do
|
||||
created_tdc2 = procedure.draft_revision.add_type_de_champ(tdc2)
|
||||
procedure.draft_revision.move_type_de_champ(created_tdc2.stable_id, 0)
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it { is_expected.to match(['libelle 2', 'libelle 1']) }
|
||||
|
||||
context 'and finaly, when this tdc is removed' do
|
||||
let!(:previous_tdc2) { procedure.published_revision.types_de_champ.find_by(libelle: 'libelle 2') }
|
||||
|
||||
before do
|
||||
procedure.draft_revision.remove_type_de_champ(previous_tdc2.stable_id)
|
||||
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it { is_expected.to match(['libelle 1', 'libelle 2']) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is another published revision with a renamed tdc' do
|
||||
let!(:previous_tdc) { procedure.published_revision.types_de_champ.first }
|
||||
let!(:changed_tdc) { { type_champ: :number, libelle: 'changed libelle 1' } }
|
||||
|
||||
before do
|
||||
type_de_champ = procedure.draft_revision.find_or_clone_type_de_champ(previous_tdc.id)
|
||||
type_de_champ.update(changed_tdc)
|
||||
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it { is_expected.to match(['changed libelle 1']) }
|
||||
end
|
||||
|
||||
context 'when there is another published which removes a previous tdc' do
|
||||
let!(:previous_tdc) { procedure.published_revision.types_de_champ.first }
|
||||
|
||||
before do
|
||||
type_de_champ = procedure.draft_revision.remove_type_de_champ(previous_tdc.id)
|
||||
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it { is_expected.to match(['libelle 1']) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -56,7 +56,7 @@ describe ProcedureRevision do
|
|||
revision.reload
|
||||
expect(revision.types_de_champ.index(type_de_champ)).to eq(2)
|
||||
expect(revision.procedure.types_de_champ.index(type_de_champ)).to eq(2)
|
||||
expect(revision.procedure.types_de_champ_for_export.index(type_de_champ)).to eq(2)
|
||||
expect(revision.procedure.types_de_champ_for_procedure_presentation.index(type_de_champ)).to eq(2)
|
||||
end
|
||||
|
||||
it 'move up' do
|
||||
|
@ -66,7 +66,7 @@ describe ProcedureRevision do
|
|||
revision.reload
|
||||
expect(revision.types_de_champ.index(last_type_de_champ)).to eq(0)
|
||||
expect(revision.procedure.types_de_champ.index(last_type_de_champ)).to eq(0)
|
||||
expect(revision.procedure.types_de_champ_for_export.index(last_type_de_champ)).to eq(0)
|
||||
expect(revision.procedure.types_de_champ_for_procedure_presentation.index(last_type_de_champ)).to eq(0)
|
||||
end
|
||||
|
||||
context 'repetition' do
|
||||
|
@ -152,5 +152,167 @@ describe ProcedureRevision do
|
|||
expect(new_revision.revision_types_de_champ).not_to eq(revision.revision_types_de_champ)
|
||||
expect(new_revision.revision_types_de_champ_private).not_to eq(revision.revision_types_de_champ_private)
|
||||
end
|
||||
|
||||
describe '#compare' do
|
||||
let(:type_de_champ_first) { revision.types_de_champ.first }
|
||||
let(:type_de_champ_second) { revision.types_de_champ.second }
|
||||
|
||||
it 'type_de_champ' do
|
||||
expect(new_revision.types_de_champ.size).to eq(2)
|
||||
new_type_de_champ = new_revision.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "Un champ text"
|
||||
})
|
||||
revision.reload
|
||||
expect(new_revision.types_de_champ.size).to eq(3)
|
||||
expect(new_revision.types_de_champ.last).to eq(new_type_de_champ)
|
||||
expect(new_revision.revision_types_de_champ.last.position).to eq(2)
|
||||
expect(new_revision.revision_types_de_champ.last.type_de_champ).to eq(new_type_de_champ)
|
||||
expect(new_revision.revision_types_de_champ.last.type_de_champ.revision).to eq(new_revision)
|
||||
expect(procedure.active_revision.changed?(new_revision)).to be_truthy
|
||||
expect(procedure.active_revision.compare(new_revision)).to eq([
|
||||
{
|
||||
op: :add,
|
||||
label: "Un champ text",
|
||||
private: false
|
||||
}
|
||||
])
|
||||
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ.first.stable_id).update(libelle: 'modifier le libelle')
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
{
|
||||
op: :update,
|
||||
attribute: :libelle,
|
||||
label: type_de_champ_first.libelle,
|
||||
private: false,
|
||||
from: type_de_champ_first.libelle,
|
||||
to: "modifier le libelle"
|
||||
},
|
||||
{
|
||||
op: :add,
|
||||
label: "Un champ text",
|
||||
private: false
|
||||
}
|
||||
])
|
||||
expect(new_revision.types_de_champ.first.revision).to eq(new_revision)
|
||||
|
||||
new_revision.move_type_de_champ(new_revision.types_de_champ.second.stable_id, 2)
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
{
|
||||
op: :update,
|
||||
attribute: :libelle,
|
||||
label: type_de_champ_first.libelle,
|
||||
private: false,
|
||||
from: type_de_champ_first.libelle,
|
||||
to: "modifier le libelle"
|
||||
},
|
||||
{
|
||||
op: :add,
|
||||
label: "Un champ text",
|
||||
private: false
|
||||
},
|
||||
{
|
||||
op: :move,
|
||||
label: type_de_champ_second.libelle,
|
||||
private: false,
|
||||
from: 1,
|
||||
to: 2
|
||||
}
|
||||
])
|
||||
expect(new_revision.types_de_champ.last.revision).to eq(revision)
|
||||
|
||||
new_revision.remove_type_de_champ(new_revision.types_de_champ.first.stable_id)
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
{
|
||||
op: :remove,
|
||||
label: type_de_champ_first.libelle,
|
||||
private: false
|
||||
},
|
||||
{
|
||||
op: :add,
|
||||
label: "Un champ text",
|
||||
private: false
|
||||
}
|
||||
])
|
||||
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ.last.stable_id).update(description: 'une description')
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ.last.stable_id).update(mandatory: true)
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
{
|
||||
op: :remove,
|
||||
label: type_de_champ_first.libelle,
|
||||
private: false
|
||||
},
|
||||
{
|
||||
op: :add,
|
||||
label: "Un champ text",
|
||||
private: false
|
||||
},
|
||||
{
|
||||
op: :update,
|
||||
attribute: :description,
|
||||
label: type_de_champ_second.libelle,
|
||||
private: false,
|
||||
from: type_de_champ_second.description,
|
||||
to: "une description"
|
||||
},
|
||||
{
|
||||
op: :update,
|
||||
attribute: :mandatory,
|
||||
label: type_de_champ_second.libelle,
|
||||
private: false,
|
||||
from: false,
|
||||
to: true
|
||||
}
|
||||
])
|
||||
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ.last.types_de_champ.first.stable_id).update(type_champ: :drop_down_list)
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ.last.types_de_champ.first.stable_id).update(drop_down_options: ['one', 'two'])
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
{
|
||||
op: :remove,
|
||||
label: type_de_champ_first.libelle,
|
||||
private: false
|
||||
},
|
||||
{
|
||||
op: :add,
|
||||
label: "Un champ text",
|
||||
private: false
|
||||
},
|
||||
{
|
||||
op: :update,
|
||||
attribute: :description,
|
||||
label: type_de_champ_second.libelle,
|
||||
private: false,
|
||||
from: type_de_champ_second.description,
|
||||
to: "une description"
|
||||
},
|
||||
{
|
||||
op: :update,
|
||||
attribute: :mandatory,
|
||||
label: type_de_champ_second.libelle,
|
||||
private: false,
|
||||
from: false,
|
||||
to: true
|
||||
},
|
||||
{
|
||||
op: :update,
|
||||
attribute: :type_champ,
|
||||
label: "sub type de champ",
|
||||
private: false,
|
||||
from: "text",
|
||||
to: "drop_down_list"
|
||||
},
|
||||
{
|
||||
op: :update,
|
||||
attribute: :drop_down_options,
|
||||
label: "sub type de champ",
|
||||
private: false,
|
||||
from: [],
|
||||
to: ["one", "two"]
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue