feat(ProcedureRevision.ineligibilites_rules): keep track of changes and show it to admin for republication

This commit is contained in:
mfo 2024-06-05 17:30:33 +02:00
parent aca3e38859
commit 5de4ce889f
No known key found for this signature in database
GPG key ID: 7CE3E1F5B794A8EC
11 changed files with 458 additions and 258 deletions

View file

@ -1,9 +1,13 @@
class Procedure::RevisionChangesComponent < ApplicationComponent
def initialize(changes:, previous_revision:)
@changes = changes
def initialize(new_revision:, previous_revision:)
@previous_revision = previous_revision
@public_move_changes, @private_move_changes = changes.filter { _1.op == :move }.partition { !_1.private? }
@delete_champ_warning = !total_dossiers.zero? && !@changes.all?(&:can_rebase?)
@new_revision = new_revision
@tdc_changes = previous_revision.compare_types_de_champ(new_revision)
@public_move_changes, @private_move_changes = @tdc_changes.filter { _1.op == :move }.partition { !_1.private? }
@delete_champ_warning = !total_dossiers.zero? && !@tdc_changes.all?(&:can_rebase?)
@ineligibilite_rules_changes = previous_revision.compare_ineligibilite_rules(new_revision)
end
private

View file

@ -80,3 +80,10 @@ fr:
update_expression_reguliere_exemple_text: Lexemple dexpression régulière de lannotation privée « %{label} » a été modifiée. Le nouvel exemple est « %{to} ».
remove_expression_reguliere_error_message: Le message derreur de lexpression régulière de lannotation privée « %{label} » a été supprimé.
update_expression_reguliere_error_message: Le message derreur de lexpression régulière de lannotation privée « %{label} » a été modifiée. Le nouveau message est « %{to} ».
ineligibilite_rules:
add: La condition dinéligibilité « %{new_condition} » a été ajoutée.
remove: La condition dinéligibilité « %{previous_condition} » a été supprimée
update: La conditon dinéligibilité « %{previous_condition} » a été changée pour « %{new_condition} »
enabled: "Linéligibilité des dossiers a été activée"
disabled: "Linéligibilité des dossiers a été désactivée"
message_updated: "Le message dinéligibilité a été changé pour « %{ineligibilite_message} »"

View file

@ -2,7 +2,7 @@
- list.with_empty do
= t('.no_changes')
- @changes.each do |change|
- @tdc_changes.each do |change|
- prefix = change.private? ? 'private' : 'public'
- case change.op
- when :add
@ -176,3 +176,7 @@
- list.with_item do
.fr-alert.fr-alert--warning.fr-mt-1v
= t(".invalid_routing_rules_alert")
- @ineligibilite_rules_changes.each do |change|
- list.with_item do
= t(".ineligibilite_rules.#{change.op}", **change.i18n_params)

View file

@ -22,7 +22,7 @@ module DossierRebaseConcern
end
def pending_changes
procedure.published_revision.present? ? revision.compare(procedure.published_revision) : []
procedure.published_revision.present? ? revision.compare_types_de_champ(procedure.published_revision) : []
end
def can_rebase_mandatory_change?(stable_id)

View file

@ -431,11 +431,15 @@ class Procedure < ApplicationRecord
def draft_changed?
preload_draft_and_published_revisions
!brouillon? && published_revision.different_from?(draft_revision) && revision_changes.present?
!brouillon? && (types_de_champ_revision_changes.present? || ineligibilite_rules_revision_changes.present?)
end
def revision_changes
published_revision.compare(draft_revision)
def types_de_champ_revision_changes
published_revision.compare_types_de_champ(draft_revision)
end
def ineligibilite_rules_revision_changes
published_revision.compare_ineligibilite_rules(draft_revision)
end
def preload_draft_and_published_revisions

View file

@ -148,16 +148,18 @@ class ProcedureRevision < ApplicationRecord
!draft?
end
def different_from?(revision)
revision_types_de_champ != revision.revision_types_de_champ
end
def compare(revision)
def compare_types_de_champ(revision)
changes = []
changes += compare_revision_types_de_champ(revision_types_de_champ, revision.revision_types_de_champ)
changes
end
def compare_ineligibilite_rules(revision)
changes = []
changes += compare_revision_ineligibilite_rules(revision)
changes
end
def dossier_for_preview(user)
dossier = Dossier
.create_with(autorisation_donnees: true)
@ -334,6 +336,29 @@ class ProcedureRevision < ApplicationRecord
end
end
def compare_revision_ineligibilite_rules(new_revision)
from_ineligibilite_rules = ineligibilite_rules
to_ineligibilite_rules = new_revision.ineligibilite_rules
changes = []
if from_ineligibilite_rules.present? && to_ineligibilite_rules.blank?
changes << ProcedureRevisionChange::RemoveEligibiliteRuleChange
end
if from_ineligibilite_rules.blank? && to_ineligibilite_rules.present?
changes << ProcedureRevisionChange::AddEligibiliteRuleChange
end
if from_ineligibilite_rules != to_ineligibilite_rules
changes << ProcedureRevisionChange::UpdateEligibiliteRuleChange
end
if ineligibilite_message != new_revision.ineligibilite_message
changes << ProcedureRevisionChange::UpdateEligibiliteMessageChange
end
if ineligibilite_enabled != new_revision.ineligibilite_enabled
changes << (new_revision.ineligibilite_enabled ? ProcedureRevisionChange::EligibiliteEnabledChange : ProcedureRevisionChange::EligibiliteDisabledChange)
end
changes.map { _1.new(self, new_revision) }
end
def compare_type_de_champ(from_type_de_champ, to_type_de_champ, from_coordinates, to_coordinates)
changes = []
if from_type_de_champ.type_champ != to_type_de_champ.type_champ

View file

@ -1,4 +1,5 @@
class ProcedureRevisionChange
class TypeDeChange
attr_reader :type_de_champ
def initialize(type_de_champ)
@type_de_champ = type_de_champ
@ -10,8 +11,9 @@ class ProcedureRevisionChange
def child? = @type_de_champ.child?
def to_h = { op:, stable_id:, label:, private: private? }
end
class AddChamp < ProcedureRevisionChange
class AddChamp < TypeDeChange
def initialize(type_de_champ)
super(type_de_champ)
end
@ -23,7 +25,7 @@ class ProcedureRevisionChange
def to_h = super.merge(mandatory: mandatory?)
end
class RemoveChamp < ProcedureRevisionChange
class RemoveChamp < TypeDeChange
def initialize(type_de_champ)
super(type_de_champ)
end
@ -32,7 +34,7 @@ class ProcedureRevisionChange
def can_rebase?(dossier = nil) = true
end
class MoveChamp < ProcedureRevisionChange
class MoveChamp < TypeDeChange
attr_reader :from, :to
def initialize(type_de_champ, from, to)
@ -46,7 +48,7 @@ class ProcedureRevisionChange
def to_h = super.merge(from:, to:)
end
class UpdateChamp < ProcedureRevisionChange
class UpdateChamp < TypeDeChange
attr_reader :attribute, :from, :to
def initialize(type_de_champ, attribute, from, to)
@ -75,4 +77,48 @@ class ProcedureRevisionChange
end
end
end
class EligibiliteRulesChange
attr_reader :previous_revision, :new_revision
def initialize(previous_revision, new_revision)
@previous_revision = previous_revision
@new_revision = new_revision
@previous_ineligibilite_rules = @previous_revision.ineligibilite_rules
@new_ineligibilite_rules = @new_revision.ineligibilite_rules
end
def i18n_params
{
previous_condition: @previous_ineligibilite_rules&.to_s(previous_revision.types_de_champ.filter { @previous_ineligibilite_rules.sources.include? _1.stable_id }),
new_condition: @new_ineligibilite_rules&.to_s(new_revision.types_de_champ.filter { @new_ineligibilite_rules.sources.include? _1.stable_id })
}
end
end
class AddEligibiliteRuleChange < EligibiliteRulesChange
def op = :add
end
class RemoveEligibiliteRuleChange < EligibiliteRulesChange
def op = :remove
end
class UpdateEligibiliteRuleChange < EligibiliteRulesChange
def op = :update
end
class EligibiliteEnabledChange < EligibiliteRulesChange
def op = :enabled
def i18n_params = {}
end
class EligibiliteDisabledChange < EligibiliteRulesChange
def op = :disabled
def i18n_params = {}
end
class UpdateEligibiliteMessageChange < EligibiliteRulesChange
def op = :message_updated
def i18n_params = { ineligibilite_message: @new_revision.ineligibilite_message }
end
end

View file

@ -8,7 +8,7 @@
%p.mb-2= t('.draft_changed_procedure_alert')
= render Dsfr::AlertComponent.new(state: :info, size: :sm, extra_class_names: 'fr-mb-2w') do |c|
- c.with_body do
= render Procedure::RevisionChangesComponent.new changes: procedure.revision_changes, previous_revision: procedure.published_revision
= render Procedure::RevisionChangesComponent.new new_revision: procedure.draft_revision, previous_revision: procedure.published_revision
- if procedure.close?
= render partial: 'publication_form_inputs', locals: { procedure: procedure, closed_procedures: @closed_procedures, form: f }
- elsif @procedure.brouillon? && @procedure.missing_steps.empty?

View file

@ -13,7 +13,6 @@
- previous_revision = nil
- @procedure.revisions.each do |revision|
- if previous_revision.present? && !revision.draft?
- changes = previous_revision.compare(revision)
- dossiers = revision.dossiers.visible_by_administration
- dossiers_en_construction_count = dossiers.state_en_construction.count
- dossiers_en_instruction_count = dossiers.state_en_instruction.count
@ -31,7 +30,7 @@
%p= t('.dossiers_en_construction', count: dossiers_en_construction_count)
- elsif !dossiers_en_instruction_count.zero?
%p= t('.dossiers_en_instruction', count: dossiers_en_instruction_count)
= render Procedure::RevisionChangesComponent.new changes:, previous_revision:
= render Procedure::RevisionChangesComponent.new new_revision: revision, previous_revision:
- previous_revision = revision
= render Procedure::FixedFooterComponent.new(procedure: @procedure)

View file

@ -30,8 +30,8 @@
- if @procedure.draft_changed?
= render Dsfr::CalloutComponent.new(title: t(:has_changes, scope: [:administrateurs, :revision_changes]), icon: "fr-fi-information-line") do |c|
- c.with_body do
= render Procedure::RevisionChangesComponent.new changes: @procedure.revision_changes, previous_revision: @procedure.published_revision
= render Procedure::ErrorsSummary.new(procedure: @procedure, validation_context: :publication)
= render Procedure::RevisionChangesComponent.new new_revision: @procedure.draft_revision, previous_revision: @procedure.published_revision
- c.with_bottom do
%ul.fr-mt-2w.fr-btns-group.fr-btns-group--inline

View file

@ -347,14 +347,14 @@ describe ProcedureRevision do
end
end
describe '#compare' do
describe '#compare_types_de_champ' do
include Logic
let(:new_draft) { procedure.create_new_revision }
subject { procedure.active_revision.compare_types_de_champ(new_draft.reload).map(&:to_h) }
describe 'when tdcs changes' do
let(:first_tdc) { draft.types_de_champ_public.first }
let(:second_tdc) { draft.types_de_champ_public.second }
let(:new_draft) { procedure.create_new_revision }
subject { procedure.active_revision.compare(new_draft.reload).map(&:to_h) }
context 'with a procedure with 2 tdcs' do
let(:procedure) do
@ -650,6 +650,117 @@ describe ProcedureRevision do
end
end
end
end
describe 'compare_ineligibilite_rules' do
include Logic
let(:new_draft) { procedure.create_new_revision }
subject { procedure.active_revision.compare_ineligibilite_rules(new_draft.reload) }
context 'when ineligibilite_rules changes' do
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
let(:types_de_champ_public) { [{ type: :yes_no }] }
let(:yes_no_tdc) { new_draft.types_de_champ_public.first }
context 'when nothing changed' do
it { is_expected.to be_empty }
end
context 'when ineligibilite_rules added' do
before do
new_draft.update!(ineligibilite_rules: ds_eq(champ_value(yes_no_tdc.stable_id), constant(true)))
end
it { is_expected.to include(an_instance_of(ProcedureRevisionChange::AddEligibiliteRuleChange)) }
end
context 'when ineligibilite_rules removed' do
before do
procedure.published_revision.update!(ineligibilite_rules: ds_eq(champ_value(yes_no_tdc.stable_id), constant(true)))
end
it { is_expected.to include(an_instance_of(ProcedureRevisionChange::RemoveEligibiliteRuleChange)) }
end
context 'when ineligibilite_rules changed' do
before do
procedure.published_revision.update!(ineligibilite_rules: ds_eq(champ_value(yes_no_tdc.stable_id), constant(true)))
new_draft.update!(ineligibilite_rules: ds_and([
ds_eq(champ_value(yes_no_tdc.stable_id), constant(true)),
empty_operator(empty, empty)
]))
end
it { is_expected.to include(an_instance_of(ProcedureRevisionChange::UpdateEligibiliteRuleChange)) }
end
context 'when when ineligibilite_enabled changes from false to true' do
before do
procedure.published_revision.update!(ineligibilite_enabled: false, ineligibilite_message: :required)
new_draft.update!(ineligibilite_enabled: true, ineligibilite_message: :required)
end
it { is_expected.to include(an_instance_of(ProcedureRevisionChange::EligibiliteEnabledChange)) }
end
context 'when ineligibilite_enabled changes from true to false' do
before do
procedure.published_revision.update!(ineligibilite_enabled: true, ineligibilite_message: :required)
new_draft.update!(ineligibilite_enabled: false, ineligibilite_message: :required)
end
it { is_expected.to include(an_instance_of(ProcedureRevisionChange::EligibiliteDisabledChange)) }
end
context 'when ineligibilite_message changes' do
before do
procedure.published_revision.update!(ineligibilite_message: :a)
new_draft.update!(ineligibilite_message: :b)
end
it { is_expected.to include(an_instance_of(ProcedureRevisionChange::UpdateEligibiliteMessageChange)) }
end
end
end
describe 'ineligibilite_rules_are_valid?' do
include Logic
let(:procedure) { create(:procedure) }
let(:draft_revision) { procedure.draft_revision }
let(:ineligibilite_message) { 'ok' }
let(:ineligibilite_enabled) { true }
before do
procedure.draft_revision.update(ineligibilite_rules:, ineligibilite_message:, ineligibilite_enabled:)
end
context 'when ineligibilite_rules are valid' do
let(:ineligibilite_rules) { ds_eq(constant(true), constant(true)) }
it 'is valid' do
expect(draft_revision.validate(:publication)).to be_truthy
expect(draft_revision.validate(:ineligibilite_rules_editor)).to be_truthy
end
end
context 'when ineligibilite_rules are invalid on simple champ' do
let(:ineligibilite_rules) { ds_eq(constant(true), constant(1)) }
it 'is invalid' do
expect(draft_revision.validate(:publication)).to be_falsey
expect(draft_revision.validate(:ineligibilite_rules_editor)).to be_falsey
end
end
context 'when ineligibilite_rules are invalid on repetition champ' do
let(:ineligibilite_rules) { ds_eq(constant(true), constant(1)) }
let(:procedure) { create(:procedure, types_de_champ_public:) }
let(:types_de_champ_public) { [{ type: :repetition, children: [{ type: :integer_number }] }] }
let(:tdc_number) { draft_revision.types_de_champ_for(scope: :public).find { _1.type_champ == 'integer_number' } }
let(:ineligibilite_rules) do
ds_eq(champ_value(tdc_number.stable_id), constant(true))
end
it 'is invalid' do
expect(draft_revision.validate(:publication)).to be_falsey
expect(draft_revision.validate(:ineligibilite_rules_editor)).to be_falsey
end
end
end
describe 'children_of' do
context 'with a simple tdc' do