feat(revisions): rebase dossiers brouillons

This commit is contained in:
Paul Chavard 2021-10-13 14:41:05 +02:00
parent 09a09d3fcf
commit 2a3a9dd822
6 changed files with 204 additions and 0 deletions

View file

@ -0,0 +1,8 @@
class DossierRebaseJob < ApplicationJob
# If by the time the job runs the Dossier has been deleted, ignore the rebase
discard_on ActiveRecord::RecordNotFound
def perform(dossier)
dossier.rebase!
end
end

View file

@ -0,0 +1,121 @@
module DossierRebaseConcern
extend ActiveSupport::Concern
def rebase!
if brouillon? && revision != procedure.published_revision
transaction do
rebase
end
end
end
private
def rebase
attachments_to_purge = []
geo_areas_to_delete = []
changes_by_type_de_champ = revision.compare(procedure.published_revision).group_by { |change| change[:stable_id] }
changes_by_type_de_champ.each do |stable_id, changes|
type_de_champ = find_type_de_champ_by_stable_id(stable_id)
published_type_de_champ = find_type_de_champ_by_stable_id(stable_id, published: true)
changes.each do |change|
case change[:op]
when :add
add_new_champs_for_revision(published_type_de_champ)
when :remove
delete_champs_for_revision(type_de_champ)
end
end
end
flattened_all_champs.each do |champ|
changes_by_stable_id = (changes_by_type_de_champ[champ.stable_id] || [])
.filter { |change| change[:op] == :update }
update_champ_for_revision(champ) do |update|
changes_by_stable_id.each do |change|
case change[:attribute]
when :type_champ
update[:type] = "Champs::#{change[:to].classify}Champ"
update[:value] = nil
update[:external_id] = nil
update[:data] = nil
geo_areas_to_delete += champ.geo_areas
if champ.piece_justificative_file.attached?
attachments_to_purge << champ.piece_justificative_file
end
when :drop_down_options
update[:value] = nil
when :carte_layers
geo_areas_to_delete += champ.geo_areas
end
update[:rebased_at] = Time.zone.now
end
end
end
self.update_column(:revision_id, procedure.published_revision_id)
attachments_to_purge.each(&:purge_later)
geo_areas_to_delete.each(&:destroy)
end
def add_new_champs_for_revision(published_type_de_champ)
if published_type_de_champ.parent
find_champs_by_stable_id(published_type_de_champ.parent.stable_id).each do |champ_repetition|
champ_repetition.rows.size.times do |row|
champ = published_type_de_champ.champ.build(row: row)
champ_repetition.champs << champ
end
end
else
champ = published_type_de_champ.build_champ
self.champs << champ
end
end
def update_champ_for_revision(champ)
published_type_de_champ = find_type_de_champ_by_stable_id(champ.stable_id, published: true)
return if !published_type_de_champ
update = {}
yield update
if champ.type_de_champ != published_type_de_champ
update[:type_de_champ_id] = published_type_de_champ.id
end
if update.present?
champ.update_columns(update)
end
end
def delete_champs_for_revision(published_type_de_champ)
Champ.where(id: find_champs_by_stable_id(published_type_de_champ.stable_id).map(&:id))
.destroy_all
end
def flattened_all_types_de_champ(published: false)
revision = published ? procedure.published_revision : self.revision
types_de_champ = revision.types_de_champ + revision.types_de_champ_private
(types_de_champ + types_de_champ.filter(&:repetition?).flat_map(&:types_de_champ))
.index_by(&:stable_id)
end
def flattened_all_champs
all_champs = (champs + champs_private)
all_champs + all_champs.filter(&:repetition?).flat_map(&:champs)
end
def find_type_de_champ_by_stable_id(stable_id, published: false)
flattened_all_types_de_champ(published: published)[stable_id]
end
def find_champs_by_stable_id(stable_id)
flattened_all_champs.filter do |champ|
champ.stable_id == stable_id
end
end
end

View file

@ -36,6 +36,7 @@
class Dossier < ApplicationRecord
self.ignored_columns = [:en_construction_conservation_extension]
include DossierFilteringConcern
include DossierRebaseConcern
include Discard::Model
self.discard_column = :hidden_at

View file

@ -714,6 +714,9 @@ class Procedure < ApplicationRecord
def publish_revision!
update!(draft_revision: create_new_revision, published_revision: draft_revision)
published_revision.touch(:published_at)
dossiers.state_brouillon.find_each do |dossier|
DossierRebaseJob.perform_later(dossier)
end
end
def cnaf_enabled?

View file

@ -5,3 +5,7 @@
- if champ.updated_at.present? && seen_at.present?
%span.updated-at{ class: highlight_if_unseen_class(seen_at, champ.updated_at) }
= "modifié le #{try_format_datetime(champ.updated_at)}"
- if champ.rebased_at.present? && champ.rebased_at > (seen_at || champ.updated_at) && current_user.owns_or_invite?(champ.dossier)
%span.updated-at.highlighted
Le type de ce champ où sa description a été modifiée par l'administration. Vérifier son contenu.

View file

@ -1459,4 +1459,71 @@ describe Dossier do
it { expect(dossier.spreadsheet_columns(types_de_champ: [])).to include(["État du dossier", "Brouillon"]) }
end
describe "#rebase" do
let(:procedure) { create(:procedure, :with_type_de_champ_mandatory, :with_yes_no, :with_repetition, :with_datetime) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:yes_no_type_de_champ) { procedure.types_de_champ.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:yes_no) } }
let(:text_type_de_champ) { procedure.types_de_champ.find(&:mandatory?) }
let(:text_champ) { dossier.champs.find(&:mandatory?) }
let(:rebased_text_champ) { dossier.champs.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:text) } }
let(:datetime_type_de_champ) { procedure.types_de_champ.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } }
let(:datetime_champ) { dossier.champs.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } }
let(:rebased_datetime_champ) { dossier.champs.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:date) } }
let(:repetition_type_de_champ) { procedure.types_de_champ.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:repetition) } }
let(:repetition_text_type_de_champ) { repetition_type_de_champ.types_de_champ.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:text) } }
let(:repetition_champ) { dossier.champs.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:repetition) } }
let(:rebased_repetition_champ) { dossier.champs.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:repetition) } }
before do
procedure.publish!
procedure.draft_revision.add_type_de_champ({
type_champ: TypeDeChamp.type_champs.fetch(:text),
libelle: "Un champ text"
})
procedure.draft_revision.find_or_clone_type_de_champ(text_type_de_champ).update(mandatory: false, libelle: "nouveau libelle")
procedure.draft_revision.find_or_clone_type_de_champ(datetime_type_de_champ).update(type_champ: TypeDeChamp.type_champs.fetch(:date))
procedure.draft_revision.find_or_clone_type_de_champ(repetition_text_type_de_champ).update(libelle: "nouveau libelle dans une repetition")
procedure.draft_revision.add_type_de_champ({
type_champ: TypeDeChamp.type_champs.fetch(:checkbox),
libelle: "oui ou non",
parent_id: repetition_type_de_champ.stable_id
})
procedure.draft_revision.remove_type_de_champ(yes_no_type_de_champ.stable_id)
datetime_champ.update(value: Date.today.to_s)
text_champ.update(value: 'bonjour')
end
it "updates the brouillon champs with the latest revision changes" do
revision_id = dossier.revision_id
libelle = text_type_de_champ.libelle
expect(dossier.revision).to eq(procedure.published_revision)
expect(dossier.champs.size).to eq(4)
expect(repetition_champ.rows.size).to eq(1)
expect(repetition_champ.rows[0].size).to eq(1)
procedure.publish_revision!
perform_enqueued_jobs
procedure.reload
dossier.reload
expect(procedure.revisions.size).to eq(3)
expect(dossier.revision).to eq(procedure.published_revision)
expect(dossier.champs.size).to eq(4)
expect(rebased_text_champ.value).to eq(text_champ.value)
expect(rebased_text_champ.type_de_champ_id).not_to eq(text_champ.type_de_champ_id)
expect(rebased_datetime_champ.type_champ).to eq(TypeDeChamp.type_champs.fetch(:date))
expect(rebased_datetime_champ.value).to be_nil
expect(rebased_repetition_champ.rows.size).to eq(1)
expect(rebased_repetition_champ.rows[0].size).to eq(2)
expect(rebased_text_champ.rebased_at).not_to be_nil
expect(rebased_datetime_champ.rebased_at).not_to be_nil
end
end
end