Merge pull request #5847 from tchak/enable-edit-procedure

Enable publish revisions
This commit is contained in:
Paul Chavard 2021-06-23 10:18:23 +02:00 committed by GitHub
commit 94546fb866
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 577 additions and 84 deletions

View file

@ -61,3 +61,7 @@
.mb-2 {
margin-bottom: 2 * $default-spacer;
}
.mb-1 {
margin-bottom: $default-spacer;
}

View file

@ -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!

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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")

View file

@ -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

View file

@ -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 laccè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 sagit dune 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 laccè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 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')
- 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

View file

@ -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])

View file

@ -14,6 +14,9 @@
- if @procedure.close? || @procedure.depubliee?
%p.mb-4 Cette démarche est <strong>close</strong> et nest 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 :

View file

@ -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

View file

@ -32,6 +32,7 @@ features = [
:hide_instructeur_email,
:instructeur_bypass_email_login_token,
:make_experts_notifiable,
:procedure_revisions,
:procedure_routage_api
]

View file

@ -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"

View file

@ -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

View 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

View file

@ -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