Merge pull request #10902 from tchak/refactor-champs-on-demande

ETQ dev, je ne veux pas créer les champs inutiles (et je veux pouvoir supprimer un row d'une répétition sans supprimer de champs)
This commit is contained in:
Paul Chavard 2024-12-09 09:39:42 +00:00 committed by GitHub
commit 7603b7c1f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 689 additions and 412 deletions

View file

@ -17,7 +17,7 @@ class EditableChamp::SectionComponent < ApplicationComponent
def header_section
node = @nodes.first
@dossier.project_champ(node, @row_id) if node.is_a?(TypeDeChamp) && node.header_section?
@dossier.project_champ(node, row_id: @row_id) if node.is_a?(TypeDeChamp) && node.header_section?
end
def splitted_tail
@ -40,7 +40,7 @@ class EditableChamp::SectionComponent < ApplicationComponent
when EditableChamp::SectionComponent
[node, nil]
else
[nil, @dossier.project_champ(node, @row_id)]
[nil, @dossier.project_champ(node, row_id: @row_id)]
end
end

View file

@ -18,11 +18,11 @@ class ViewableChamp::SectionComponent < ApplicationComponent
def header_section
node = @nodes.first
@dossier.project_champ(node, @row_id) if node.is_a?(TypeDeChamp) && node.header_section?
@dossier.project_champ(node, row_id: @row_id) if node.is_a?(TypeDeChamp) && node.header_section?
end
def champs
tail.filter_map { _1.is_a?(TypeDeChamp) ? @dossier.project_champ(_1, @row_id) : nil }
tail.filter_map { _1.is_a?(TypeDeChamp) ? @dossier.project_champ(_1, row_id: @row_id) : nil }
end
def sections

View file

@ -10,9 +10,9 @@ class Champs::ChampController < ApplicationController
dossier = policy_scope(Dossier).includes(:champs, revision: [:types_de_champ]).find(params[:dossier_id])
type_de_champ = dossier.find_type_de_champ_by_stable_id(params[:stable_id])
if type_de_champ.repetition?
dossier.project_champ(type_de_champ, nil)
dossier.project_champ(type_de_champ)
else
dossier.champ_for_update(type_de_champ, params_row_id, updated_by: current_user.email)
dossier.champ_for_update(type_de_champ, row_id: params_row_id, updated_by: current_user.email)
end
end

View file

@ -308,7 +308,7 @@ module Instructeurs
respond_to do |format|
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_private_attributes_params, dossier.champs.filter(&:private?))
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_private_attributes_params, dossier.project_champs_private_all)
end
end
end
@ -321,7 +321,7 @@ module Instructeurs
def annotation
@dossier = dossier_with_champs(pj_template: false)
type_de_champ = @dossier.find_type_de_champ_by_stable_id(params[:stable_id], :private)
annotation = @dossier.project_champ(type_de_champ, params[:row_id])
annotation = @dossier.project_champ(type_de_champ, row_id: params[:row_id])
respond_to do |format|
format.turbo_stream do

View file

@ -286,7 +286,7 @@ module Users
respond_to do |format|
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.project_champs_public_all)
render :update, layout: false
end
end
@ -299,7 +299,7 @@ module Users
def champ
@dossier = dossier_with_champs(pj_template: false)
type_de_champ = dossier.find_type_de_champ_by_stable_id(params[:stable_id], :public)
champ = dossier.project_champ(type_de_champ, params[:row_id])
champ = dossier.project_champ(type_de_champ, row_id: params[:row_id])
respond_to do |format|
format.turbo_stream do

View file

@ -46,7 +46,7 @@ module Mutations
.find_by(type_champ: annotation_type_champ, stable_id:)
return nil if type_de_champ.nil?
dossier.champ_for_update(type_de_champ, row_id, updated_by: current_administrateur.email)
dossier.champ_for_update(type_de_champ, row_id:, updated_by: current_administrateur.email)
end
def annotation_type_champ

View file

@ -34,7 +34,7 @@ module Mutations
.find_by(type_champ: TypeDeChamp.type_champs.fetch(:repetition), stable_id:)
return nil if type_de_champ.nil?
dossier.project_champ(type_de_champ, nil)
dossier.project_champ(type_de_champ)
end
end
end

View file

@ -92,7 +92,7 @@ class AttestationTemplate < ApplicationRecord
used_tags.filter_map do |used_tag|
corresponding_type_de_champ = types_de_champ_by_tag_id[used_tag]
if corresponding_type_de_champ && dossier.project_champ(corresponding_type_de_champ, nil).blank?
if corresponding_type_de_champ && dossier.project_champ(corresponding_type_de_champ).blank?
corresponding_type_de_champ
end
end

View file

@ -2,7 +2,8 @@
class Champ < ApplicationRecord
include ChampConditionalConcern
include ChampsValidateConcern
include ChampValidateConcern
include ChampRevisionConcern
self.ignored_columns += [:type_de_champ_id, :parent_id]
@ -69,7 +70,11 @@ class Champ < ApplicationRecord
end
def child?
row_id.present?
row_id.present? && !is_type?(TypeDeChamp.type_champs.fetch(:repetition))
end
def row?
row_id.present? && is_type?(TypeDeChamp.type_champs.fetch(:repetition))
end
# used for the `required` html attribute

View file

@ -23,6 +23,14 @@ class Champs::RepetitionChamp < Champ
rows.last&.first&.focusable_input_id
end
def discarded?
discarded_at.present?
end
def discard!
touch(:discarded_at)
end
def search_terms
# The user cannot enter any information here so it doesnt make much sense to search
end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
module ChampRevisionConcern
extend ActiveSupport::Concern
protected
def is_same_type_as_revision?
is_type?(type_de_champ.type_champ)
end
def in_dossier_revision?
dossier.stable_id_in_revision?(stable_id)
end
def in_discarded_row?
if child?
repetition_type_de_champ = dossier.revision.parent_of(type_de_champ)
row_ids = dossier.repetition_row_ids(repetition_type_de_champ)
!row_id.in?(row_ids)
end
end
end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
module ChampsValidateConcern
module ChampValidateConcern
extend ActiveSupport::Concern
included do
@ -16,20 +16,20 @@ module ChampsValidateConcern
def validate_champ_value?
case validation_context
when :champs_public_value
public? && in_dossier_revision? && visible?
public? && can_validate? && visible?
when :champs_private_value
private? && in_dossier_revision? && visible?
private? && can_validate? && visible?
else
false
end
end
def can_validate?
in_dossier_revision? && is_same_type_as_revision? && !row? && !in_discarded_row?
end
def validate_champ_value_or_prefill?
validate_champ_value? || validation_context == :prefill
end
def in_dossier_revision?
dossier.revision.in_revision?(stable_id) && is_type?(type_de_champ.type_champ)
end
end
end

View file

@ -3,8 +3,8 @@
module DossierChampsConcern
extend ActiveSupport::Concern
def project_champ(type_de_champ, row_id)
check_valid_row_id?(type_de_champ, row_id)
def project_champ(type_de_champ, row_id: nil)
check_valid_row_id_on_read?(type_de_champ, row_id)
champ = champs_by_public_id[type_de_champ.public_id(row_id)]
if champ.nil? || !champ.is_type?(type_de_champ.type_champ)
value = type_de_champ.champ_blank?(champ) ? nil : champ.value
@ -17,11 +17,11 @@ module DossierChampsConcern
end
def project_champs_public
@project_champs_public ||= revision.types_de_champ_public.map { project_champ(_1, nil) }
@project_champs_public ||= revision.types_de_champ_public.map { project_champ(_1) }
end
def project_champs_private
@project_champs_private ||= revision.types_de_champ_private.map { project_champ(_1, nil) }
@project_champs_private ||= revision.types_de_champ_private.map { project_champ(_1) }
end
def filled_champs_public
@ -52,6 +52,28 @@ module DossierChampsConcern
filled_champs_public + filled_champs_private
end
def project_champs_public_all
revision.types_de_champ_public.flat_map do |type_de_champ|
champ = project_champ(type_de_champ)
if type_de_champ.repetition?
[champ] + project_rows_for(type_de_champ).flatten
else
champ
end
end
end
def project_champs_private_all
revision.types_de_champ_private.flat_map do |type_de_champ|
champ = project_champ(type_de_champ)
if type_de_champ.repetition?
[champ] + project_rows_for(type_de_champ).flatten
else
champ
end
end
end
def project_rows_for(type_de_champ)
return [] if !type_de_champ.repetition?
@ -59,7 +81,7 @@ module DossierChampsConcern
row_ids = repetition_row_ids(type_de_champ)
row_ids.map do |row_id|
children.map { project_champ(_1, row_id) }
children.map { project_champ(_1, row_id:) }
end
end
@ -79,17 +101,21 @@ module DossierChampsConcern
.types_de_champ
.filter { _1.stable_id.in?(stable_ids) }
.filter { !_1.child?(revision) }
.map { _1.repetition? ? project_champ(_1, nil) : champ_for_update(_1, nil, updated_by: nil) }
.map { _1.repetition? ? project_champ(_1) : champ_for_update(_1, updated_by: nil) }
end
def champ_value_for_tag(type_de_champ, path = :value)
champ = filled_champ(type_de_champ, nil)
champ = if type_de_champ.repetition?
project_champ(type_de_champ)
else
filled_champ(type_de_champ)
end
type_de_champ.champ_value_for_tag(champ, path)
end
def champ_for_update(type_de_champ, row_id, updated_by:)
champ, attributes = champ_with_attributes_for_update(type_de_champ, row_id, updated_by:)
champ.assign_attributes(attributes)
def champ_for_update(type_de_champ, row_id: nil, updated_by:)
champ = champ_upsert_by!(type_de_champ, row_id)
champ.updated_by = updated_by
champ
end
@ -109,30 +135,42 @@ module DossierChampsConcern
def repetition_row_ids(type_de_champ)
return [] if !type_de_champ.repetition?
return [] if !revision.in_revision?(type_de_champ.stable_id)
return [] unless stable_id_in_revision?(type_de_champ.stable_id)
@repetition_row_ids ||= {}
@repetition_row_ids[type_de_champ.stable_id] ||= begin
rows = champs_in_revision.filter { _1.row? && _1.stable_id == type_de_champ.stable_id }
row_ids = rows.reject(&:discarded?).map(&:row_id)
stable_ids = revision.children_of(type_de_champ).map(&:stable_id)
champs.filter { _1.stable_id.in?(stable_ids) && _1.row_id.present? }
.map(&:row_id)
.uniq
.sort
# Legacy rows are rows that have been created before the introduction of the discarded_at column
# TODO migrate and clean
children_stable_ids = revision.children_of(type_de_champ).map(&:stable_id)
discarded_row_ids = rows.filter(&:discarded?).map(&:row_id)
legacy_row_ids = champs_in_revision.filter { _1.stable_id.in?(children_stable_ids) && _1.row_id.present? }.map(&:row_id).uniq
row_ids += (legacy_row_ids - discarded_row_ids)
row_ids.uniq.sort
end
end
def repetition_add_row(type_de_champ, updated_by:)
raise "Can't add row to non-repetition type de champ" if !type_de_champ.repetition?
row_id = ULID.generate
types_de_champ = revision.children_of(type_de_champ)
self.champs += types_de_champ.map { _1.build_champ(row_id:, updated_by:) }
reload_champs_cache
champ = champ_for_update(type_de_champ, row_id:, updated_by:)
champ.save!
reset_champ_cache(champ)
row_id
end
def repetition_remove_row(type_de_champ, row_id, updated_by:)
raise "Can't remove row from non-repetition type de champ" if !type_de_champ.repetition?
champs.where(row_id:).destroy_all
reload_champs_cache
champ = champ_for_update(type_de_champ, row_id:, updated_by:)
champ.discard!
reset_champ_cache(champ)
end
def stable_id_in_revision?(stable_id)
revision_stable_ids.member?(stable_id.to_i)
end
def reload
@ -142,10 +180,18 @@ module DossierChampsConcern
private
def champs_by_public_id
@champs_by_public_id ||= champs.sort_by(&:id).index_by(&:public_id)
@champs_by_public_id ||= champs_in_revision.index_by(&:public_id)
end
def filled_champ(type_de_champ, row_id)
def revision_stable_ids
@revision_stable_ids ||= revision.types_de_champ.map(&:stable_id).to_set
end
def champs_in_revision
champs.filter { stable_id_in_revision?(_1.stable_id) }
end
def filled_champ(type_de_champ, row_id: nil)
champ = champs_by_public_id[type_de_champ.public_id(row_id)]
if type_de_champ.champ_blank?(champ) || !champ.visible?
nil
@ -157,41 +203,51 @@ module DossierChampsConcern
def champ_attributes_by_public_id(public_id, attributes, scope, updated_by:)
stable_id, row_id = public_id.split('-')
type_de_champ = find_type_de_champ_by_stable_id(stable_id, scope)
champ_with_attributes_for_update(type_de_champ, row_id, updated_by:).last.merge(attributes)
champ = champ_upsert_by!(type_de_champ, row_id)
attributes.merge(id: champ.id, updated_by:)
end
def champ_with_attributes_for_update(type_de_champ, row_id, updated_by:)
check_valid_row_id?(type_de_champ, row_id)
attributes = type_de_champ.params_for_champ
def champ_upsert_by!(type_de_champ, row_id)
check_valid_row_id_on_write?(type_de_champ, row_id)
champ_attributes = type_de_champ.params_for_champ
# TODO: Once we have the right index in place, we should change this to use `create_or_find_by` instead of `find_or_create_by`
champ = champs
.create_with(**attributes)
.create_with(**champ_attributes)
.find_or_create_by!(stable_id: type_de_champ.stable_id, row_id:)
attributes[:id] = champ.id
attributes[:updated_by] = updated_by
# Needed when a revision change the champ type in this case, we reset the champ data
if champ.type != attributes[:type]
attributes[:value] = nil
attributes[:value_json] = nil
attributes[:external_id] = nil
attributes[:data] = nil
champ = champ.becomes!(attributes[:type].constantize)
champ.save!
if champ.type != champ_attributes[:type]
champ_attributes[:value] = nil
champ_attributes[:value_json] = nil
champ_attributes[:external_id] = nil
champ_attributes[:data] = nil
champ = champ.becomes!(champ_attributes[:type].constantize)
end
champ.assign_attributes(champ_attributes)
champ.save!
reset_champ_cache(champ)
[champ, attributes]
champ
end
def check_valid_row_id?(type_de_champ, row_id)
def check_valid_row_id_on_write?(type_de_champ, row_id)
if type_de_champ.repetition?
if row_id.blank?
raise "type_de_champ #{type_de_champ.stable_id} in revision #{revision_id} must have a row_id because it represents a row in a repetition"
end
else
check_valid_row_id_on_read?(type_de_champ, row_id)
end
end
def check_valid_row_id_on_read?(type_de_champ, row_id)
if type_de_champ.child?(revision)
if row_id.blank?
raise "type_de_champ #{type_de_champ.stable_id} in revision #{revision_id} must have a row_id because it is part of a repetition"
end
elsif row_id.present? && type_de_champ.in_revision?(revision)
elsif row_id.present? && stable_id_in_revision?(type_de_champ.stable_id)
raise "type_de_champ #{type_de_champ.stable_id} in revision #{revision_id} can not have a row_id because it is not part of a repetition"
end
end
@ -202,15 +258,12 @@ module DossierChampsConcern
@filled_champs_private = nil
@project_champs_public = nil
@project_champs_private = nil
@repetition_row_ids = nil
@revision_stable_ids = nil
end
def reset_champ_cache(champ)
champs_by_public_id[champ.public_id]&.reload
reset_champs_cache
end
def reload_champs_cache
champs.reload if persisted?
reset_champs_cache
end
end

View file

@ -89,7 +89,11 @@ module DossierCloneConcern
dossier_attributes += [:groupe_instructeur_id] if fork
relationships = [:individual, :etablissement]
cloned_champs = champs
discarded_row_ids = champs_in_revision
.filter { _1.row? && _1.discarded? }
.to_set(&:row_id)
cloned_champs = champs_in_revision
.reject { discarded_row_ids.member?(_1.row_id) }
.index_by(&:id)
.transform_values { [_1, _1.clone(fork)] }
@ -143,34 +147,38 @@ module DossierCloneConcern
end
def apply_diff(diff)
champs_added = diff[:added].filter(&:persisted?)
champs_updated = diff[:updated].filter(&:persisted?)
champs_removed = diff[:removed].filter(&:persisted?)
added_row_ids = {}
diff[:added].each do |champ|
next if !champ.child?
next if added_row_ids.key?(champ.row_id)
added_row_ids[champ.row_id] = revision.parent_of(champ.type_de_champ)
end
champs_added.each { _1.update_column(:dossier_id, id) }
removed_row_ids = {}
diff[:removed].each do |champ|
next if !champ.child?
next if removed_row_ids.key?(champ.row_id)
removed_row_ids[champ.row_id] = revision.parent_of(champ.type_de_champ)
end
if champs_updated.present?
added_champs = diff[:added].filter { _1.persisted? && _1.fillable? }
updated_champs = diff[:updated].filter { _1.persisted? && _1.fillable? }
added_champs.each { _1.update_column(:dossier_id, id) }
if updated_champs.present?
champs_index = filled_champs_public.index_by(&:public_id)
champs_updated.each do |champ|
updated_champs.each do |champ|
champs_index[champ.public_id]&.destroy!
champ.update_column(:dossier_id, id)
end
end
champs_removed.each(&:destroy!)
end
protected
# This is a temporary method that is only used by diff/merge algorithm. Once it's gone, this method should be removed.
def project_champs_public_all
revision.types_de_champ_public.flat_map do |type_de_champ|
champ = project_champ(type_de_champ, nil)
if type_de_champ.repetition?
[champ] + project_rows_for(type_de_champ).flatten
else
champ
added_row_ids.each do |row_id, repetition_type_de_champ|
champ_for_update(repetition_type_de_champ, row_id:, updated_by: user.email).save!
end
removed_row_ids.each do |row_id, repetition_type_de_champ|
champ_for_update(repetition_type_de_champ, row_id:, updated_by: user.email).discard!
end
end
end

View file

@ -17,7 +17,7 @@ module DossierExportConcern
def champ_values_for_export(types_de_champ, row_id: nil, export_template: nil, format:)
types_de_champ.flat_map do |type_de_champ|
champ = filled_champ(type_de_champ, row_id)
champ = filled_champ(type_de_champ, row_id:)
if export_template.present?
export_template
.columns_for_stable_id(type_de_champ.stable_id)

View file

@ -49,59 +49,27 @@ module DossierRebaseConcern
# revision we are rebasing to
target_revision = procedure.published_revision
# index published types de champ coordinates by stable_id
target_coordinates_by_stable_id = target_revision
.revision_types_de_champ
.includes(:parent)
.index_by(&:stable_id)
changes_by_op = pending_changes
changed_stable_ids_by_op = pending_changes
.group_by(&:op)
.tap { _1.default = [] }
champs_by_stable_id = champs
.group_by(&:stable_id)
.transform_values { Champ.where(id: _1) }
.tap { _1.default = Champ.none }
# remove champ
changes_by_op[:remove].each { champs_by_stable_id[_1.stable_id].destroy_all }
# update champ
changes_by_op[:update].each { champs_by_stable_id[_1.stable_id].update_all(rebased_at: Time.zone.now) }
.transform_values { _1.map(&:stable_id) }
updated_stable_ids = changed_stable_ids_by_op.fetch(:update, [])
added_stable_ids = changed_stable_ids_by_op.fetch(:add, [])
# update dossier revision
update_column(:revision_id, target_revision.id)
# add champ (after changing dossier revision to avoid errors)
changes_by_op[:add]
.map { target_coordinates_by_stable_id[_1.stable_id] }
.each { add_new_champs_for_revision(_1) }
end
# mark updated champs as rebased
champs.where(stable_id: updated_stable_ids).update_all(rebased_at: Time.zone.now)
def add_new_champs_for_revision(target_coordinate)
if target_coordinate.child?
row_ids = repetition_row_ids(target_coordinate.parent.type_de_champ)
if row_ids.present?
row_ids.each do |row_id|
create_champ(target_coordinate, row_id:)
end
elsif target_coordinate.parent.mandatory?
create_champ(target_coordinate, row_id: ULID.generate)
end
else
create_champ(target_coordinate)
# add rows for new repetitions
repetition_types_de_champ = target_revision
.types_de_champ
.repetition
.where(stable_id: added_stable_ids)
repetition_types_de_champ.mandatory
.or(repetition_types_de_champ.private_only)
.find_each do |type_de_champ|
self.champs << type_de_champ.build_champ(row_id: ULID.generate, rebased_at: Time.zone.now)
end
end
def create_champ(target_coordinate, row_id: nil)
self.champs << target_coordinate
.type_de_champ
.build_champ(rebased_at: Time.zone.now, row_id:)
end
def purge_piece_justificative_file(champ)
ActiveStorage::Attachment.where(id: champ.piece_justificative_file.ids).delete_all
end
end

View file

@ -33,7 +33,7 @@ module DossierSectionsConcern
return "#{index}.#{index_in_repetition + 1}" if index_in_repetition
else
return index if tdc.stable_id == type_de_champ.stable_id
next unless project_champ(tdc, nil).visible?
next unless project_champ(tdc).visible?
index += 1 if tdc.header_section?
end

View file

@ -11,9 +11,7 @@ module DossierStateConcern
resolve_pending_correction!
process_sva_svr!
remove_piece_justificative_file_not_visible!
editing_forks.each(&:destroy_editing_fork!)
clean_champs_after_submit!
end
def after_passer_en_construction
@ -41,7 +39,8 @@ module DossierStateConcern
groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur|
DossierMailer.notify_new_dossier_depose_to_instructeur(self, instructeur.email).deliver_later
end
remove_piece_justificative_file_not_visible!
clean_champs_after_submit!
end
def after_passer_en_instruction(h)
@ -154,7 +153,7 @@ module DossierStateConcern
end
send_dossier_decision_to_experts(self)
remove_titres_identite!
clean_champs_after_instruction!
end
def after_accepter_automatiquement
@ -189,7 +188,7 @@ module DossierStateConcern
NotificationMailer.send_notification_for_tiers(self).deliver_later if self.for_tiers?
send_dossier_decision_to_experts(self)
remove_titres_identite!
clean_champs_after_instruction!
end
def after_refuser(h)
@ -226,7 +225,7 @@ module DossierStateConcern
end
send_dossier_decision_to_experts(self)
remove_titres_identite!
clean_champs_after_instruction!
end
def after_refuser_automatiquement
@ -254,7 +253,7 @@ module DossierStateConcern
NotificationMailer.send_notification_for_tiers(self).deliver_later if self.for_tiers?
send_dossier_decision_to_experts(self)
remove_titres_identite!
clean_champs_after_instruction!
end
def after_classer_sans_suite(h)
@ -291,7 +290,7 @@ module DossierStateConcern
end
send_dossier_decision_to_experts(self)
remove_titres_identite!
clean_champs_after_instruction!
end
def after_repasser_en_instruction(h)
@ -329,4 +328,58 @@ module DossierStateConcern
rebase_later
end
def clean_champs_after_submit!
remove_discarded_rows!
remove_not_visible_rows!
remove_not_visible_or_empty_champs!
editing_forks.each(&:destroy_editing_fork!)
end
def clean_champs_after_instruction!
remove_discarded_rows!
remove_titres_identite!
end
private
def remove_discarded_rows!
row_to_remove_ids = champs.filter { _1.row? && _1.discarded? }.map(&:row_id)
return if row_to_remove_ids.empty?
champs.where(row_id: row_to_remove_ids).destroy_all
end
def remove_not_visible_or_empty_champs!
repetition_to_keep_stable_ids, champ_to_keep_public_ids = project_champs_public_all
.reject { _1.blank? || !_1.visible? }
.partition(&:repetition?)
.then { |(repetitions, champs)| [repetitions.to_set(&:stable_id), champs.to_set(&:public_id)] }
rows_public, champs_public = champs
.filter(&:public?)
.partition(&:row?)
champs_to_remove = champs_public.reject { champ_to_keep_public_ids.member?(_1.public_id) }
champs_to_remove += rows_public.reject { repetition_to_keep_stable_ids.member?(_1.stable_id) }
return if champs_to_remove.empty?
champs.where(id: champs_to_remove).destroy_all
end
def remove_not_visible_rows!
row_to_remove_ids = project_champs_public
.filter { _1.repetition? && !_1.visible? }
.flat_map(&:row_ids)
return if row_to_remove_ids.empty?
champs.where(row_id: row_to_remove_ids).destroy_all
end
def remove_titres_identite!
champ_to_remove_ids = filled_champs.filter(&:titre_identite?).map(&:id)
return if champ_to_remove_ids.empty?
champs.where(id: champ_to_remove_ids).destroy_all
end
end

View file

@ -905,19 +905,6 @@ class Dossier < ApplicationRecord
traitements.any?(&:termine?)
end
def remove_titres_identite!
champs.filter(&:titre_identite?).map(&:piece_justificative_file).each(&:purge_later)
end
def remove_piece_justificative_file_not_visible!
champs.each do |champ|
next unless champ.piece_justificative_file.attached?
next if champ.visible?
champ.piece_justificative_file.purge_later
end
end
def check_mandatory_and_visible_champs
project_champs_public.filter(&:visible?).each do |champ|
if champ.mandatory_blank?
@ -1058,13 +1045,13 @@ class Dossier < ApplicationRecord
end
def build_default_champs_for(types_de_champ)
self.champs << types_de_champ.flat_map do |type_de_champ|
champ = type_de_champ.build_champ(dossier: self)
if type_de_champ.repetition? && (type_de_champ.private? || type_de_champ.mandatory?)
row_id = ULID.generate
[champ] + revision.children_of(type_de_champ).map { _1.build_champ(dossier: self, row_id:) }
self.champs << types_de_champ.filter(&:fillable?).filter_map do |type_de_champ|
if type_de_champ.repetition?
if type_de_champ.private? || type_de_champ.mandatory?
type_de_champ.build_champ(dossier: self, row_id: ULID.generate)
end
else
champ
type_de_champ.build_champ(dossier: self)
end
end
end

View file

@ -203,10 +203,6 @@ class ProcedureRevision < ApplicationRecord
coordinate_for(tdc).parent_type_de_champ
end
def in_revision?(stable_id)
types_de_champ.any? { _1.stable_id == stable_id }
end
def dependent_conditions(tdc)
stable_id = tdc.stable_id

View file

@ -171,6 +171,7 @@ class TypeDeChamp < ApplicationRecord
scope :not_condition, -> { where(condition: nil) }
scope :fillable, -> { where.not(type_champ: [type_champs.fetch(:header_section), type_champs.fetch(:explication)]) }
scope :with_header_section, -> { where.not(type_champ: TypeDeChamp.type_champs[:explication]) }
scope :mandatory, -> { where(mandatory: true) }
scope :dubious, -> {
where("unaccent(types_de_champ.libelle) ~* unaccent(?)", DubiousProcedure.forbidden_regexp)
@ -331,10 +332,6 @@ class TypeDeChamp < ApplicationRecord
!private?
end
def in_revision?(revision)
revision.types_de_champ.any? { _1.stable_id == stable_id }
end
def child?(revision)
revision.coordinate_for(self)&.child?
end

View file

@ -65,7 +65,7 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh
type_de_champ = revision.types_de_champ.find { _1.stable_id == stable_id }
next unless type_de_champ
subchamp = champ.dossier.champ_for_update(type_de_champ, row_id, updated_by: nil)
subchamp = champ.dossier.champ_for_update(type_de_champ, row_id:, updated_by: nil)
TypesDeChamp::PrefillTypeDeChamp.build(subchamp.type_de_champ, revision).to_assignable_attributes(subchamp, value)
end.compact
end

View file

@ -1,17 +0,0 @@
# frozen_string_literal: true
module Maintenance
class RemovePieceJustificativeFileNotVisibleTask < MaintenanceTasks::Task
attribute :procedure_id, :string
validates :procedure_id, presence: true
def collection
procedure = Procedure.with_discarded.find(procedure_id.strip.to_i)
procedure.dossiers.state_not_brouillon
end
def process(dossier)
dossier.remove_piece_justificative_file_not_visible!
end
end
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddDiscardedAtToChamps < ActiveRecord::Migration[7.0]
def change
add_column :champs, :discarded_at, :datetime
end
end

View file

@ -250,6 +250,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_26_145420) do
create_table "champs", id: :serial, force: :cascade do |t|
t.datetime "created_at", precision: nil
t.jsonb "data"
t.datetime "discarded_at"
t.integer "dossier_id"
t.integer "etablissement_id"
t.string "external_id"

View file

@ -2,8 +2,8 @@
describe EditableChamp::ExplicationComponent, type: :component do
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
let(:champ) { dossier.champs.first }
let(:dossier) { create(:dossier, procedure:) }
let(:champ) { dossier.project_champs_public.first }
let(:component) {
described_class.new(form: instance_double(ActionView::Helpers::FormBuilder, object_name: "dossier[champs_public_attributes]"), champ:)

View file

@ -40,20 +40,7 @@ describe Champs::PieceJustificativeController, type: :controller do
end
context 'when the file is invalid' do
let(:file) { fixture_file_upload('spec/fixtures/files/invalid_file_format.json', 'application/json') }
# TODO: for now there are no validators on the champ piece_justificative_file,
# so we have to mock a failing validation.
# Once the validators will be enabled, remove those mocks, and let the usual
# validation fail naturally.
#
# See https://github.com/betagouv/demarches-simplifiees.fr/issues/4926
before do
champ
expect_any_instance_of(Champs::PieceJustificativeChamp).to receive(:save).and_return(false)
expect_any_instance_of(Champs::PieceJustificativeChamp).to receive(:errors)
.and_return(double(full_messages: ['La pièce justificative nest pas dun type accepté']))
end
let(:file) { fixture_file_upload('spec/fixtures/files/invalid_file_format.json', 'bad/bad') }
it 'doesnt attach the file' do
subject
@ -64,7 +51,7 @@ describe Champs::PieceJustificativeController, type: :controller do
subject
expect(response.status).to eq(422)
expect(response.header['Content-Type']).to include('application/json')
expect(response.parsed_body).to eq({ 'errors' => ['La pièce justificative nest pas dun type accepté'] })
expect(response.parsed_body).to eq({ 'errors' => ['Le champ « Piece justificative file » nest pas dun type accepté'] })
end
end
end

View file

@ -3,14 +3,18 @@
describe Champs::RepetitionController, type: :controller do
describe '#remove' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :repetition, mandatory: true, children: [{ libelle: 'Nom' }, { type: :integer_number, libelle: 'Age' }] }]) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:dossier) { create(:dossier, procedure:) }
let(:repetition) { dossier.project_champs_public.find(&:repetition?) }
let(:row) { dossier.champs.find(&:row?) }
before { sign_in dossier.user }
it 'removes repetition' do
rows, repetitions = dossier.champs.partition(&:child?)
repetition = repetitions.first
expect { delete :remove, params: { dossier_id: dossier, stable_id: repetition.stable_id, row_id: rows.first.row_id }, format: :turbo_stream }
.to change { dossier.reload.champs.size }.from(3).to(1)
subject { delete :remove, params: { dossier_id: dossier, stable_id: repetition.stable_id, row_id: row.row_id }, format: :turbo_stream }
context 'removes repetition' do
it { expect { subject }.not_to change { dossier.reload.champs.size } }
it { expect { subject }.to change { dossier.reload; dossier.project_champs_public.find(&:repetition?).row_ids.size }.from(1).to(0) }
it { expect { subject }.to change { row.reload.discarded_at }.from(nil).to(Time) }
end
end
end

View file

@ -1002,10 +1002,11 @@ describe Instructeurs::DossiersController, type: :controller do
let(:another_instructeur) { create(:instructeur) }
let(:now) { Time.zone.parse('01/01/2100') }
let(:champ_repetition) { dossier.project_champs_private.fourth }
let(:champ_text) { champ_repetition.rows.first.first }
let(:champ_multiple_drop_down_list) { dossier.project_champs_private.first }
let(:champ_linked_drop_down_list) { dossier.project_champs_private.second }
let(:champ_datetime) { dossier.project_champs_private.third }
let(:champ_repetition) { dossier.project_champs_private.fourth }
let(:champ_drop_down_list) { dossier.project_champs_private.fifth }
context 'when no invalid champs_public' do
@ -1015,12 +1016,12 @@ describe Instructeurs::DossiersController, type: :controller do
another_instructeur.follow(dossier)
Timecop.freeze(now)
patch :update_annotations, params: params, format: :turbo_stream
dossier.reload
champ_multiple_drop_down_list.reload
champ_linked_drop_down_list.reload
champ_datetime.reload
champ_repetition.reload
champ_drop_down_list.reload
champ_text.reload
end
after do
@ -1059,9 +1060,9 @@ describe Instructeurs::DossiersController, type: :controller do
expect(champ_linked_drop_down_list.primary_value).to eq('primary')
expect(champ_linked_drop_down_list.secondary_value).to eq('secondary')
expect(champ_datetime.value).to eq(Time.zone.parse('2019-12-21T13:17:00').iso8601)
expect(champ_repetition.rows.first.first.value).to eq('text')
expect(champ_text.value).to eq('text')
expect(champ_drop_down_list.value).to eq('other value')
expect(dossier.reload.last_champ_private_updated_at).to eq(now)
expect(dossier.last_champ_private_updated_at).to eq(now)
expect(response).to have_http_status(200)
assert_enqueued_jobs(1, only: DossierIndexSearchTermsJob)
}

View file

@ -2,7 +2,8 @@
describe Users::CommencerController, type: :controller do
let(:user) { create(:user) }
let(:published_procedure) { create(:procedure, :for_individual, :published) }
let(:published_procedure) { create(:procedure, :for_individual, :published, types_de_champ_public:) }
let(:types_de_champ_public) { [] }
let(:draft_procedure) { create(:procedure, :with_path) }
describe '#commencer' do
@ -160,7 +161,8 @@ describe Users::CommencerController, type: :controller do
end
context 'when a dossier is being prefilled by GET' do
let(:type_de_champ_text) { create(:type_de_champ_text, procedure: published_procedure) }
let(:types_de_champ_public) { [{}] }
let(:type_de_champ_text) { published_procedure.published_revision.types_de_champ.first }
let(:path) { published_procedure.path }
let(:user) { create(:user) }

View file

@ -594,11 +594,11 @@ describe Users::DossiersController, type: :controller do
let(:dossier) { create(:dossier, :en_construction, :with_populated_champs, procedure:, user:) }
let(:types_de_champ_public) { [{ type: :repetition, libelle: 'repetition', children: [{ type: :text, libelle: 'child' }] }] }
let(:editing_fork) { dossier.owner_editing_fork }
let(:champ_repetition) { editing_fork.champs.find(&:repetition?) }
let(:champ_repetition) { editing_fork.project_champs_public.find(&:repetition?) }
before do
editing_fork
procedure.draft_revision.remove_type_de_champ(editing_fork.champs.find(&:repetition?).stable_id)
procedure.draft_revision.remove_type_de_champ(champ_repetition.stable_id)
procedure.publish_revision!
editing_fork.reload
@ -611,7 +611,7 @@ describe Users::DossiersController, type: :controller do
context 'when dossier was already submitted' do
before do
expect_any_instance_of(Dossier).to receive(:remove_piece_justificative_file_not_visible!)
expect_any_instance_of(Dossier).to receive(:remove_not_visible_or_empty_champs!)
post :submit_en_construction, params: payload
end

View file

@ -94,14 +94,6 @@ FactoryBot.define do
external_id { '200071991' }
end
factory :champ_do_not_use_header_section, class: 'Champs::HeaderSectionChamp' do
value { 'une section' }
end
factory :champ_do_not_use_explication, class: 'Champs::ExplicationChamp' do
value { '' }
end
factory :champ_do_not_use_dossier_link, class: 'Champs::DossierLinkChamp' do
value { create(:dossier, :en_construction).id }
end
@ -188,25 +180,5 @@ FactoryBot.define do
factory :champ_do_not_use_expression_reguliere, class: 'Champs::ExpressionReguliereChamp' do
end
factory :champ_do_not_use_repetition, class: 'Champs::RepetitionChamp' do
transient do
rows { 2 }
end
after(:build) do |champ_repetition, evaluator|
revision = champ_repetition.procedure.active_revision
parent = revision.revision_types_de_champ.find { _1.type_de_champ == champ_repetition.type_de_champ }
types_de_champ = revision.revision_types_de_champ.filter { _1.parent == parent }.map(&:type_de_champ)
evaluator.rows.times do
row_id = ULID.generate
champ_repetition.dossier.champs << types_de_champ.map do |type_de_champ|
attrs = { dossier: champ_repetition.dossier, private: champ_repetition.private?, stable_id: type_de_champ.stable_id, row_id: }
build(:"champ_do_not_use_#{type_de_champ.type_champ}", **attrs)
end
end
end
end
end
end

View file

@ -24,25 +24,13 @@ FactoryBot.define do
after(:create) do |dossier, evaluator|
if evaluator.populate_champs
dossier.revision.types_de_champ_public.each do |type_de_champ|
value = if type_de_champ.drop_down_list?
type_de_champ.drop_down_options.first
elsif type_de_champ.multiple_drop_down_list?
type_de_champ.drop_down_options.first(2).to_json
end
attrs = { stable_id: type_de_champ.stable_id, dossier:, value: }.compact
create(:"champ_do_not_use_#{type_de_champ.type_champ}", **attrs)
dossier_factory_create_champ_or_repetition(type_de_champ, dossier)
end
end
if evaluator.populate_annotations
dossier.revision.types_de_champ_private.each do |type_de_champ|
value = if type_de_champ.drop_down_list?
type_de_champ.drop_down_options.first
elsif type_de_champ.multiple_drop_down_list?
type_de_champ.drop_down_options.first(2).to_json
end
attrs = { stable_id: type_de_champ.stable_id, dossier:, private: true, value: }.compact
create(:"champ_do_not_use_#{type_de_champ.type_champ}", **attrs)
dossier_factory_create_champ_or_repetition(type_de_champ, dossier)
end
end
@ -301,3 +289,30 @@ FactoryBot.define do
end
end
end
def dossier_factory_create_champ_or_repetition(type_de_champ, dossier)
if type_de_champ.repetition?
types_de_champ = dossier.revision.children_of(type_de_champ)
2.times do
row_id = ULID.generate
type_de_champ.build_champ(dossier:, row_id:).save!
types_de_champ.each do |type_de_champ|
dossier_factory_create_champ(type_de_champ, dossier, row_id:)
end
end
else
dossier_factory_create_champ(type_de_champ, dossier)
end
end
def dossier_factory_create_champ(type_de_champ, dossier, row_id: nil)
return unless type_de_champ.fillable?
value = if type_de_champ.drop_down_list?
type_de_champ.drop_down_options.first
elsif type_de_champ.multiple_drop_down_list?
type_de_champ.drop_down_options.first(2).to_json
end
attrs = { stable_id: type_de_champ.stable_id, private: type_de_champ.private?, row_id:, dossier:, value: }.compact
create(:"champ_do_not_use_#{type_de_champ.type_champ}", **attrs)
end

View file

@ -55,7 +55,8 @@ RSpec.describe Mutations::DossierModifierAnnotation, type: :graphql do
},
errors: nil
})
expect(annotation.reload.row_ids.size).to eq(3)
dossier.reload
expect(annotation.row_ids.size).to eq(3)
end
end

View file

@ -15,7 +15,7 @@ describe 'Dossier::Recovery::LifeCycle' do
let(:geo_area) { build(:geo_area, :selection_utilisateur, :polygon) }
let(:fp) { Rails.root.join('spec', 'fixtures', 'export.dump') }
let(:dossier) do
d = create(:dossier, procedure:)
d = create(:dossier, :with_populated_champs, procedure:)
repetition(d).add_row(updated_by: 'test')
pj_champ(d).piece_justificative_file.attach(some_file)
@ -49,7 +49,7 @@ describe 'Dossier::Recovery::LifeCycle' do
d
end
def repetition(d) = d.champs.find_by(type: "Champs::RepetitionChamp")
def repetition(d) = d.project_champs_public.find(&:repetition?)
def pj_champ(d) = d.champs.find_by(type: "Champs::PieceJustificativeChamp")
def carte(d) = d.champs.find_by(type: "Champs::CarteChamp")
def siret(d) = d.champs.find_by(type: "Champs::SiretChamp")
@ -83,7 +83,7 @@ describe 'Dossier::Recovery::LifeCycle' do
expect(reloaded_dossier.champs.count).not_to be(0)
expect(repetition(reloaded_dossier).rows.flatten.map(&:type)).to match_array(["Champs::PieceJustificativeChamp"])
expect(repetition(reloaded_dossier).rows.flatten.map(&:type)).to match_array(["Champs::PieceJustificativeChamp", "Champs::PieceJustificativeChamp", "Champs::PieceJustificativeChamp"])
expect(pj_champ(reloaded_dossier).piece_justificative_file).to be_attached
expect(carte(reloaded_dossier).geo_areas).to be_present

View file

@ -15,7 +15,7 @@ describe ChampPresentations::RepetitionPresentation do
}
let(:dossier) { create(:dossier, procedure:) }
let(:champ_repetition) { dossier.champs.find(&:repetition?) }
let(:champ_repetition) { dossier.project_champs_public.first }
before do
champ_repetition.add_row(updated_by: 'test')
@ -23,15 +23,15 @@ describe ChampPresentations::RepetitionPresentation do
row1, row2, row3 = champ_repetition.rows
nom, stars = row1
nom.update(value: "ruby")
stars.update(value: 5)
champ_for_update(nom).update(value: "ruby")
champ_for_update(stars).update(value: 5)
nom = row2.first
nom.update(value: "js")
champ_for_update(nom).update(value: "js")
nom, stars = row3
nom.update(value: "rust")
stars.update(value: 4)
champ_for_update(nom).update(value: "rust")
champ_for_update(stars).update(value: 4)
end
let(:representation) { described_class.new(libelle, champ_repetition.rows) }

View file

@ -58,7 +58,7 @@ describe Champ do
context 'when repetition not blank' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :repetition, children: [{ type: :text }] }]) }
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
let(:champ) { dossier.champs.find(&:repetition?) }
let(:champ) { dossier.project_champs_public.find(&:repetition?) }
it { expect(champ.blank?).to be(false) }
end
@ -108,11 +108,10 @@ describe Champ do
let(:dossier) { create(:dossier, procedure: procedure) }
let(:public_champ) { dossier.project_champs_public.first }
let(:private_champ) { dossier.project_champs_private.first }
let(:champ_in_repetition) { dossier.project_champs_public.find(&:repetition?).champs.first }
let(:standalone_champ) { build(:champ, type_de_champ: build(:type_de_champ), dossier: build(:dossier)) }
let(:public_sections) { dossier.project_champs_public.filter(&:header_section?) }
let(:private_sections) { dossier.project_champs_private.filter(&:header_section?) }
let(:sections_in_repetition) { dossier.champs.filter(&:child?).filter(&:header_section?) }
let(:sections_in_repetition) { dossier.project_champs_public.find(&:repetition?).rows.flatten.filter(&:header_section?) }
it 'returns the sibling sections of a champ' do
expect(public_sections).not_to be_empty

View file

@ -8,7 +8,7 @@ describe Champs::EpciChamp, type: :model do
let(:champ) { Champs::EpciChamp.new(code_departement: code_departement, dossier: build(:dossier)) }
before do
allow(champ).to receive(:visible?).and_return(true)
allow(champ).to receive(:in_dossier_revision?).and_return(true)
allow(champ).to receive(:can_validate?).and_return(true)
end
context 'when nil' do
let(:code_departement) { nil }

View file

@ -4,7 +4,7 @@ describe Champs::IntegerNumberChamp do
let(:champ) { Champs::IntegerNumberChamp.new(value:, dossier: build(:dossier)) }
before do
allow(champ).to receive(:visible?).and_return(true)
allow(champ).to receive(:in_dossier_revision?).and_return(true)
allow(champ).to receive(:can_validate?).and_return(true)
end
subject { champ.validate(:champs_public_value) }

View file

@ -11,11 +11,11 @@ describe Champs::RepetitionChamp do
])
}
let(:dossier) { create(:dossier, procedure:) }
let(:champ) { dossier.champs.find(&:repetition?) }
let(:champ) { dossier.project_champs_public.find(&:repetition?) }
describe "#for_tag" do
before do
champ.rows[0][0].update(value: "rb")
champ_for_update(champ.rows.first.first).update(value: "rb")
end
it "can render as string" do

View file

@ -155,13 +155,13 @@ describe Columns::ChampColumn do
def expect_type_de_champ_values(type, assertion)
type_de_champ = types_de_champ.find { _1.type_champ == type }
champ = dossier.send(:filled_champ, type_de_champ, nil)
champ = dossier.send(:filled_champ, type_de_champ)
columns = type_de_champ.columns(procedure:)
expect(columns.map { _1.value(champ) }).to assertion
end
def retrieve_champ(type)
type_de_champ = types_de_champ.find { _1.type_champ == type }
dossier.send(:filled_champ, type_de_champ, nil)
dossier.send(:filled_champ, type_de_champ)
end
end

View file

@ -1,15 +1,15 @@
# frozen_string_literal: true
RSpec.describe ChampsValidateConcern do
RSpec.describe ChampValidateConcern do
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
let(:type_de_champ) { dossier.revision.types_de_champ_public.first }
let(:public_id) { type_de_champ.public_id(nil) }
let(:types_de_champ_public) { [{ type: :email }] }
def update_champ(value)
dossier.update_champs_attributes({
type_de_champ.stable_id.to_s => { value: }
public_id => { value: }
}, :public, updated_by: 'test')
dossier.save
end
@ -68,4 +68,45 @@ RSpec.describe ChampsValidateConcern do
}
end
end
context 'when in a row' do
let(:types_de_champ_public) { [{ type: :repetition, children: [{ type: :email }], mandatory: true }] }
let(:type_de_champ_in_repetition) { dossier.revision.children_of(type_de_champ).first }
let(:row_id) { dossier.repetition_row_ids(type_de_champ).first }
let(:public_id) { type_de_champ_in_repetition.public_id(row_id) }
context 'valid' do
before {
update_champ('test@test.com')
dossier.validate(:champs_public_value)
}
it {
expect(dossier.champs).not_to be_empty
expect(dossier.errors).to be_empty
}
end
context 'invalid' do
before {
update_champ('test')
dossier.validate(:champs_public_value)
}
it {
expect(dossier.champs).not_to be_empty
expect(dossier.errors).not_to be_empty
}
end
context 'do not validate when in discarded row' do
before {
update_champ('test')
dossier.repetition_remove_row(type_de_champ, row_id, updated_by: 'test')
dossier.validate(:champs_public_value)
}
it {
expect(dossier.champs).not_to be_empty
expect(dossier.errors).to be_empty
}
end
end
end

View file

@ -1,9 +1,7 @@
# frozen_string_literal: true
RSpec.describe DossierChampsConcern do
let(:procedure) do
create(:procedure, types_de_champ_public:, types_de_champ_private:)
end
let(:procedure) { create(:procedure, types_de_champ_public:, types_de_champ_private:) }
let(:types_de_champ_public) do
[
{ type: :text, libelle: "Un champ text", stable_id: 99 },
@ -38,16 +36,16 @@ RSpec.describe DossierChampsConcern do
context "public champ" do
let(:row_id) { nil }
subject { dossier.project_champ(type_de_champ_public, row_id) }
subject { dossier.project_champ(type_de_champ_public, row_id:) }
it { expect(subject.persisted?).to be_truthy }
context "in repetition" do
let(:type_de_champ_public) { dossier.find_type_de_champ_by_stable_id(994) }
let(:row_id) { dossier.project_champ(type_de_champ_repetition, nil).row_ids.first }
let(:row_id) { dossier.project_champ(type_de_champ_repetition).row_ids.first }
it {
expect(subject.persisted?).to be_truthy
expect(subject.new_record?).to be_truthy
expect(subject.row_id).to eq(row_id)
}
@ -90,7 +88,7 @@ RSpec.describe DossierChampsConcern do
end
context "private champ" do
subject { dossier.project_champ(type_de_champ_private, nil) }
subject { dossier.project_champ(type_de_champ_private) }
it { expect(subject.persisted?).to be_truthy }
@ -130,10 +128,11 @@ RSpec.describe DossierChampsConcern do
{ type: :explication }
]
end
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
subject { dossier.filled_champs_public }
it { expect(subject.size).to eq(4) }
it { expect(subject.find { _1.libelle == 'Nom' }).to be_truthy }
it { expect(subject.size).to eq(5) }
it { expect(subject.filter { _1.libelle == 'Nom' }.size).to eq(2) }
end
describe '#filled_champs_private' do
@ -156,14 +155,8 @@ RSpec.describe DossierChampsConcern do
it { expect(subject.size).to eq(1) }
context 'given a type de champ repetition in another revision' do
let(:procedure) { create(:procedure, :published, types_de_champ_public:, types_de_champ_private:) }
let(:draft) { procedure.draft_revision }
let(:errored_stable_id) { 666 }
let(:type_de_champ_repetition) { procedure.active_revision.types_de_champ.find { _1.stable_id == errored_stable_id } }
before do
dossier
tdc_repetition = draft.add_type_de_champ(type_champ: :repetition, libelle: "repetition", stable_id: errored_stable_id)
draft.add_type_de_champ(type_champ: :text, libelle: "t1", parent_stable_id: tdc_repetition.stable_id)
procedure.draft_revision.remove_type_de_champ(type_de_champ_repetition.stable_id)
procedure.publish_revision!
end
@ -232,7 +225,7 @@ RSpec.describe DossierChampsConcern do
let(:row_id) { nil }
context "public champ" do
subject { dossier.champ_for_update(type_de_champ_public, row_id, updated_by: dossier.user.email) }
subject { dossier.champ_for_update(type_de_champ_public, row_id:, updated_by: dossier.user.email) }
it {
expect(subject.persisted?).to be_truthy
@ -290,7 +283,7 @@ RSpec.describe DossierChampsConcern do
end
context "private champ" do
subject { dossier.champ_for_update(type_de_champ_private, row_id, updated_by: dossier.user.email) }
subject { dossier.champ_for_update(type_de_champ_private, row_id:, updated_by: dossier.user.email) }
it {
expect(subject.persisted?).to be_truthy
@ -311,9 +304,9 @@ RSpec.describe DossierChampsConcern do
}
end
let(:champ_99) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(99), nil) }
let(:champ_991) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(991), nil) }
let(:champ_994) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(994), row_id) }
let(:champ_99) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(99)) }
let(:champ_991) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(991)) }
let(:champ_994) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(994), row_id:) }
subject { dossier.update_champs_attributes(attributes, :public, updated_by: dossier.user.email) }
@ -372,7 +365,7 @@ RSpec.describe DossierChampsConcern do
}
end
let(:annotation_995) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(995), nil) }
let(:annotation_995) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(995)) }
subject { dossier.update_champs_attributes(attributes, :private, updated_by: dossier.user.email) }

View file

@ -135,8 +135,8 @@ RSpec.describe DossierCloneConcern do
context 'for Champs::Repetition with rows, original_champ.repetition and rows are duped' do
let(:types_de_champ_public) { [{ type: :repetition, children: [{}, {}] }] }
let(:champ_repetition) { dossier.champs.find(&:repetition?) }
let(:cloned_champ_repetition) { new_dossier.champs.find(&:repetition?) }
let(:champ_repetition) { dossier.project_champs_public.find(&:repetition?) }
let(:cloned_champ_repetition) { new_dossier.project_champs_public.find(&:repetition?) }
it do
expect(cloned_champ_repetition.rows.flatten.count).to eq(4)
@ -308,7 +308,7 @@ RSpec.describe DossierCloneConcern do
end
context 'with new revision' do
let(:added_champ) { forked_dossier.champs.find { _1.libelle == "Un nouveau champ text" } }
let(:added_champ) { forked_dossier.project_champs_public.find { _1.libelle == "Un nouveau champ text" } }
let(:removed_champ) { dossier.champs.find { _1.stable_id == 99 } }
let(:new_dossier) { dossier.clone }
@ -325,12 +325,16 @@ RSpec.describe DossierCloneConcern do
expect(dossier.revision_id).to eq(procedure.revisions.first.id)
expect(new_dossier.revision_id).to eq(procedure.published_revision.id)
expect(forked_dossier.revision_id).to eq(procedure.published_revision_id)
is_expected.to eq(added: [added_champ], updated: [], removed: [removed_champ])
expect(subject[:added].map(&:stable_id)).to eq([added_champ.stable_id])
expect(subject[:added].first.new_record?).to be_truthy
expect(subject[:updated]).to be_empty
expect(subject[:removed]).to eq([removed_champ])
}
end
end
describe '#merge_fork' do
let(:dossier) { create(:dossier, :en_construction, :with_populated_champs, procedure:) }
subject { dossier.merge_fork(forked_dossier) }
context 'with updated champ' do
@ -348,11 +352,11 @@ RSpec.describe DossierCloneConcern do
dossier.debounce_index_search_terms_flag.remove
end
it { expect { subject }.to change { dossier.champs.size }.by(0) }
it { expect { subject }.to change { dossier.champs.reload.size }.by(0) }
it { expect { subject }.not_to change { dossier.champs.order(:created_at).reject { _1.stable_id.in?([99, 994]) }.map(&:value) } }
it { expect { subject }.to have_enqueued_job(DossierIndexSearchTermsJob).with(dossier) }
it { expect { subject }.to change { dossier.champs.find { _1.stable_id == 99 }.value }.from('old value').to('new value') }
it { expect { subject }.to change { dossier.champs.find { _1.stable_id == 994 }.value }.from('old value').to('new value in repetition') }
it { expect { subject }.to change { dossier.reload.champs.find { _1.stable_id == 994 }.value }.from('old value').to('new value in repetition') }
it 'fork is hidden after merge' do
subject
@ -362,8 +366,16 @@ RSpec.describe DossierCloneConcern do
end
context 'with new revision' do
let(:added_champ) { forked_dossier.champs.find { _1.libelle == "Un nouveau champ text" } }
let(:added_repetition_champ) { forked_dossier.champs.find { _1.libelle == "Texte en répétition" } }
let(:added_champ) {
tdc = forked_dossier.revision.types_de_champ.find { _1.libelle == "Un nouveau champ text" }
forked_dossier.champ_for_update(tdc, updated_by: 'test')
}
let(:added_repetition_champ) {
tdc_repetition = forked_dossier.revision.types_de_champ.find { _1.stable_id == 993 }
tdc = forked_dossier.revision.types_de_champ.find { _1.libelle == "Texte en répétition" }
row_id = forked_dossier.repetition_row_ids(tdc_repetition).first
forked_dossier.champ_for_update(tdc, row_id:, updated_by: 'test')
}
let(:removed_champ) { dossier.champs.find { _1.stable_id == 99 } }
let(:updated_champ) { dossier.champs.find { _1.stable_id == 991 } }
@ -393,8 +405,8 @@ RSpec.describe DossierCloneConcern do
super()
}
it { expect { subject }.to change { dossier.champs.size }.by(1) }
it { expect { subject }.to change { dossier.champs.order(:created_at).map(&:to_s) }.from(['old value', 'old value', 'Non', 'old value', 'old value']).to(['new value for updated champ', 'Non', 'old value', 'old value', 'new value for added champ', 'new value in repetition champ']) }
it { expect { subject }.to change { dossier.filled_champs.size }.by(1) }
it { expect { subject }.to change { dossier.filled_champs.sort_by(&:created_at).map(&:to_s) }.from(['old value', 'old value', 'Non', 'old value', 'old value']).to(['new value for updated champ', 'Non', 'old value', 'old value', 'new value for added champ', 'new value in repetition champ']) }
it "dossier after merge should be on last published revision" do
expect(dossier.revision_id).to eq(procedure.revisions.first.id)
@ -404,13 +416,13 @@ RSpec.describe DossierCloneConcern do
perform_enqueued_jobs only: DestroyRecordLaterJob
expect(dossier.revision_id).to eq(procedure.published_revision_id)
expect(dossier.champs.all? { dossier.revision.in?(_1.type_de_champ.revisions) }).to be_truthy
expect(dossier.filled_champs.all? { dossier.revision.in?(_1.type_de_champ.revisions) }).to be_truthy
expect(Dossier.exists?(forked_dossier.id)).to be_falsey
end
end
context 'with old revision having repetition' do
let(:removed_champ) { dossier.champs.find(&:repetition?) }
let(:removed_champ) { dossier.project_champs_public.find(&:repetition?) }
before do
dossier.champs.each do |champ|
@ -423,5 +435,42 @@ RSpec.describe DossierCloneConcern do
expect { subject }.not_to raise_error
end
end
context 'with added row' do
let(:repetition_champ) { forked_dossier.project_champs_public.find(&:repetition?) }
def dossier_rows(dossier) = dossier.champs.filter(&:row?)
before do
repetition_champ.add_row(updated_by: 'test')
end
it {
expect(dossier_rows(dossier).size).to eq(2)
expect { subject }.to change { dossier_rows(dossier).size }.by(1)
}
end
context 'with removed row' do
let(:repetition_champ) { forked_dossier.project_champs_public.find(&:repetition?) }
let(:row_id) { repetition_champ.row_ids.first }
def dossier_rows(dossier) = dossier.champs.filter(&:row?)
def dossier_discarded_rows(dossier) = dossier_rows(dossier).filter(&:discarded?)
before do
repetition_champ.remove_row(row_id, updated_by: 'test')
end
it {
expect(dossier_rows(dossier).size).to eq(2)
expect { subject }.to change { dossier_rows(dossier).size }.by(0)
}
it {
expect(dossier_discarded_rows(dossier).size).to eq(0)
expect { subject }.to change { dossier_discarded_rows(dossier).size }.by(1)
}
end
end
end

View file

@ -356,7 +356,7 @@ describe DossierRebaseConcern do
it "updates the brouillon champs with the latest revision changes" do
expect(dossier.revision).to eq(procedure.published_revision)
expect(dossier.project_champs_public.size).to eq(5)
expect(dossier.champs.count(&:public?)).to eq(7)
expect(dossier.champs.count(&:public?)).to eq(6)
expect(repetition_champ.rows.size).to eq(2)
expect(repetition_champ.rows[0].size).to eq(1)
expect(repetition_champ.rows[1].size).to eq(1)
@ -369,7 +369,7 @@ describe DossierRebaseConcern do
expect(procedure.revisions.size).to eq(3)
expect(dossier.revision).to eq(procedure.published_revision)
expect(dossier.project_champs_public.size).to eq(7)
expect(dossier.champs.count(&:public?)).to eq(13)
expect(dossier.champs.count(&:public?)).to eq(7)
expect(rebased_text_champ.value).to eq(text_champ.value)
expect(rebased_text_champ.type_de_champ).not_to eq(text_champ.type_de_champ)
expect(rebased_datetime_champ.type_champ).to eq(TypeDeChamp.type_champs.fetch(:date))
@ -381,7 +381,6 @@ describe DossierRebaseConcern do
expect(rebased_datetime_champ.rebased_at).not_to be_nil
expect(rebased_number_champ.rebased_at).to be_nil
expect(rebased_new_repetition_champ).not_to be_nil
expect(rebased_new_repetition_champ.rebased_at).not_to be_nil
expect(rebased_new_repetition_champ.rows.size).to eq(1)
expect(rebased_new_repetition_champ.rows[0].size).to eq(2)
@ -728,9 +727,8 @@ describe DossierRebaseConcern do
parent.update(type_champ: :integer_number)
end
it { expect { subject }.to change { dossier.champs.filter(&:child?).count }.from(2).to(0) }
it { expect { subject }.to change { Champ.count }.from(3).to(1) }
it { expect { subject }.to change { dossier.project_champs_public.find(&:repetition?)&.libelle }.from('p1').to(nil) }
it { expect { subject }.not_to change { Champ.count } }
end
end
end

View file

@ -72,7 +72,7 @@ describe DossierSectionsConcern do
context "when there are invisible sections" do
it "index accordingly header sections" do
expect(dossier.index_for_section_header(headers[0])).to eq(1)
expect(dossier.project_champ(headers[1], nil)).not_to be_visible
expect(dossier.project_champ(headers[1])).not_to be_visible
expect(dossier.index_for_section_header(headers[2])).to eq(2)
end
end
@ -81,7 +81,7 @@ describe DossierSectionsConcern do
let(:number_value) { 5 }
it "index accordingly header sections" do
expect(dossier.index_for_section_header(headers[0])).to eq(1)
expect(dossier.project_champ(headers[1], nil)).to be_visible
expect(dossier.project_champ(headers[1])).to be_visible
expect(dossier.index_for_section_header(headers[1])).to eq(2)
expect(dossier.index_for_section_header(headers[2])).to eq(3)
end

View file

@ -0,0 +1,149 @@
# frozen_string_literal: true
RSpec.describe DossierStateConcern do
include Logic
let(:procedure) { create(:procedure, :published, :for_individual, types_de_champ_public:, declarative_with_state:) }
let(:types_de_champ_public) do
[
{ type: :text, stable_id: 90 },
{ type: :text, stable_id: 91 },
{ type: :piece_justificative, stable_id: 92, condition: ds_eq(constant(true), constant(false)) },
{ type: :titre_identite, stable_id: 93, condition: ds_eq(constant(true), constant(false)) },
{ type: :repetition, stable_id: 94, children: [{ type: :text, stable_id: 941 }, { type: :text, stable_id: 942 }] },
{ type: :repetition, stable_id: 95, children: [{ type: :text, stable_id: 951 }] },
{ type: :repetition, stable_id: 96, children: [{ type: :text, stable_id: 961 }], condition: ds_eq(constant(true), constant(false)) },
{ type: :text, stable_id: 97, condition: ds_eq(constant(true), constant(false)) },
{ type: :titre_identite, stable_id: 98 }
]
end
let(:declarative_with_state) { nil }
let(:dossier_state) { :brouillon }
let(:dossier) do
create(:dossier, dossier_state, :with_individual, :with_populated_champs, procedure:).tap do |dossier|
procedure.draft_revision.remove_type_de_champ(91)
procedure.draft_revision.remove_type_de_champ(95)
procedure.draft_revision.remove_type_de_champ(942)
procedure.publish_revision!
perform_enqueued_jobs
dossier.reload
champ_repetition = dossier.project_champs_public.find { _1.stable_id == 94 }
row_id = champ_repetition.row_ids.first
champ_repetition.remove_row(row_id, updated_by: 'test')
end
end
describe 'submit brouillon' do
it do
expect(dossier.champs.size).to eq(20)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(2)
expect(dossier.champs.filter { _1.row? && _1.discarded? }.size).to eq(1)
expect(dossier.champs.filter { _1.row? && _1.stable_id.in?([95, 96]) }.size).to eq(4)
expect(dossier.champs.filter { _1.stable_id.in?([90, 92, 93, 97, 961, 951]) }.size).to eq(8)
champ_text = dossier.project_champs_public.find { _1.stable_id == 90 }
champ_text.update(value: '')
dossier.passer_en_construction!
dossier.reload
expect(dossier.champs.size).to eq(3)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(1)
expect(dossier.champs.filter { _1.row? && _1.discarded? }.size).to eq(0)
expect(dossier.champs.filter { _1.row? && _1.stable_id.in?([95, 96]) }.size).to eq(0)
expect(dossier.champs.filter { _1.stable_id.in?([90, 92, 93, 97, 961, 951]) }.size).to eq(0)
end
end
describe 'submit en construction' do
let(:dossier_state) { :en_construction }
it do
expect(dossier.champs.size).to eq(20)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(2)
expect(dossier.champs.filter { _1.row? && _1.discarded? }.size).to eq(1)
expect(dossier.champs.filter { _1.row? && _1.stable_id.in?([95, 96]) }.size).to eq(4)
expect(dossier.champs.filter { _1.stable_id.in?([92, 93, 97, 961, 951]) }.size).to eq(7)
dossier.submit_en_construction!
dossier.reload
expect(dossier.champs.size).to eq(4)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(1)
expect(dossier.champs.filter { _1.row? && _1.discarded? }.size).to eq(0)
expect(dossier.champs.filter { _1.row? && _1.stable_id.in?([95, 96]) }.size).to eq(0)
expect(dossier.champs.filter { _1.stable_id.in?([92, 93, 97, 961, 951]) }.size).to eq(0)
end
end
describe 'accepter' do
let(:dossier_state) { :en_instruction }
it do
expect(dossier.champs.size).to eq(20)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(2)
expect(dossier.champs.filter { _1.stable_id.in?([93, 98]) }.size).to eq(2)
dossier.accepter!(motivation: 'test')
dossier.reload
expect(dossier.champs.size).to eq(15)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(1)
expect(dossier.champs.filter { _1.stable_id.in?([93, 98]) }.size).to eq(0)
end
end
describe 'refuser' do
let(:dossier_state) { :en_instruction }
it do
expect(dossier.champs.size).to eq(20)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(2)
expect(dossier.champs.filter { _1.stable_id.in?([93, 98]) }.size).to eq(2)
dossier.refuser!(motivation: 'test')
dossier.reload
expect(dossier.champs.size).to eq(15)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(1)
expect(dossier.champs.filter { _1.stable_id.in?([93, 98]) }.size).to eq(0)
end
end
describe 'classer_sans_suite' do
let(:dossier_state) { :en_instruction }
it do
expect(dossier.champs.size).to eq(20)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(2)
expect(dossier.champs.filter { _1.stable_id.in?([93, 98]) }.size).to eq(2)
dossier.classer_sans_suite!(motivation: 'test')
dossier.reload
expect(dossier.champs.size).to eq(15)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(1)
expect(dossier.champs.filter { _1.stable_id.in?([93, 98]) }.size).to eq(0)
end
end
describe 'automatiquement' do
let(:dossier_state) { :en_construction }
let(:declarative_with_state) { Dossier.states.fetch(:accepte) }
describe 'accepter' do
it do
expect(dossier.champs.size).to eq(20)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(2)
expect(dossier.champs.filter { _1.stable_id.in?([93, 98]) }.size).to eq(2)
dossier.accepter_automatiquement!
dossier.reload
expect(dossier.champs.size).to eq(15)
expect(dossier.champs.filter { _1.row? && _1.stable_id == 94 }.size).to eq(1)
expect(dossier.champs.filter { _1.stable_id.in?([93, 98]) }.size).to eq(0)
end
end
end
end

View file

@ -237,11 +237,11 @@ describe TagsSubstitutionConcern, type: :model do
repetition.add_row(updated_by: 'test')
paul_champs, pierre_champs = repetition.rows
paul_champs.first.update(value: 'Paul')
paul_champs.last.update(value: 'Chavard')
champ_for_update(paul_champs.first).update(value: 'Paul')
champ_for_update(paul_champs.last).update(value: 'Chavard')
pierre_champs.first.update(value: 'Pierre')
pierre_champs.last.update(value: 'de La Morinerie')
champ_for_update(pierre_champs.first).update(value: 'Pierre')
champ_for_update(pierre_champs.last).update(value: 'de La Morinerie')
end
it { is_expected.to eq("Répétition\n\nNom : Paul\nPrénom : Chavard\n\nNom : Pierre\nPrénom : de La Morinerie") }
@ -258,7 +258,7 @@ describe TagsSubstitutionConcern, type: :model do
context 'and the champ has a primary value' do
before do
dossier.champs.find_by(stable_id: type_de_champ.stable_id).update(primary_value: 'primo')
dossier.champ_for_update(type_de_champ, updated_by: 'test').update(primary_value: 'primo')
dossier.reload
end
@ -266,7 +266,7 @@ describe TagsSubstitutionConcern, type: :model do
context 'and the champ has a secondary value' do
before do
dossier.champs.find_by(stable_id: type_de_champ.stable_id).update(secondary_value: 'secundo')
dossier.champ_for_update(type_de_champ, updated_by: 'test').update(secondary_value: 'secundo')
dossier.reload
end
@ -481,7 +481,7 @@ describe TagsSubstitutionConcern, type: :model do
before do
draft_type_de_champ.update(libelle: 'mon nouveau libellé')
dossier.project_champs_public.first.update(value: 'valeur')
procedure.update!(draft_revision: procedure.create_new_revision, published_revision: procedure.draft_revision)
procedure.publish_revision!
end
context "when using the champ's original label" do

View file

@ -29,7 +29,7 @@ describe DossierPreloader do
expect(subject.changed?).to be false
expect(first_child.type).to eq('Champs::TextChamp')
expect(repetition.id).not_to eq(first_child.id)
expect(repetition).not_to eq(first_child)
expect(subject.champs.first.dossier).to eq(subject)
expect(subject.champs.find(&:public?).dossier).to eq(subject)
expect(subject.project_champs_public.first.dossier).to eq(subject)
@ -40,7 +40,7 @@ describe DossierPreloader do
expect(subject.champs.find(&:public?).conditional?).to eq(false)
expect(subject.project_champs_public.first.conditional?).to eq(false)
expect(repetition.rows.first.first).to eq(first_child)
expect(repetition.rows.first.first.public_id).to eq(first_child.public_id)
expect(repetition_optional.row_ids).to be_empty
end

View file

@ -501,15 +501,13 @@ describe Dossier, type: :model do
let(:procedure) { create(:procedure, types_de_champ_public:) }
let(:dossier) { create(:dossier, :brouillon, :with_populated_champs, procedure:) }
before { expect(champ).to receive(:visible?).and_return(visible) }
context 'when piece_justificative' do
let(:types_de_champ_public) { [{ type: :piece_justificative }] }
let(:types_de_champ_public) { [{ type: :piece_justificative, condition: ds_eq(constant(true), constant(visible)) }] }
let(:champ) { dossier.project_champs_public.find(&:piece_justificative?) }
context 'when not visible' do
let(:visible) { false }
it { expect { subject }.to change { champ.reload.piece_justificative_file.attached? } }
it { expect { subject }.to change { Champ.exists?(champ.id) } }
end
context 'when visible' do
@ -519,12 +517,12 @@ describe Dossier, type: :model do
end
context 'when titre identite' do
let(:types_de_champ_public) { [{ type: :titre_identite }] }
let(:types_de_champ_public) { [{ type: :titre_identite, condition: ds_eq(constant(true), constant(visible)) }] }
let(:champ) { dossier.project_champs_public.find(&:titre_identite?) }
context 'when not visible' do
let(:visible) { false }
it { expect { subject }.to change { champ.reload.piece_justificative_file.attached? } }
it { expect { subject }.to change { Champ.exists?(champ.id) } }
end
context 'when visible' do
@ -1986,13 +1984,12 @@ describe Dossier, type: :model do
context 'with integer_number' do
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :integer_number, libelle: 'c1' }]) }
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
let(:integer_number_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:integer_number) } }
let(:integer_number_type_de_champ) { procedure.active_revision.types_de_champ_public.find(&:integer_number?) }
it 'give me back my decimal number' do
dossier
expect {
integer_number_type_de_champ.update(type_champ: :decimal_number)
procedure.update(published_revision: procedure.draft_revision, draft_revision: procedure.create_new_revision)
}.to change { dossier.reload.champ_values_for_export(procedure.all_revisions_types_de_champ.not_repetition.to_a, format: :xlsx) }
.from([["c1", 42]]).to([["c1", 42.0]])
end
@ -2032,7 +2029,7 @@ describe Dossier, type: :model do
procedure.draft_revision.find_and_ensure_exclusive_use(yes_no_type_de_champ.stable_id).update(libelle: 'Updated yes/no')
procedure.draft_revision.find_and_ensure_exclusive_use(commune_type_de_champ.stable_id).update(libelle: 'Commune de naissance')
procedure.draft_revision.find_and_ensure_exclusive_use(repetition_type_de_champ.stable_id).update(libelle: 'Repetition')
procedure.update(published_revision: procedure.draft_revision, draft_revision: procedure.create_new_revision)
procedure.publish_revision!
dossier.reload
procedure.reload
end

View file

@ -17,7 +17,7 @@ describe TypeDeChamp do
it do
dossier.revision.types_de_champ_public.each do |type_de_champ|
champ = dossier.project_champ(type_de_champ, nil)
champ = dossier.project_champ(type_de_champ)
expect(type_de_champ.dynamic_type.class.name).to match(/^TypesDeChamp::/)
expect(champ.class.name).to match(/^Champs::/)
end

View file

@ -3,14 +3,14 @@
RSpec.describe TypesDeChamp::PrefillRepetitionTypeDeChamp, type: :model do
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :repetition, children: [{}, { type: :integer_number }, { type: :regions }] }]) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:champ) { dossier.project_champs_public.first }
let(:type_de_champ) { champ.type_de_champ }
let(:champ) { dossier.champs.first }
let(:prefillable_subchamps) { TypesDeChamp::PrefillRepetitionTypeDeChamp.new(type_de_champ, procedure.active_revision).send(:prefillable_subchamps) }
let(:text_repetition) { prefillable_subchamps.first }
let(:integer_repetition) { prefillable_subchamps.second }
let(:region_repetition) { prefillable_subchamps.third }
let(:text_repetition_champs) { dossier.champs.where(stable_id: text_repetition.stable_id) }
let(:integer_repetition_champs) { dossier.champs.where(stable_id: integer_repetition.stable_id) }
let(:text_repetition_champs) { champ.rows.flat_map(&:first) }
let(:integer_repetition_champs) { champ.rows.flat_map(&:second) }
describe 'ancestors' do
subject { described_class.build(type_de_champ, procedure.active_revision) }

View file

@ -125,6 +125,13 @@ RSpec.configure do |config|
end
end
module SpecHelpers
def champ_for_update(champ)
champ.dossier.champ_for_update(champ.type_de_champ, row_id: champ.row_id, updated_by: 'test')
end
end
config.include SpecHelpers
config.include ActiveSupport::Testing::TimeHelpers
config.include Shoulda::Matchers::ActiveRecord, type: :model
config.include Shoulda::Matchers::ActiveModel, type: :model

View file

@ -10,8 +10,8 @@ describe PiecesJustificativesService do
let(:pj_service) { PiecesJustificativesService.new(user_profile:, export_template:) }
let(:user_profile) { build(:administrateur) }
def pj_champ(d) = d.project_champs_public.find { _1.type == 'Champs::PieceJustificativeChamp' }
def repetition(d) = d.champs.find_by(type: "Champs::RepetitionChamp")
def pj_champ(d) = d.project_champs_public.find(&:piece_justificative?)
def repetition(d) = d.project_champs_public.find(&:repetition?)
def attachments(champ) = champ.piece_justificative_file.attachments
before { attach_file_to_champ(pj_champ(witness)) }
@ -52,21 +52,20 @@ describe PiecesJustificativesService do
end
context 'with a repetition' do
let(:first_champ) { repetition(dossier).rows.first.first }
let(:second_champ) { repetition(dossier).rows.second.first }
let(:first_champ) { champ_for_update(repetition(dossier).rows.first.first) }
let(:second_champ) { champ_for_update(repetition(dossier).rows.second.first) }
before do
repetition(dossier).add_row(updated_by: 'test')
attach_file_to_champ(first_champ)
attach_file_to_champ(first_champ)
repetition(dossier).add_row(updated_by: 'test')
attach_file_to_champ(first_champ)
attach_file_to_champ(first_champ)
attach_file_to_champ(second_champ)
end
it do
first_child_attachments = attachments(repetition(dossier).rows.first.first)
second_child_attachments = attachments(repetition(dossier).rows.second.first)
first_child_attachments = attachments(first_champ)
second_child_attachments = attachments(second_champ)
expect(export_template).to receive(:attachment_path)
.with(dossier, first_child_attachments.first, index: 0, row_index: 0, champ: first_champ)
@ -103,7 +102,7 @@ describe PiecesJustificativesService do
let(:user_profile) { build(:administrateur) }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :piece_justificative }]) }
let(:witness) { create(:dossier, procedure: procedure) }
def pj_champ(d) = d.project_champs_public.find { |c| c.type == 'Champs::PieceJustificativeChamp' }
def pj_champ(d) = d.project_champs_public.find(&:piece_justificative?)
context 'with a single attachment' do
before do
@ -144,7 +143,7 @@ describe PiecesJustificativesService do
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :titre_identite }]) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:champ_identite) { dossier.project_champs_public.find { |c| c.type == 'Champs::TitreIdentiteChamp' } }
let(:champ_identite) { dossier.project_champs_public.find(&:titre_identite?) }
before { attach_file_to_champ(champ_identite) }
@ -261,7 +260,7 @@ describe PiecesJustificativesService do
let(:witness) { create(:dossier, procedure: procedure) }
let!(:private_pj) { create(:type_de_champ_piece_justificative, procedure: procedure, private: true) }
def private_pj_champ(d) = d.project_champs_private.find { |c| c.type == 'Champs::PieceJustificativeChamp' }
def private_pj_champ(d) = d.project_champs_private.find(&:piece_justificative?)
before do
attach_file_to_champ(private_pj_champ(dossier))
@ -501,17 +500,14 @@ describe PiecesJustificativesService do
end
let(:procedure) { create(:procedure, types_de_champ_public:) }
let(:dossier_1) { create(:dossier, procedure:) }
let(:champs) { dossier_1.champs }
let(:dossier_1) { create(:dossier, :with_populated_champs, procedure:) }
let(:champs) { dossier_1.filled_champs }
def pj_champ(d) = d.project_champs_public.find { _1.type == 'Champs::PieceJustificativeChamp' }
def repetition(d, index:) = d.project_champs_public.filter(&:repetition?)[index]
subject { PiecesJustificativesService.new(user_profile:, export_template: nil).send(:compute_champ_id_row_index, champs) }
before do
pj_champ(dossier_1)
# repet_0 (stable_id: r0)
# # row_0
# # # pj_champ_0 (stable_id: 0)
@ -553,7 +549,9 @@ describe PiecesJustificativesService do
end
def attach_file_to_champ(champ, safe = true)
champ = champ_for_update(champ)
attach_file(champ.piece_justificative_file, safe)
champ.save!
end
def attach_file(attachable, safe = true)

View file

@ -7,8 +7,8 @@ describe ProcedureExportService do
let(:export_template) { create(:export_template, :enabled_pjs, groupe_instructeur: procedure.defaut_groupe_instructeur) }
let(:service) { ProcedureExportService.new(procedure, procedure.dossiers, instructeur, export_template) }
def pj_champ(d) = d.project_champs_public.find { _1.type == 'Champs::PieceJustificativeChamp' }
def repetition(d) = d.champs.find_by(type: "Champs::RepetitionChamp")
def pj_champ(d) = d.project_champs_public.find(&:piece_justificative?)
def repetition(d) = d.project_champs_public.find(&:repetition?)
def attachments(champ) = champ.piece_justificative_file.attachments
before do
@ -69,7 +69,9 @@ describe ProcedureExportService do
end
def attach_file_to_champ(champ, safe = true)
champ = champ_for_update(champ)
attach_file(champ.piece_justificative_file, safe)
champ.save!
end
def attach_file(attachable, safe = true)

View file

@ -171,7 +171,7 @@ describe 'The user', js: true do
wait_until { page.all(".row").size == 1 }
# removing a repetition means one child only, thus its button destroy is not visible
expect(page).to have_selector(".repetition .row:first-child .utils-repetition-required-destroy-button", count: 1, visible: false)
end.to change { Champ.count }
end.to change { Champ.where.not(discarded_at: nil).count }
end
let(:simple_procedure) {
@ -569,6 +569,16 @@ describe 'The user', js: true do
expect(page).to have_no_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
fill_in('age du candidat', with: '18')
wait_for_autosave
# the champ keeps their previous value so they are all displayed
expect(page).to have_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_css('label', text: 'tonnage', visible: true)
fill_in('age du candidat', with: '2')
wait_for_autosave
click_on 'Déposer le dossier'
click_on 'Accéder à votre dossier'
click_on 'Modifier mon dossier'
@ -576,13 +586,6 @@ describe 'The user', js: true do
expect(page).to have_css('label', text: 'age du candidat', visible: true)
expect(page).to have_no_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
fill_in('age du candidat', with: '18')
wait_for_autosave
# the champ keeps their previous value so they are all displayed
expect(page).to have_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_css('label', text: 'tonnage', visible: true)
end
end
end

View file

@ -1,30 +0,0 @@
# frozen_string_literal: true
require "rails_helper"
module Maintenance
RSpec.describe RemovePieceJustificativeFileNotVisibleTask do
describe "#process" do
subject(:process) { described_class.process(dossier) }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :piece_justificative }]) }
let(:dossier) { create(:dossier, :en_construction, :with_populated_champs, procedure:) }
before { expect(champ).to receive(:visible?).and_return(visible) }
context 'when piece_justificative' do
let(:champ) { dossier.filled_champs_public.find(&:piece_justificative?) }
context 'when not visible' do
let(:visible) { false }
it { expect { subject }.to change { champ.reload.piece_justificative_file.attached? } }
end
context 'when visible' do
let(:visible) { true }
it { expect { subject }.not_to change { champ.reload.piece_justificative_file.attached? } }
end
end
end
end
end

View file

@ -19,9 +19,9 @@ describe 'shared/dossiers/edit', type: :view do
let(:type_de_champ_checkbox) { procedure.draft_types_de_champ_public.find(&:checkbox?) }
let(:type_de_champ_textarea) { procedure.draft_types_de_champ_public.find(&:textarea?) }
let(:champ_checkbox) { dossier.project_champ(type_de_champ_checkbox, nil) }
let(:champ_dossier_link) { dossier.project_champ(type_de_champ_dossier_link, nil) }
let(:champ_textarea) { dossier.project_champ(type_de_champ_textarea, nil) }
let(:champ_checkbox) { dossier.project_champ(type_de_champ_checkbox) }
let(:champ_dossier_link) { dossier.project_champ(type_de_champ_dossier_link) }
let(:champ_textarea) { dossier.project_champ(type_de_champ_textarea) }
let(:types_de_champ_public) { [{ type: :checkbox }, { type: :header_section }, { type: :explication }, { type: :dossier_link }, { type: :textarea }] }