Merge branch 'main' into etq-usager-bouton-jdma
This commit is contained in:
commit
97ef01b075
170 changed files with 4076 additions and 1251 deletions
|
@ -88,10 +88,6 @@ class Champ < ApplicationRecord
|
|||
parent_id.present?
|
||||
end
|
||||
|
||||
def stable_id_with_row
|
||||
[row_id, stable_id].compact
|
||||
end
|
||||
|
||||
# used for the `required` html attribute
|
||||
# check visibility to avoid hidden required input
|
||||
# which prevent the form from being sent.
|
||||
|
|
|
@ -21,6 +21,10 @@ module ChampConditionalConcern
|
|||
end
|
||||
end
|
||||
|
||||
def reset_visible # recompute after a dossier update
|
||||
remove_instance_variable :@visible if instance_variable_defined? :@visible
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def champs_for_condition
|
||||
|
|
|
@ -22,7 +22,7 @@ module DossierRebaseConcern
|
|||
end
|
||||
|
||||
def pending_changes
|
||||
procedure.published_revision.present? ? revision.compare(procedure.published_revision) : []
|
||||
procedure.published_revision.present? ? revision.compare_types_de_champ(procedure.published_revision) : []
|
||||
end
|
||||
|
||||
def can_rebase_mandatory_change?(stable_id)
|
||||
|
|
|
@ -307,7 +307,8 @@ module TagsSubstitutionConcern
|
|||
|
||||
def format_date(date)
|
||||
if date.present?
|
||||
date.strftime('%d/%m/%Y')
|
||||
format = defined?(self.class::FORMAT_DATE) ? self.class::FORMAT_DATE : '%d/%m/%Y'
|
||||
date.strftime(format)
|
||||
else
|
||||
''
|
||||
end
|
||||
|
|
|
@ -59,7 +59,7 @@ class Dossier < ApplicationRecord
|
|||
has_many :previous_follows, -> { inactive }, class_name: 'Follow', inverse_of: :dossier
|
||||
has_many :followers_instructeurs, through: :follows, source: :instructeur
|
||||
has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur
|
||||
has_many :avis, inverse_of: :dossier, dependent: :destroy
|
||||
has_many :avis, -> { order(:created_at) }, inverse_of: :dossier, dependent: :destroy
|
||||
has_many :experts, through: :avis
|
||||
has_many :traitements, -> { order(:processed_at) }, inverse_of: :dossier, dependent: :destroy do
|
||||
def passer_en_construction(instructeur: nil, processed_at: Time.zone.now)
|
||||
|
@ -156,7 +156,7 @@ class Dossier < ApplicationRecord
|
|||
state :sans_suite
|
||||
|
||||
event :passer_en_construction, after: :after_passer_en_construction, after_commit: :after_commit_passer_en_construction do
|
||||
transitions from: :brouillon, to: :en_construction
|
||||
transitions from: :brouillon, to: :en_construction, guard: :can_passer_en_construction?
|
||||
end
|
||||
|
||||
event :passer_en_instruction, after: :after_passer_en_instruction, after_commit: :after_commit_passer_en_instruction do
|
||||
|
@ -558,8 +558,18 @@ class Dossier < ApplicationRecord
|
|||
false
|
||||
end
|
||||
|
||||
def blocked_with_pending_correction?
|
||||
procedure.feature_enabled?(:blocking_pending_correction) && pending_correction?
|
||||
end
|
||||
|
||||
def can_passer_en_construction?
|
||||
return true if !revision.ineligibilite_enabled
|
||||
|
||||
!revision.ineligibilite_rules.compute(champs_for_revision(scope: :public))
|
||||
end
|
||||
|
||||
def can_passer_en_instruction?
|
||||
return false if procedure.feature_enabled?(:blocking_pending_correction) && pending_correction?
|
||||
return false if blocked_with_pending_correction?
|
||||
|
||||
true
|
||||
end
|
||||
|
@ -932,6 +942,7 @@ class Dossier < ApplicationRecord
|
|||
.map do |champ|
|
||||
champ.errors.add(:value, :missing)
|
||||
end
|
||||
.each { errors.import(_1) }
|
||||
end
|
||||
|
||||
def demander_un_avis!(avis)
|
||||
|
@ -1006,7 +1017,6 @@ class Dossier < ApplicationRecord
|
|||
else
|
||||
columns << ['Entreprise raison sociale', etablissement&.entreprise_raison_sociale]
|
||||
end
|
||||
|
||||
if procedure.chorusable? && procedure.chorus_configuration.complete?
|
||||
columns += [
|
||||
['Domaine Fonctionnel', procedure.chorus_configuration.domaine_fonctionnel&.fetch("code") { '' }],
|
||||
|
|
|
@ -7,6 +7,7 @@ class ExportTemplate < ApplicationRecord
|
|||
validates_with ExportTemplateValidator
|
||||
|
||||
DOSSIER_STATE = Dossier.states.fetch(:en_construction)
|
||||
FORMAT_DATE = "%Y-%m-%d"
|
||||
|
||||
def set_default_values
|
||||
content["default_dossier_directory"] = tiptap_json("dossier-")
|
||||
|
@ -48,7 +49,7 @@ class ExportTemplate < ApplicationRecord
|
|||
def attachment_and_path(dossier, attachment, index: 0, row_index: nil, champ: nil)
|
||||
[
|
||||
attachment,
|
||||
path(dossier, attachment, index, row_index, champ)
|
||||
path(dossier, attachment, index:, row_index:, champ:)
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -116,7 +117,7 @@ class ExportTemplate < ApplicationRecord
|
|||
"#{render_attributes_for(content["pdf_name"], dossier)}.pdf"
|
||||
end
|
||||
|
||||
def path(dossier, attachment, index, row_index, champ)
|
||||
def path(dossier, attachment, index: 0, row_index: nil, champ: nil)
|
||||
if attachment.name == 'pdf_export_for_instructeur'
|
||||
return export_path(dossier)
|
||||
end
|
||||
|
@ -128,6 +129,8 @@ class ExportTemplate < ApplicationRecord
|
|||
'messagerie'
|
||||
when 'Avis'
|
||||
'avis'
|
||||
when 'Attestation', 'Etablissement'
|
||||
'pieces_justificatives'
|
||||
else
|
||||
# for attachment
|
||||
return attachment_path(dossier, attachment, index, row_index, champ)
|
||||
|
|
|
@ -259,13 +259,19 @@ class Procedure < ApplicationRecord
|
|||
validates :lien_dpo, url: { no_local: true, allow_blank: true, accept_email: true }
|
||||
|
||||
validates :draft_types_de_champ_public,
|
||||
'types_de_champ/condition': true,
|
||||
'types_de_champ/expression_reguliere': true,
|
||||
'types_de_champ/header_section_consistency': true,
|
||||
'types_de_champ/no_empty_block': true,
|
||||
'types_de_champ/no_empty_drop_down': true,
|
||||
on: :publication
|
||||
on: [:types_de_champ_public_editor, :publication]
|
||||
|
||||
validates :draft_types_de_champ_private,
|
||||
'types_de_champ/condition': true,
|
||||
'types_de_champ/header_section_consistency': true,
|
||||
'types_de_champ/no_empty_block': true,
|
||||
'types_de_champ/no_empty_drop_down': true,
|
||||
on: :publication
|
||||
on: [:types_de_champ_private_editor, :publication]
|
||||
|
||||
validate :check_juridique, on: [:create, :publication]
|
||||
|
||||
|
@ -287,7 +293,7 @@ class Procedure < ApplicationRecord
|
|||
|
||||
validates_with MonAvisEmbedValidator
|
||||
|
||||
validates_associated :draft_revision, on: :publication
|
||||
validate :validates_associated_draft_revision_with_context
|
||||
validates_associated :initiated_mail, on: :publication
|
||||
validates_associated :received_mail, on: :publication
|
||||
validates_associated :closed_mail, on: :publication
|
||||
|
@ -425,11 +431,15 @@ class Procedure < ApplicationRecord
|
|||
|
||||
def draft_changed?
|
||||
preload_draft_and_published_revisions
|
||||
!brouillon? && published_revision.different_from?(draft_revision) && revision_changes.present?
|
||||
!brouillon? && (types_de_champ_revision_changes.present? || ineligibilite_rules_revision_changes.present?)
|
||||
end
|
||||
|
||||
def revision_changes
|
||||
published_revision.compare(draft_revision)
|
||||
def types_de_champ_revision_changes
|
||||
published_revision.compare_types_de_champ(draft_revision)
|
||||
end
|
||||
|
||||
def ineligibilite_rules_revision_changes
|
||||
published_revision.compare_ineligibilite_rules(draft_revision)
|
||||
end
|
||||
|
||||
def preload_draft_and_published_revisions
|
||||
|
@ -553,6 +563,7 @@ class Procedure < ApplicationRecord
|
|||
procedure.closing_notification_brouillon = false
|
||||
procedure.closing_notification_en_cours = false
|
||||
procedure.template = false
|
||||
procedure.monavis_embed = nil
|
||||
|
||||
if !procedure.valid?
|
||||
procedure.errors.attribute_names.each do |attribute|
|
||||
|
@ -1014,6 +1025,13 @@ class Procedure < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def validates_associated_draft_revision_with_context
|
||||
return if draft_revision.blank?
|
||||
return if draft_revision.validate(validation_context)
|
||||
|
||||
draft_revision.errors.map { errors.import(_1) }
|
||||
end
|
||||
|
||||
def validate_auto_archive_on_in_the_future
|
||||
return if auto_archive_on.nil?
|
||||
return if auto_archive_on.future?
|
||||
|
|
|
@ -14,7 +14,7 @@ ProcedureDetail = Struct.new(:id, :libelle, :published_at, :aasm_state, :estimat
|
|||
end
|
||||
|
||||
def parsed_latest_zone_labels
|
||||
# Replace curly braces with square brackets to make it a valid JSON array
|
||||
return [] if latest_zone_labels.nil? || latest_zone_labels.strip.empty?
|
||||
JSON.parse(latest_zone_labels.tr('{', '[').tr('}', ']'))
|
||||
rescue JSON::ParserError
|
||||
[]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
class ProcedureRevision < ApplicationRecord
|
||||
include Logic
|
||||
self.implicit_order_column = :created_at
|
||||
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false
|
||||
belongs_to :dossier_submitted_message, inverse_of: :revisions, optional: true, dependent: :destroy
|
||||
|
@ -17,12 +18,19 @@ class ProcedureRevision < ApplicationRecord
|
|||
|
||||
scope :ordered, -> { order(:created_at) }
|
||||
|
||||
validate :conditions_are_valid?
|
||||
validate :header_sections_are_valid?
|
||||
validate :expressions_regulieres_are_valid?
|
||||
validates :ineligibilite_message, presence: true, if: -> { ineligibilite_enabled? }
|
||||
|
||||
delegate :path, to: :procedure, prefix: true
|
||||
|
||||
validate :ineligibilite_rules_are_valid?,
|
||||
on: [:ineligibilite_rules_editor, :publication]
|
||||
validates :ineligibilite_message,
|
||||
presence: true,
|
||||
if: -> { ineligibilite_enabled? },
|
||||
on: [:ineligibilite_rules_editor, :publication]
|
||||
|
||||
serialize :ineligibilite_rules, LogicSerializer
|
||||
|
||||
def build_champs_public
|
||||
# reload: it can be out of sync in test if some tdcs are added wihtout using add_tdc
|
||||
types_de_champ_public.reload.map(&:build_champ)
|
||||
|
@ -140,16 +148,18 @@ class ProcedureRevision < ApplicationRecord
|
|||
!draft?
|
||||
end
|
||||
|
||||
def different_from?(revision)
|
||||
revision_types_de_champ != revision.revision_types_de_champ
|
||||
end
|
||||
|
||||
def compare(revision)
|
||||
def compare_types_de_champ(revision)
|
||||
changes = []
|
||||
changes += compare_revision_types_de_champ(revision_types_de_champ, revision.revision_types_de_champ)
|
||||
changes
|
||||
end
|
||||
|
||||
def compare_ineligibilite_rules(revision)
|
||||
changes = []
|
||||
changes += compare_revision_ineligibilite_rules(revision)
|
||||
changes
|
||||
end
|
||||
|
||||
def dossier_for_preview(user)
|
||||
dossier = Dossier
|
||||
.create_with(autorisation_donnees: true)
|
||||
|
@ -255,6 +265,10 @@ class ProcedureRevision < ApplicationRecord
|
|||
types_de_champ_public.filter(&:routable?)
|
||||
end
|
||||
|
||||
def conditionable_types_de_champ
|
||||
types_de_champ_for(scope: :public).filter(&:conditionable?)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compute_estimated_fill_duration
|
||||
|
@ -322,6 +336,29 @@ class ProcedureRevision < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def compare_revision_ineligibilite_rules(new_revision)
|
||||
from_ineligibilite_rules = ineligibilite_rules
|
||||
to_ineligibilite_rules = new_revision.ineligibilite_rules
|
||||
changes = []
|
||||
|
||||
if from_ineligibilite_rules.present? && to_ineligibilite_rules.blank?
|
||||
changes << ProcedureRevisionChange::RemoveEligibiliteRuleChange
|
||||
end
|
||||
if from_ineligibilite_rules.blank? && to_ineligibilite_rules.present?
|
||||
changes << ProcedureRevisionChange::AddEligibiliteRuleChange
|
||||
end
|
||||
if from_ineligibilite_rules != to_ineligibilite_rules
|
||||
changes << ProcedureRevisionChange::UpdateEligibiliteRuleChange
|
||||
end
|
||||
if ineligibilite_message != new_revision.ineligibilite_message
|
||||
changes << ProcedureRevisionChange::UpdateEligibiliteMessageChange
|
||||
end
|
||||
if ineligibilite_enabled != new_revision.ineligibilite_enabled
|
||||
changes << (new_revision.ineligibilite_enabled ? ProcedureRevisionChange::EligibiliteEnabledChange : ProcedureRevisionChange::EligibiliteDisabledChange)
|
||||
end
|
||||
changes.map { _1.new(self, new_revision) }
|
||||
end
|
||||
|
||||
def compare_type_de_champ(from_type_de_champ, to_type_de_champ, from_coordinates, to_coordinates)
|
||||
changes = []
|
||||
if from_type_de_champ.type_champ != to_type_de_champ.type_champ
|
||||
|
@ -446,6 +483,13 @@ class ProcedureRevision < ApplicationRecord
|
|||
changes
|
||||
end
|
||||
|
||||
def ineligibilite_rules_are_valid?
|
||||
if ineligibilite_rules
|
||||
ineligibilite_rules.errors(types_de_champ_for(scope: :public).to_a)
|
||||
.each { errors.add(:ineligibilite_rules, :invalid) }
|
||||
end
|
||||
end
|
||||
|
||||
def replace_type_de_champ_by_clone(coordinate)
|
||||
cloned_type_de_champ = coordinate.type_de_champ.deep_clone do |original, kopy|
|
||||
ClonePiecesJustificativesService.clone_attachments(original, kopy)
|
||||
|
@ -453,48 +497,4 @@ class ProcedureRevision < ApplicationRecord
|
|||
coordinate.update!(type_de_champ: cloned_type_de_champ)
|
||||
cloned_type_de_champ
|
||||
end
|
||||
|
||||
def conditions_are_valid?
|
||||
public_tdcs = types_de_champ_public.to_a
|
||||
.flat_map { _1.repetition? ? children_of(_1) : _1 }
|
||||
|
||||
public_tdcs
|
||||
.map.with_index
|
||||
.filter_map { |tdc, i| tdc.condition? ? [tdc, i] : nil }
|
||||
.map do |tdc, i|
|
||||
[tdc, tdc.condition.errors(public_tdcs.take(i))]
|
||||
end
|
||||
.filter { |_tdc, errors| errors.present? }
|
||||
.each { |tdc, message| errors.add(:condition, message, type_de_champ: tdc) }
|
||||
end
|
||||
|
||||
def header_sections_are_valid?
|
||||
public_tdcs = types_de_champ_public.to_a
|
||||
|
||||
root_tdcs_errors = errors_for_header_sections_order(public_tdcs)
|
||||
repetition_tdcs_errors = public_tdcs
|
||||
.filter_map { _1.repetition? ? children_of(_1) : nil }
|
||||
.map { errors_for_header_sections_order(_1) }
|
||||
|
||||
repetition_tdcs_errors + root_tdcs_errors
|
||||
end
|
||||
|
||||
def expressions_regulieres_are_valid?
|
||||
types_de_champ_public.to_a
|
||||
.flat_map { _1.repetition? ? children_of(_1) : _1 }
|
||||
.each do |tdc|
|
||||
if tdc.expression_reguliere? && tdc.invalid_regexp?
|
||||
errors.add(:expression_reguliere, type_de_champ: tdc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def errors_for_header_sections_order(tdcs)
|
||||
tdcs
|
||||
.map.with_index
|
||||
.filter_map { |tdc, i| tdc.header_section? ? [tdc, i] : nil }
|
||||
.map { |tdc, i| [tdc, tdc.check_coherent_header_level(tdcs.take(i))] }
|
||||
.filter { |_tdc, errors| errors.present? }
|
||||
.each { |tdc, message| errors.add(:header_section, message, type_de_champ: tdc) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
class ProcedureRevisionChange
|
||||
attr_reader :type_de_champ
|
||||
def initialize(type_de_champ)
|
||||
@type_de_champ = type_de_champ
|
||||
class TypeDeChange
|
||||
attr_reader :type_de_champ
|
||||
def initialize(type_de_champ)
|
||||
@type_de_champ = type_de_champ
|
||||
end
|
||||
|
||||
def label = @type_de_champ.libelle
|
||||
def stable_id = @type_de_champ.stable_id
|
||||
def private? = @type_de_champ.private?
|
||||
def child? = @type_de_champ.child?
|
||||
|
||||
def to_h = { op:, stable_id:, label:, private: private? }
|
||||
end
|
||||
|
||||
def label = @type_de_champ.libelle
|
||||
def stable_id = @type_de_champ.stable_id
|
||||
def private? = @type_de_champ.private?
|
||||
def child? = @type_de_champ.child?
|
||||
|
||||
def to_h = { op:, stable_id:, label:, private: private? }
|
||||
|
||||
class AddChamp < ProcedureRevisionChange
|
||||
class AddChamp < TypeDeChange
|
||||
def initialize(type_de_champ)
|
||||
super(type_de_champ)
|
||||
end
|
||||
|
@ -23,7 +25,7 @@ class ProcedureRevisionChange
|
|||
def to_h = super.merge(mandatory: mandatory?)
|
||||
end
|
||||
|
||||
class RemoveChamp < ProcedureRevisionChange
|
||||
class RemoveChamp < TypeDeChange
|
||||
def initialize(type_de_champ)
|
||||
super(type_de_champ)
|
||||
end
|
||||
|
@ -32,7 +34,7 @@ class ProcedureRevisionChange
|
|||
def can_rebase?(dossier = nil) = true
|
||||
end
|
||||
|
||||
class MoveChamp < ProcedureRevisionChange
|
||||
class MoveChamp < TypeDeChange
|
||||
attr_reader :from, :to
|
||||
|
||||
def initialize(type_de_champ, from, to)
|
||||
|
@ -46,7 +48,7 @@ class ProcedureRevisionChange
|
|||
def to_h = super.merge(from:, to:)
|
||||
end
|
||||
|
||||
class UpdateChamp < ProcedureRevisionChange
|
||||
class UpdateChamp < TypeDeChange
|
||||
attr_reader :attribute, :from, :to
|
||||
|
||||
def initialize(type_de_champ, attribute, from, to)
|
||||
|
@ -75,4 +77,48 @@ class ProcedureRevisionChange
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
class EligibiliteRulesChange
|
||||
attr_reader :previous_revision, :new_revision
|
||||
def initialize(previous_revision, new_revision)
|
||||
@previous_revision = previous_revision
|
||||
@new_revision = new_revision
|
||||
@previous_ineligibilite_rules = @previous_revision.ineligibilite_rules
|
||||
@new_ineligibilite_rules = @new_revision.ineligibilite_rules
|
||||
end
|
||||
|
||||
def i18n_params
|
||||
{
|
||||
previous_condition: @previous_ineligibilite_rules&.to_s(previous_revision.types_de_champ.filter { @previous_ineligibilite_rules.sources.include? _1.stable_id }),
|
||||
new_condition: @new_ineligibilite_rules&.to_s(new_revision.types_de_champ.filter { @new_ineligibilite_rules.sources.include? _1.stable_id })
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class AddEligibiliteRuleChange < EligibiliteRulesChange
|
||||
def op = :add
|
||||
end
|
||||
|
||||
class RemoveEligibiliteRuleChange < EligibiliteRulesChange
|
||||
def op = :remove
|
||||
end
|
||||
|
||||
class UpdateEligibiliteRuleChange < EligibiliteRulesChange
|
||||
def op = :update
|
||||
end
|
||||
|
||||
class EligibiliteEnabledChange < EligibiliteRulesChange
|
||||
def op = :enabled
|
||||
def i18n_params = {}
|
||||
end
|
||||
|
||||
class EligibiliteDisabledChange < EligibiliteRulesChange
|
||||
def op = :disabled
|
||||
def i18n_params = {}
|
||||
end
|
||||
|
||||
class UpdateEligibiliteMessageChange < EligibiliteRulesChange
|
||||
def op = :message_updated
|
||||
def i18n_params = { ineligibilite_message: @new_revision.ineligibilite_message }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -75,4 +75,8 @@ class ProcedureRevisionTypeDeChamp < ApplicationRecord
|
|||
def used_by_routing_rules?
|
||||
stable_id.in?(procedure.stable_ids_used_by_routing_rules)
|
||||
end
|
||||
|
||||
def used_by_ineligibilite_rules?
|
||||
revision.ineligibilite_enabled? && stable_id.in?(revision.ineligibilite_rules&.sources || [])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -505,15 +505,15 @@ class TypeDeChamp < ApplicationRecord
|
|||
end
|
||||
|
||||
def check_coherent_header_level(upper_tdcs)
|
||||
errs = []
|
||||
previous_level = previous_section_level(upper_tdcs)
|
||||
|
||||
current_level = header_section_level_value.to_i
|
||||
|
||||
difference = current_level - previous_level
|
||||
if current_level > previous_level && difference != 1
|
||||
errs << I18n.t('activerecord.errors.type_de_champ.attributes.header_section_level.gap_error', level: current_level - previous_level - 1)
|
||||
I18n.t('activerecord.errors.type_de_champ.attributes.header_section_level.gap_error', level: current_level - previous_level - 1)
|
||||
else
|
||||
nil
|
||||
end
|
||||
errs
|
||||
end
|
||||
|
||||
def current_section_level(revision)
|
||||
|
@ -657,6 +657,10 @@ class TypeDeChamp < ApplicationRecord
|
|||
type_champ.in?(ROUTABLE_TYPES)
|
||||
end
|
||||
|
||||
def conditionable?
|
||||
Logic::ChampValue::MANAGED_TYPE_DE_CHAMP.values.include?(type_champ)
|
||||
end
|
||||
|
||||
def invalid_regexp?
|
||||
self.errors.delete(:expression_reguliere)
|
||||
self.errors.delete(:expression_reguliere_exemple_text)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue