complete spec and alternative implem
This commit is contained in:
parent
abc3681053
commit
db5d291acd
2 changed files with 148 additions and 97 deletions
|
@ -50,137 +50,102 @@ module DossierRebaseConcern
|
||||||
end
|
end
|
||||||
|
|
||||||
def rebase
|
def rebase
|
||||||
attachments_to_purge = []
|
|
||||||
geo_areas_to_delete = []
|
|
||||||
champs_to_delete = []
|
|
||||||
|
|
||||||
# revision we are rebasing to
|
# revision we are rebasing to
|
||||||
target_revision = procedure.published_revision
|
target_revision = procedure.published_revision
|
||||||
|
|
||||||
# group changes by stable_id
|
|
||||||
# { 12 : [ move, update_libelle, update_mandatory ]
|
|
||||||
changes_by_stable_id = pending_changes
|
|
||||||
.filter { |change| change[:model] == :type_de_champ }
|
|
||||||
.group_by { |change| change[:stable_id] }
|
|
||||||
|
|
||||||
# index current revision types de champ by stable_id
|
|
||||||
current_types_de_champ_by_stable_id = revision.types_de_champ.index_by(&:stable_id)
|
|
||||||
|
|
||||||
# index published types de champ coordinates by stable_id
|
# index published types de champ coordinates by stable_id
|
||||||
target_coordinates_by_stable_id = target_revision
|
target_coordinates_by_stable_id = target_revision
|
||||||
.revision_types_de_champ
|
.revision_types_de_champ
|
||||||
.includes(:type_de_champ, :parent)
|
.includes(:type_de_champ, :parent)
|
||||||
.index_by(&:stable_id)
|
.index_by(&:stable_id)
|
||||||
|
|
||||||
# add and remove champs
|
changes_by_op = pending_changes
|
||||||
changes_by_stable_id.each do |stable_id, changes|
|
.filter { |change| change[:model] == :type_de_champ }
|
||||||
type_de_champ = current_types_de_champ_by_stable_id[stable_id]
|
.group_by { |change| change[:op] }
|
||||||
published_coordinate = target_coordinates_by_stable_id[stable_id]
|
.tap { |h| h.default = [] }
|
||||||
|
|
||||||
changes.each do |change|
|
# add champ
|
||||||
case change[:op]
|
changes_by_op[:add]
|
||||||
when :add
|
.map { |change| change[:stable_id] }
|
||||||
add_new_champs_for_revision(published_coordinate)
|
.map { |stable_id| target_coordinates_by_stable_id[stable_id] }
|
||||||
when :remove
|
.each { |coordinate| add_new_champs_for_revision(coordinate) }
|
||||||
delete_champs_for_revision(type_de_champ)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# find all champs with respective update changes and the published type de champ
|
# remove champ
|
||||||
champs_with_changes = Champ.where(dossier: self).includes(:type_de_champ).filter_map do |champ|
|
changes_by_op[:remove]
|
||||||
# type de champ from published revision
|
.each { |change| delete_champs_for_revision(change[:stable_id]) }
|
||||||
type_de_champ = target_coordinates_by_stable_id[champ.stable_id]&.type_de_champ
|
|
||||||
# only update op changes
|
|
||||||
changes = (changes_by_stable_id[champ.stable_id] || []).filter { |change| change[:op] == :update }
|
|
||||||
|
|
||||||
if type_de_champ
|
changes_by_op[:update]
|
||||||
[champ, type_de_champ, changes]
|
.map { |change| [change, Champ.joins(:type_de_champ).where(dossier: self, type_de_champ: { stable_id: change[:stable_id] })] }
|
||||||
end
|
.each { |change, champs| apply(change, champs) }
|
||||||
end
|
|
||||||
|
|
||||||
# apply changes to existing champs and reset values when needed
|
# due to repetition tdc clone on update or erase
|
||||||
update_champs_for_revision(champs_with_changes) do |champ, change, update|
|
# we must reassign tdc to the latest version
|
||||||
case change[:attribute]
|
Champ
|
||||||
when :type_champ
|
.includes(:type_de_champ)
|
||||||
update[:type] = "Champs::#{change[:to].classify}Champ"
|
.where(dossier: self)
|
||||||
update[:value] = nil
|
.map { |c| [c, target_coordinates_by_stable_id[c.stable_id].type_de_champ] }
|
||||||
update[:external_id] = nil
|
.each { |c, target_tdc| c.update_columns(type_de_champ_id: target_tdc.id, rebased_at: Time.zone.now) }
|
||||||
update[:data] = nil
|
|
||||||
geo_areas_to_delete += champ.geo_areas
|
|
||||||
champs_to_delete += champ.champs
|
|
||||||
if champ.piece_justificative_file.attached?
|
|
||||||
attachments_to_purge << champ.piece_justificative_file
|
|
||||||
end
|
|
||||||
when :drop_down_options
|
|
||||||
update[:value] = nil
|
|
||||||
when :carte_layers
|
|
||||||
# if we are removing cadastres layer, we need to remove cadastre geo areas
|
|
||||||
if change[:from].include?(:cadastres) && !change[:to].include?(:cadastres)
|
|
||||||
geo_areas_to_delete += champ.cadastres
|
|
||||||
end
|
|
||||||
end
|
|
||||||
update[:rebased_at] = Time.zone.now
|
|
||||||
end
|
|
||||||
|
|
||||||
# update dossier revision
|
# update dossier revision
|
||||||
self.update_column(:revision_id, target_revision.id)
|
self.update_column(:revision_id, target_revision.id)
|
||||||
|
|
||||||
# clear orphaned data
|
|
||||||
attachments_to_purge.each(&:purge_later)
|
|
||||||
geo_areas_to_delete.each(&:destroy)
|
|
||||||
champs_to_delete.each(&:destroy)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_new_champs_for_revision(published_coordinate)
|
def apply(change, champs)
|
||||||
if published_coordinate.child?
|
case change[:attribute]
|
||||||
|
when :type_champ
|
||||||
|
champs.each { |champ| champ.piece_justificative_file.purge_later }
|
||||||
|
GeoArea.where(champ: champs).destroy_all
|
||||||
|
|
||||||
|
{
|
||||||
|
type: "Champs::#{change[:to].classify}Champ",
|
||||||
|
value: nil,
|
||||||
|
external_id: nil,
|
||||||
|
data: nil
|
||||||
|
}
|
||||||
|
when :drop_down_options
|
||||||
|
{ value: nil }
|
||||||
|
when :carte_layers
|
||||||
|
# if we are removing cadastres layer, we need to remove cadastre geo areas
|
||||||
|
if change[:from].include?(:cadastres) && !change[:to].include?(:cadastres)
|
||||||
|
champs.each { |champ| champ.cadastres.each(&:destroy) }
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
&.then { |update_params| champs.update_all(update_params) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_new_champs_for_revision(target_coordinate)
|
||||||
|
if target_coordinate.child?
|
||||||
# If this type de champ is a child, we create a new champ for each row of the parent
|
# If this type de champ is a child, we create a new champ for each row of the parent
|
||||||
parent_stable_id = published_coordinate.parent.stable_id
|
parent_stable_id = target_coordinate.parent.stable_id
|
||||||
champs_repetition = Champ
|
champs_repetition = Champ
|
||||||
.includes(:champs, :type_de_champ)
|
.includes(:champs, :type_de_champ)
|
||||||
.where(dossier: self, type_de_champ: { stable_id: parent_stable_id })
|
.where(dossier: self, type_de_champ: { stable_id: parent_stable_id })
|
||||||
|
|
||||||
champs_repetition.each do |champ_repetition|
|
champs_repetition.each do |champ_repetition|
|
||||||
champ_repetition.champs.map(&:row).uniq.each do |row|
|
champ_repetition.champs.map(&:row).uniq.each do |row|
|
||||||
create_champ(published_coordinate, champ_repetition, row: row)
|
create_champ(target_coordinate, champ_repetition, row: row)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
create_champ(published_coordinate, self)
|
create_champ(target_coordinate, self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_champ(published_coordinate, parent, row: nil)
|
def create_champ(target_coordinate, parent, row: nil)
|
||||||
params = { revision: published_coordinate.revision }
|
params = { revision: target_coordinate.revision }
|
||||||
params[:row] = row if row
|
params[:row] = row if row
|
||||||
champ = published_coordinate
|
champ = target_coordinate
|
||||||
.type_de_champ
|
.type_de_champ
|
||||||
.build_champ(params)
|
.build_champ(params)
|
||||||
parent.champs << champ
|
parent.champs << champ
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_champs_for_revision(champs_with_changes)
|
def delete_champs_for_revision(stable_id)
|
||||||
champs_with_changes.each do |champ, type_de_champ, changes|
|
|
||||||
update = {}
|
|
||||||
|
|
||||||
changes.each do |change|
|
|
||||||
yield champ, change, update
|
|
||||||
end
|
|
||||||
|
|
||||||
# update type de champ to reflect new revision
|
|
||||||
if champ.type_de_champ != type_de_champ
|
|
||||||
update[:type_de_champ_id] = type_de_champ.id
|
|
||||||
end
|
|
||||||
|
|
||||||
if update.present?
|
|
||||||
champ.update_columns(update)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_champs_for_revision(type_de_champ)
|
|
||||||
Champ
|
Champ
|
||||||
.where(dossier: self, type_de_champ: type_de_champ)
|
.joins(:type_de_champ)
|
||||||
|
.where(dossier: self, types_de_champ: { stable_id: stable_id })
|
||||||
.destroy_all
|
.destroy_all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -233,6 +233,50 @@ describe Dossier do
|
||||||
dossier.reload
|
dossier.reload
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a procedure with a dropdown tdc' do
|
||||||
|
let!(:procedure) do
|
||||||
|
create(:procedure).tap do |p|
|
||||||
|
p.draft_revision.add_type_de_champ(type_champ: :drop_down_list, libelle: 'l1', drop_down_list_value: 'option')
|
||||||
|
p.publish!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
let!(:dossier) { create(:dossier, procedure: procedure) }
|
||||||
|
|
||||||
|
context 'when a dropdown option is changed' do
|
||||||
|
before do
|
||||||
|
dossier.champs.first.update(value: 'v1')
|
||||||
|
|
||||||
|
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
|
||||||
|
tdc_to_update = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
|
||||||
|
tdc_to_update.update(drop_down_list_value: 'option updated')
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect { subject }.to change { dossier.champs.first.value }.from('v1').to(nil) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a procedure with a carte tdc' do
|
||||||
|
let!(:procedure) do
|
||||||
|
create(:procedure).tap do |p|
|
||||||
|
champ = p.draft_revision.add_type_de_champ(type_champ: :carte, libelle: 'l1', cadastres: true)
|
||||||
|
p.publish!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
let!(:dossier) { create(:dossier, procedure: procedure) }
|
||||||
|
|
||||||
|
context 'and the cadastre are removed' do
|
||||||
|
before do
|
||||||
|
dossier.champs.first.update(value: 'v1', geo_areas: [create(:geo_area, :cadastre)])
|
||||||
|
|
||||||
|
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
|
||||||
|
tdc_to_update = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
|
||||||
|
tdc_to_update.update(cadastres: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect { subject }.to change { dossier.champs.first.cadastres.count }.from(1).to(0) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with a procedure with 2 tdc' do
|
context 'with a procedure with 2 tdc' do
|
||||||
let!(:procedure) do
|
let!(:procedure) do
|
||||||
create(:procedure).tap do |p|
|
create(:procedure).tap do |p|
|
||||||
|
@ -256,16 +300,27 @@ describe Dossier do
|
||||||
|
|
||||||
context 'when the first tdc is removed' do
|
context 'when the first tdc is removed' do
|
||||||
before do
|
before do
|
||||||
tdc_to_remove = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
|
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
|
||||||
|
tdc_to_remove = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
|
||||||
procedure.draft_revision.remove_type_de_champ(tdc_to_remove.stable_id)
|
procedure.draft_revision.remove_type_de_champ(tdc_to_remove.stable_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect { subject }.to change { champ_libelles }.from(['l1', 'l2']).to(['l2']) }
|
it { expect { subject }.to change { champ_libelles }.from(['l1', 'l2']).to(['l2']) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the second tdc is moved at the first place' do
|
||||||
|
before do
|
||||||
|
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l2')
|
||||||
|
procedure.draft_revision.move_type_de_champ(stable_id, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect { subject }.to change { champ_libelles }.from(['l1', 'l2']).to(['l2', 'l1']) }
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the first tdc libelle is updated' do
|
context 'when the first tdc libelle is updated' do
|
||||||
before do
|
before do
|
||||||
tdc_to_update = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
|
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
|
||||||
|
tdc_to_update = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
|
||||||
tdc_to_update.update(libelle: 'l1 updated')
|
tdc_to_update.update(libelle: 'l1 updated')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -273,19 +328,39 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the first tdc type is updated' do
|
context 'when the first tdc type is updated' do
|
||||||
|
def first_champ = dossier.champs.first
|
||||||
|
|
||||||
before do
|
before do
|
||||||
tdc_to_update = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
|
first_champ.update(value: 'v1', external_id: '123', geo_areas: [create(:geo_area)])
|
||||||
|
first_champ.update(data: { a: 1 })
|
||||||
|
|
||||||
|
first_champ.piece_justificative_file.attach(
|
||||||
|
io: StringIO.new("toto"),
|
||||||
|
filename: "toto.txt",
|
||||||
|
content_type: "text/plain",
|
||||||
|
# we don't want to run virus scanner on this file
|
||||||
|
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
|
||||||
|
)
|
||||||
|
|
||||||
|
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
|
||||||
|
tdc_to_update = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
|
||||||
tdc_to_update.update(type_champ: :integer_number)
|
tdc_to_update.update(type_champ: :integer_number)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect { subject }.to change { dossier.champs.map(&:type_champ) }.from(['text', 'text']).to(['integer_number', 'text']) }
|
it { expect { subject }.to change { dossier.champs.map(&:type_champ) }.from(['text', 'text']).to(['integer_number', 'text']) }
|
||||||
|
it { expect { subject }.to change { first_champ.class }.from(Champs::TextChamp).to(Champs::IntegerNumberChamp) }
|
||||||
|
it { expect { subject }.to change { first_champ.value }.from('v1').to(nil) }
|
||||||
|
it { expect { subject }.to change { first_champ.external_id }.from('123').to(nil) }
|
||||||
|
it { expect { subject }.to change { first_champ.data }.from({ 'a' => 1 }).to(nil) }
|
||||||
|
it { expect { subject }.to change { first_champ.geo_areas.count }.from(1).to(0) }
|
||||||
|
it { expect { subject }.to change { first_champ.piece_justificative_file.attached? }.from(true).to(false) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a procedure with a repetition' do
|
context 'with a procedure with a repetition' do
|
||||||
let!(:procedure) do
|
let!(:procedure) do
|
||||||
create(:procedure).tap do |p|
|
create(:procedure).tap do |p|
|
||||||
repetition = p.draft_revision.add_type_de_champ(type_champ: :repetition, libelle: 'r1')
|
repetition = p.draft_revision.add_type_de_champ(type_champ: :repetition, libelle: 'p1')
|
||||||
p.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'c1', parent_id: repetition.stable_id)
|
p.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'c1', parent_id: repetition.stable_id)
|
||||||
p.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'c2', parent_id: repetition.stable_id)
|
p.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'c2', parent_id: repetition.stable_id)
|
||||||
p.publish!
|
p.publish!
|
||||||
|
@ -333,6 +408,17 @@ describe Dossier do
|
||||||
|
|
||||||
it { expect { subject }.to change { dossier.champs[0].champs.map(&:type_champ) }.from(['text', 'text']).to(['integer_number', 'text']) }
|
it { expect { subject }.to change { dossier.champs[0].champs.map(&:type_champ) }.from(['text', 'text']).to(['integer_number', 'text']) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the parents type is changed' do
|
||||||
|
before do
|
||||||
|
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'p1')
|
||||||
|
parent = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
|
||||||
|
parent.update(type_champ: :integer_number)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect { subject }.to change { dossier.champs[0].champs.count }.from(2).to(0) }
|
||||||
|
it { expect { subject }.to change { Champ.count }.from(3).to(1) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue