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
|
||||
|
||||
def rebase
|
||||
attachments_to_purge = []
|
||||
geo_areas_to_delete = []
|
||||
champs_to_delete = []
|
||||
|
||||
# revision we are rebasing to
|
||||
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
|
||||
target_coordinates_by_stable_id = target_revision
|
||||
.revision_types_de_champ
|
||||
.includes(:type_de_champ, :parent)
|
||||
.index_by(&:stable_id)
|
||||
|
||||
# add and remove champs
|
||||
changes_by_stable_id.each do |stable_id, changes|
|
||||
type_de_champ = current_types_de_champ_by_stable_id[stable_id]
|
||||
published_coordinate = target_coordinates_by_stable_id[stable_id]
|
||||
changes_by_op = pending_changes
|
||||
.filter { |change| change[:model] == :type_de_champ }
|
||||
.group_by { |change| change[:op] }
|
||||
.tap { |h| h.default = [] }
|
||||
|
||||
changes.each do |change|
|
||||
case change[:op]
|
||||
when :add
|
||||
add_new_champs_for_revision(published_coordinate)
|
||||
when :remove
|
||||
delete_champs_for_revision(type_de_champ)
|
||||
end
|
||||
end
|
||||
end
|
||||
# add champ
|
||||
changes_by_op[:add]
|
||||
.map { |change| change[:stable_id] }
|
||||
.map { |stable_id| target_coordinates_by_stable_id[stable_id] }
|
||||
.each { |coordinate| add_new_champs_for_revision(coordinate) }
|
||||
|
||||
# find all champs with respective update changes and the published type de champ
|
||||
champs_with_changes = Champ.where(dossier: self).includes(:type_de_champ).filter_map do |champ|
|
||||
# type de champ from published revision
|
||||
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 }
|
||||
# remove champ
|
||||
changes_by_op[:remove]
|
||||
.each { |change| delete_champs_for_revision(change[:stable_id]) }
|
||||
|
||||
if type_de_champ
|
||||
[champ, type_de_champ, changes]
|
||||
end
|
||||
end
|
||||
changes_by_op[:update]
|
||||
.map { |change| [change, Champ.joins(:type_de_champ).where(dossier: self, type_de_champ: { stable_id: change[:stable_id] })] }
|
||||
.each { |change, champs| apply(change, champs) }
|
||||
|
||||
# apply changes to existing champs and reset values when needed
|
||||
update_champs_for_revision(champs_with_changes) do |champ, change, update|
|
||||
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
|
||||
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
|
||||
# due to repetition tdc clone on update or erase
|
||||
# we must reassign tdc to the latest version
|
||||
Champ
|
||||
.includes(:type_de_champ)
|
||||
.where(dossier: self)
|
||||
.map { |c| [c, target_coordinates_by_stable_id[c.stable_id].type_de_champ] }
|
||||
.each { |c, target_tdc| c.update_columns(type_de_champ_id: target_tdc.id, rebased_at: Time.zone.now) }
|
||||
|
||||
# update dossier revision
|
||||
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
|
||||
|
||||
def add_new_champs_for_revision(published_coordinate)
|
||||
if published_coordinate.child?
|
||||
def apply(change, champs)
|
||||
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
|
||||
parent_stable_id = published_coordinate.parent.stable_id
|
||||
parent_stable_id = target_coordinate.parent.stable_id
|
||||
champs_repetition = Champ
|
||||
.includes(:champs, :type_de_champ)
|
||||
.where(dossier: self, type_de_champ: { stable_id: parent_stable_id })
|
||||
|
||||
champs_repetition.each do |champ_repetition|
|
||||
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
|
||||
else
|
||||
create_champ(published_coordinate, self)
|
||||
create_champ(target_coordinate, self)
|
||||
end
|
||||
end
|
||||
|
||||
def create_champ(published_coordinate, parent, row: nil)
|
||||
params = { revision: published_coordinate.revision }
|
||||
def create_champ(target_coordinate, parent, row: nil)
|
||||
params = { revision: target_coordinate.revision }
|
||||
params[:row] = row if row
|
||||
champ = published_coordinate
|
||||
champ = target_coordinate
|
||||
.type_de_champ
|
||||
.build_champ(params)
|
||||
parent.champs << champ
|
||||
end
|
||||
|
||||
def update_champs_for_revision(champs_with_changes)
|
||||
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)
|
||||
def delete_champs_for_revision(stable_id)
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -233,6 +233,50 @@ describe Dossier do
|
|||
dossier.reload
|
||||
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
|
||||
let!(:procedure) do
|
||||
create(:procedure).tap do |p|
|
||||
|
@ -256,16 +300,27 @@ describe Dossier do
|
|||
|
||||
context 'when the first tdc is removed' 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)
|
||||
end
|
||||
|
||||
it { expect { subject }.to change { champ_libelles }.from(['l1', 'l2']).to(['l2']) }
|
||||
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
|
||||
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')
|
||||
end
|
||||
|
||||
|
@ -273,19 +328,39 @@ describe Dossier do
|
|||
end
|
||||
|
||||
context 'when the first tdc type is updated' do
|
||||
def first_champ = dossier.champs.first
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
context 'with a procedure with a repetition' do
|
||||
let!(:procedure) do
|
||||
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: 'c2', parent_id: repetition.stable_id)
|
||||
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']) }
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue