Compare commits
23 commits
main
...
gh-readonl
Author | SHA1 | Date | |
---|---|---|---|
|
bef9d26fc2 | ||
|
0d624aee25 | ||
|
316ca7b3e8 | ||
|
e393132fd8 | ||
|
1a704f0479 | ||
|
c8ed0532ed | ||
|
14c9012b87 | ||
|
21a829ec1e | ||
|
52dff40197 | ||
|
f1bcb84832 | ||
|
1ae40f1a22 | ||
|
6a2d2ae0a7 | ||
|
9c695cf39f | ||
|
02fab28ad6 | ||
|
0a2b24aea2 | ||
|
d52ee477b0 | ||
|
384b7f9fac | ||
|
f5c8271e26 | ||
|
25956c5141 | ||
|
b52a2ca972 | ||
|
c2461f230c | ||
|
34697a3085 | ||
|
65730bcfcb |
29 changed files with 361 additions and 15 deletions
|
@ -61,7 +61,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='date'] {
|
input[type='date'] {
|
||||||
display: inline;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,10 @@ class TypesDeChampEditor::ConditionsErrorsComponent < ApplicationComponent
|
||||||
right: right.to_s.downcase)
|
right: right.to_s.downcase)
|
||||||
in { type: :required_list }
|
in { type: :required_list }
|
||||||
t('required_list', scope: '.errors')
|
t('required_list', scope: '.errors')
|
||||||
|
in { type: :required_include, operator_name: "Logic::Eq" }
|
||||||
|
t("required_include.eq", scope: '.errors')
|
||||||
|
in { type: :required_include, operator_name: "Logic::NotEq" }
|
||||||
|
t("required_include.not_eq", scope: '.errors')
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
---
|
---
|
||||||
fr:
|
en:
|
||||||
errors:
|
errors:
|
||||||
not_available: "A targeted field is not available."
|
not_available: "A targeted field is not available."
|
||||||
unmanaged: "The field « %{libelle} » is a « %{type_champ} » and cannot be used as conditional source."
|
unmanaged: "The field « %{libelle} » is a « %{type_champ} » and cannot be used as conditional source."
|
||||||
incompatible: "The field « %{libelle} » is a « %{type_champ} ». It cannot be %{operator} « %{right} »."
|
incompatible: "The field « %{libelle} » is a « %{type_champ} ». It cannot be %{operator} « %{right} »."
|
||||||
required_number: "« %{operator} » applies only to number."
|
required_number: "« %{operator} » applies only to number."
|
||||||
required_list: "The « include » operator only applies to simple or multiple choice."
|
required_list: "The « include » operator only applies to simple or multiple choice."
|
||||||
|
required_include:
|
||||||
|
eq: "The « is » operator does not apply to multiple dropdown list. Select the « includes » operator."
|
||||||
|
not_eq: "The « is not » operator does not apply to multiple dropdown list."
|
||||||
not_included: "« %{right} » is not included in « %{libelle} »."
|
not_included: "« %{right} » is not included in « %{libelle} »."
|
||||||
|
|
|
@ -6,4 +6,7 @@ fr:
|
||||||
incompatible: "Le champ « %{libelle} » est de type « %{type_champ} ». Il ne peut pas être %{operator} « %{right} »."
|
incompatible: "Le champ « %{libelle} » est de type « %{type_champ} ». Il ne peut pas être %{operator} « %{right} »."
|
||||||
required_number: "« %{operator} » ne s'applique qu'à des nombres."
|
required_number: "« %{operator} » ne s'applique qu'à des nombres."
|
||||||
required_list: "Lʼopérateur « inclus » ne s'applique qu'au choix simple ou multiple."
|
required_list: "Lʼopérateur « inclus » ne s'applique qu'au choix simple ou multiple."
|
||||||
|
required_include:
|
||||||
|
eq: "Lʼopérateur « est » ne s'applique pas au choix multiple. Sélectionnez l’opérateur « contient »."
|
||||||
|
not_eq: "Lʼopérateur « n’est pas » ne s'applique pas au choix multiple."
|
||||||
not_included: "« %{right} » ne fait pas partie de « %{libelle} »."
|
not_included: "« %{right} » ne fait pas partie de « %{libelle} »."
|
||||||
|
|
|
@ -111,7 +111,7 @@ class Avis < ApplicationRecord
|
||||||
|
|
||||||
def remind_by!(revocator)
|
def remind_by!(revocator)
|
||||||
return false if !remindable_by?(revocator) || answer.present?
|
return false if !remindable_by?(revocator) || answer.present?
|
||||||
update!(reminded_at: Time.zone.now)
|
update_column(:reminded_at, Time.zone.now)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -21,4 +21,11 @@
|
||||||
# type_de_champ_id :integer
|
# type_de_champ_id :integer
|
||||||
#
|
#
|
||||||
class Champs::EmailChamp < Champs::TextChamp
|
class Champs::EmailChamp < Champs::TextChamp
|
||||||
|
validates :value,
|
||||||
|
format: {
|
||||||
|
with: Devise.email_regexp,
|
||||||
|
message: I18n.t('invalid', scope: 'activerecord.errors.models.email_champ.attributes.value')
|
||||||
|
},
|
||||||
|
allow_nil: true,
|
||||||
|
if: -> { validation_context != :brouillon }
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,7 +51,7 @@ class GeoArea < ApplicationRecord
|
||||||
scope :selections_utilisateur, -> { where(source: sources.fetch(:selection_utilisateur)) }
|
scope :selections_utilisateur, -> { where(source: sources.fetch(:selection_utilisateur)) }
|
||||||
scope :cadastres, -> { where(source: sources.fetch(:cadastre)) }
|
scope :cadastres, -> { where(source: sources.fetch(:cadastre)) }
|
||||||
|
|
||||||
validates :geometry, geo_json: true, allow_blank: false
|
validates :geometry, geo_json: true, allow_nil: false
|
||||||
before_validation :normalize_geometry
|
before_validation :normalize_geometry
|
||||||
|
|
||||||
def to_feature
|
def to_feature
|
||||||
|
|
|
@ -20,6 +20,12 @@ class Logic::Eq < Logic::BinaryOperator
|
||||||
stable_id: @left.stable_id,
|
stable_id: @left.stable_id,
|
||||||
right: @right
|
right: @right
|
||||||
}
|
}
|
||||||
|
elsif @left.type(type_de_champs) == :enums
|
||||||
|
errors << {
|
||||||
|
type: :required_include,
|
||||||
|
stable_id: @left.try(:stable_id),
|
||||||
|
operator_name: self.class.name
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
errors + @left.errors(type_de_champs) + @right.errors(type_de_champs)
|
errors + @left.errors(type_de_champs) + @right.errors(type_de_champs)
|
||||||
|
|
|
@ -60,7 +60,8 @@ class ProcedurePresentation < ApplicationRecord
|
||||||
fields.push(
|
fields.push(
|
||||||
field_hash('user', 'email', type: :text),
|
field_hash('user', 'email', type: :text),
|
||||||
field_hash('followers_instructeurs', 'email', type: :text),
|
field_hash('followers_instructeurs', 'email', type: :text),
|
||||||
field_hash('groupe_instructeur', 'id', type: :enum)
|
field_hash('groupe_instructeur', 'id', type: :enum),
|
||||||
|
field_hash('avis', 'id', type: :text)
|
||||||
)
|
)
|
||||||
|
|
||||||
if procedure.for_individual
|
if procedure.for_individual
|
||||||
|
@ -246,7 +247,7 @@ class ProcedurePresentation < ApplicationRecord
|
||||||
Dossier.human_attribute_name("state.#{filter['value']}")
|
Dossier.human_attribute_name("state.#{filter['value']}")
|
||||||
elsif filter['table'] == 'groupe_instructeur' && filter['column'] == 'id'
|
elsif filter['table'] == 'groupe_instructeur' && filter['column'] == 'id'
|
||||||
instructeur.groupe_instructeurs
|
instructeur.groupe_instructeurs
|
||||||
.find { _1.id == filter['value'].to_i }&.label || "Groupe Instucteur #{filter['value']}"
|
.find { _1.id == filter['value'].to_i }&.label || filter['value']
|
||||||
else
|
else
|
||||||
filter['value']
|
filter['value']
|
||||||
end
|
end
|
||||||
|
|
|
@ -92,6 +92,14 @@ class DossierProjectionService
|
||||||
.group_by { |dossier_id, _| dossier_id }
|
.group_by { |dossier_id, _| dossier_id }
|
||||||
.to_h { |dossier_id, dossier_id_emails| [dossier_id, dossier_id_emails.sort.map { |_, email| email }&.join(', ')] }
|
.to_h { |dossier_id, dossier_id_emails| [dossier_id, dossier_id_emails.sort.map { |_, email| email }&.join(', ')] }
|
||||||
# rubocop:enable Style/HashTransformValues
|
# rubocop:enable Style/HashTransformValues
|
||||||
|
when 'avis'
|
||||||
|
# rubocop:disable Style/HashTransformValues
|
||||||
|
fields[0][:id_value_h] = Avis
|
||||||
|
.where(dossier_id: dossiers_ids)
|
||||||
|
.pluck('dossier_id', 'question_answer')
|
||||||
|
.group_by { |dossier_id, _| dossier_id }
|
||||||
|
.to_h { |dossier_id, question_answer| [dossier_id, question_answer.map { |_, answer| answer }&.compact&.tally&.map { |k, v| I18n.t("helpers.label.question_answer_with_count.#{k}", count: v) }&.join(' / ')] }
|
||||||
|
# rubocop:enable Style/HashTransformValues
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
class GeoJSONValidator < ActiveModel::EachValidator
|
class GeoJSONValidator < ActiveModel::EachValidator
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
|
if options[:allow_nil] == false && value.nil?
|
||||||
|
record.errors.add(attribute, :blank, message: options[:message] || "ne peut pas être vide")
|
||||||
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
RGeo::GeoJSON.decode(value.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
|
RGeo::GeoJSON.decode(value.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
|
||||||
rescue RGeo::Error::InvalidGeometry
|
rescue RGeo::Error::InvalidGeometry
|
||||||
record.errors[attribute] << (options[:message] || "n'est pas un GeoJSON valide")
|
record.errors.add(attribute, :invalid_geometry, message: options[:message] || "n'est pas un GeoJSON valide")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,10 @@ def maybe_start_new_page(pdf, size)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def clean_string(str)
|
||||||
|
str.tr(' ', ' ') # replace non breaking space, which are invalid in pdf
|
||||||
|
end
|
||||||
|
|
||||||
def text_box(pdf, text, x, width)
|
def text_box(pdf, text, x, width)
|
||||||
box = ::Prawn::Text::Box.new(text.to_s,
|
box = ::Prawn::Text::Box.new(text.to_s,
|
||||||
document: pdf,
|
document: pdf,
|
||||||
|
@ -158,7 +162,7 @@ def add_single_champ(pdf, champ)
|
||||||
|
|
||||||
pdf.indent(default_margin) do
|
pdf.indent(default_margin) do
|
||||||
champ.geo_areas.each do |area|
|
champ.geo_areas.each do |area|
|
||||||
pdf.text "- #{area.label}".tr(' ', ' ') # replace non breaking space, which are invalid in pdf
|
pdf.text "- #{clean_string(area.label)}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -219,7 +223,7 @@ end
|
||||||
|
|
||||||
def add_etat_dossier(pdf, dossier)
|
def add_etat_dossier(pdf, dossier)
|
||||||
pdf.pad_bottom(default_margin) do
|
pdf.pad_bottom(default_margin) do
|
||||||
pdf.text "Ce dossier est <b>#{dossier_display_state(dossier, lower: true)}</b>.", inline_format: true
|
pdf.text "Ce dossier est <b>#{clean_string(dossier_display_state(dossier, lower: true))}</b>.", inline_format: true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -561,6 +561,10 @@ en:
|
||||||
attributes:
|
attributes:
|
||||||
email:
|
email:
|
||||||
taken: ': Invitation already sent'
|
taken: ': Invitation already sent'
|
||||||
|
email_champ:
|
||||||
|
attributes:
|
||||||
|
value:
|
||||||
|
invalid: "is invalid. Fill in a valid email address, example: john.doe@example.fr"
|
||||||
|
|
||||||
user:
|
user:
|
||||||
attributes: &error_attributes
|
attributes: &error_attributes
|
||||||
|
|
|
@ -562,6 +562,10 @@ fr:
|
||||||
attributes:
|
attributes:
|
||||||
email:
|
email:
|
||||||
taken: ': Invitation déjà envoyée'
|
taken: ': Invitation déjà envoyée'
|
||||||
|
email_champ:
|
||||||
|
attributes:
|
||||||
|
value:
|
||||||
|
invalid: "est invalide. Saisir une adresse éléctronique valide, exemple : john.doe@exemple.fr"
|
||||||
user:
|
user:
|
||||||
attributes: &error_attributes
|
attributes: &error_attributes
|
||||||
reset_password_token:
|
reset_password_token:
|
||||||
|
|
|
@ -21,6 +21,9 @@ en:
|
||||||
question_answer:
|
question_answer:
|
||||||
true: 'yes'
|
true: 'yes'
|
||||||
false: 'no'
|
false: 'no'
|
||||||
|
question_answer_with_count:
|
||||||
|
true: "yes : %{count}"
|
||||||
|
false: "no : %{count}"
|
||||||
confirmation:
|
confirmation:
|
||||||
revoke: "Would you like to revoke the opinion request to %{email} ?"
|
revoke: "Would you like to revoke the opinion request to %{email} ?"
|
||||||
remind: "Would you like to remind %{email} ?"
|
remind: "Would you like to remind %{email} ?"
|
||||||
|
|
|
@ -21,6 +21,9 @@ fr:
|
||||||
question_answer:
|
question_answer:
|
||||||
true: oui
|
true: oui
|
||||||
false: non
|
false: non
|
||||||
|
question_answer_with_count:
|
||||||
|
true: "oui : %{count}"
|
||||||
|
false: "non : %{count}"
|
||||||
confirmation:
|
confirmation:
|
||||||
revoke: "Souhaitez-vous révoquer la demande d’avis à %{email} ?"
|
revoke: "Souhaitez-vous révoquer la demande d’avis à %{email} ?"
|
||||||
remind: "Souhaitez-vous relancer %{email} ?"
|
remind: "Souhaitez-vous relancer %{email} ?"
|
||||||
|
|
|
@ -28,6 +28,8 @@ en:
|
||||||
prenom: First name
|
prenom: First name
|
||||||
nom: Last name
|
nom: Last name
|
||||||
gender: Title
|
gender: Title
|
||||||
|
avis:
|
||||||
|
id: Opinion
|
||||||
etablissement:
|
etablissement:
|
||||||
entreprise_siren: SIREN
|
entreprise_siren: SIREN
|
||||||
entreprise_forme_juridique: Forme juridique
|
entreprise_forme_juridique: Forme juridique
|
||||||
|
|
|
@ -28,6 +28,8 @@ fr:
|
||||||
prenom: Prénom
|
prenom: Prénom
|
||||||
nom: Nom
|
nom: Nom
|
||||||
gender: Civilité
|
gender: Civilité
|
||||||
|
avis:
|
||||||
|
id: Avis
|
||||||
etablissement:
|
etablissement:
|
||||||
entreprise_siren: SIREN
|
entreprise_siren: SIREN
|
||||||
entreprise_forme_juridique: Forme juridique
|
entreprise_forme_juridique: Forme juridique
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace :after_party do
|
||||||
|
desc 'Deployment task: fix_geo_area_without_geometry_again'
|
||||||
|
task fix_geo_area_without_geometry_again: :environment do
|
||||||
|
puts "Running deploy task 'fix_geo_area_without_geometry_again'"
|
||||||
|
|
||||||
|
Rake::Task['after_party:fix_geo_area_without_geometry'].invoke
|
||||||
|
|
||||||
|
# Update task as completed. If you remove the line below, the task will
|
||||||
|
# run with every deploy (or every time you call after_party:run).
|
||||||
|
AfterParty::TaskRecord
|
||||||
|
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,54 @@
|
||||||
|
namespace :after_party do
|
||||||
|
desc 'Deployment task: fix_include_in_logic'
|
||||||
|
task fix_include_in_logic: :environment do
|
||||||
|
include Logic
|
||||||
|
puts "Running deploy task 'fix_include_in_logic'"
|
||||||
|
|
||||||
|
tdcs_with_condition = TypeDeChamp.where.not(condition: nil)
|
||||||
|
|
||||||
|
progress = ProgressReport.new(tdcs_with_condition.count)
|
||||||
|
|
||||||
|
tdcs_with_condition.find_each do |tdc|
|
||||||
|
begin
|
||||||
|
tdc.revisions.each do |revision|
|
||||||
|
tdcs = revision.types_de_champ.where(stable_id: tdc.condition.sources)
|
||||||
|
|
||||||
|
transformed_condition = transform_eq_to_include(tdc.condition, tdcs)
|
||||||
|
|
||||||
|
if (transformed_condition != tdc.condition)
|
||||||
|
rake_puts "found #{tdc.id}, original: #{tdc.condition.to_s(tdcs)}, correction: #{transformed_condition.to_s(tdcs)}!"
|
||||||
|
new_tdc = revision.find_and_ensure_exclusive_use(tdc.stable_id)
|
||||||
|
new_tdc.update_columns(condition: transformed_condition)
|
||||||
|
Champ.joins(:dossier).where(dossier: { revision: revision }, type_de_champ: tdc).update(type_de_champ: new_tdc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue StandardError => e
|
||||||
|
rake_puts "problem with tdc #{tdc.id},\ncondition: #{tdc.read_attribute_before_type_cast('condition')},\nmessage: #{e.message}"
|
||||||
|
ensure
|
||||||
|
progress.inc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
progress.finish
|
||||||
|
|
||||||
|
# Update task as completed. If you remove the line below, the task will
|
||||||
|
# run with every deploy (or every time you call after_party:run).
|
||||||
|
AfterParty::TaskRecord
|
||||||
|
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
|
||||||
|
end
|
||||||
|
|
||||||
|
def transform_eq_to_include(condition, tdcs)
|
||||||
|
case condition
|
||||||
|
when Logic::NAryOperator
|
||||||
|
condition.class.new(condition.operands.map { transform_eq_to_include(_1, tdcs) })
|
||||||
|
when Eq
|
||||||
|
target = tdcs.find { _1.stable_id == condition.left.stable_id }
|
||||||
|
if target.type_champ == 'multiple_drop_down_list'
|
||||||
|
ds_include(condition.left, condition.right)
|
||||||
|
else
|
||||||
|
condition
|
||||||
|
end
|
||||||
|
else
|
||||||
|
condition
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -72,6 +72,22 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
it { expect(page).to have_content("« another choice » ne fait pas partie de « #{tdc.libelle} ».") }
|
it { expect(page).to have_content("« another choice » ne fait pas partie de « #{tdc.libelle} ».") }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when an eq operator applies to a multiple_drop_down' do
|
||||||
|
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
||||||
|
let(:upper_tdcs) { [tdc] }
|
||||||
|
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant(tdc.drop_down_list_enabled_non_empty_options.first))] }
|
||||||
|
|
||||||
|
it { expect(page).to have_content("« est » ne s'applique pas au choix multiple.") }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when an not_eq operator applies to a multiple_drop_down' do
|
||||||
|
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
||||||
|
let(:upper_tdcs) { [tdc] }
|
||||||
|
let(:conditions) { [ds_not_eq(champ_value(tdc.stable_id), constant(tdc.drop_down_list_enabled_non_empty_options.first))] }
|
||||||
|
|
||||||
|
it { expect(page).to have_content("« n’est pas » ne s'applique pas au choix multiple.") }
|
||||||
|
end
|
||||||
|
|
||||||
context 'when target became unavailable but a right still references the value' do
|
context 'when target became unavailable but a right still references the value' do
|
||||||
# Cf https://demarches-simplifiees.sentry.io/issues/3625488398/events/53164e105bc94d55a004d69f96d58fb2/?project=1429550
|
# Cf https://demarches-simplifiees.sentry.io/issues/3625488398/events/53164e105bc94d55a004d69f96d58fb2/?project=1429550
|
||||||
# However maybe we should not have empty at left with still a constant at right
|
# However maybe we should not have empty at left with still a constant at right
|
||||||
|
|
|
@ -9,12 +9,13 @@ describe Instructeurs::AvisController, type: :controller do
|
||||||
let(:instructeur) { create(:instructeur) }
|
let(:instructeur) { create(:instructeur) }
|
||||||
let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) }
|
let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) }
|
||||||
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||||
let!(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) }
|
|
||||||
let!(:avis_without_answer) { create(:avis, dossier: dossier, claimant: claimant, experts_procedure: experts_procedure) }
|
let!(:avis_without_answer) { create(:avis, dossier: dossier, claimant: claimant, experts_procedure: experts_procedure) }
|
||||||
|
|
||||||
before { sign_in(instructeur.user) }
|
before { sign_in(instructeur.user) }
|
||||||
|
|
||||||
describe "#revoker" do
|
describe "#revoker" do
|
||||||
|
let!(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
patch :revoquer, params: { procedure_id: procedure.id, id: avis.id }
|
patch :revoquer, params: { procedure_id: procedure.id, id: avis.id }
|
||||||
end
|
end
|
||||||
|
@ -28,12 +29,26 @@ describe Instructeurs::AvisController, type: :controller do
|
||||||
before do
|
before do
|
||||||
allow(AvisMailer).to receive(:avis_invitation).and_return(double(deliver_later: nil))
|
allow(AvisMailer).to receive(:avis_invitation).and_return(double(deliver_later: nil))
|
||||||
end
|
end
|
||||||
|
context 'without question' do
|
||||||
|
let!(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) }
|
||||||
|
|
||||||
it 'sends a reminder to the expert' do
|
it 'sends a reminder to the expert' do
|
||||||
get :remind, params: { procedure_id: procedure.id, id: avis.id }
|
get :remind, params: { procedure_id: procedure.id, id: avis.id }
|
||||||
expect(AvisMailer).to have_received(:avis_invitation).once.with(avis)
|
expect(AvisMailer).to have_received(:avis_invitation).once.with(avis)
|
||||||
expect(flash.notice).to eq("Un mail de relance a été envoyé à #{avis.expert.email}")
|
expect(flash.notice).to eq("Un mail de relance a été envoyé à #{avis.expert.email}")
|
||||||
expect(avis.reload.reminded_at).to be_present
|
expect(avis.reload.reminded_at).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with question' do
|
||||||
|
let!(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure, question_label: '123') }
|
||||||
|
|
||||||
|
it 'sends a reminder to the expert' do
|
||||||
|
get :remind, params: { procedure_id: procedure.id, id: avis.id }
|
||||||
|
expect(AvisMailer).to have_received(:avis_invitation).once.with(avis)
|
||||||
|
expect(flash.notice).to eq("Un mail de relance a été envoyé à #{avis.expert.email}")
|
||||||
|
expect(avis.reload.reminded_at).to be_present
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,7 @@ FactoryBot.define do
|
||||||
factory :geo_area do
|
factory :geo_area do
|
||||||
association :champ
|
association :champ
|
||||||
properties { {} }
|
properties { {} }
|
||||||
|
geometry { {} }
|
||||||
|
|
||||||
trait :cadastre do
|
trait :cadastre do
|
||||||
source { GeoArea.sources.fetch(:cadastre) }
|
source { GeoArea.sources.fetch(:cadastre) }
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
describe '20230424154715_fix_include_in_logic.rake' do
|
||||||
|
let(:rake_task) { Rake::Task['after_party:fix_include_in_logic'] }
|
||||||
|
|
||||||
|
include Logic
|
||||||
|
|
||||||
|
subject(:run_task) { rake_task.invoke }
|
||||||
|
after { rake_task.reenable }
|
||||||
|
|
||||||
|
context 'test condition correction' do
|
||||||
|
let(:procedure) do
|
||||||
|
types_de_champ_public = [
|
||||||
|
{ type: :multiple_drop_down_list },
|
||||||
|
{ type: :drop_down_list },
|
||||||
|
{ type: :integer_number },
|
||||||
|
{ type: :text }
|
||||||
|
]
|
||||||
|
create(:procedure, :published, types_de_champ_public:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def multiple_stable_id = procedure.reload.published_types_de_champ_public.first.stable_id
|
||||||
|
def simple_stable_id = procedure.reload.published_types_de_champ_public.second.stable_id
|
||||||
|
def integer_tdc = procedure.reload.published_types_de_champ_public.third
|
||||||
|
def text_tdc = procedure.reload.published_types_de_champ_public.last
|
||||||
|
|
||||||
|
before do
|
||||||
|
and_condition = ds_and([
|
||||||
|
# incorrect: should change ds_eq => ds_include
|
||||||
|
ds_eq(champ_value(multiple_stable_id), constant("a")),
|
||||||
|
# correct
|
||||||
|
ds_include(champ_value(multiple_stable_id), constant("b")),
|
||||||
|
# correct ds_eq because drop_down_list
|
||||||
|
ds_eq(champ_value(simple_stable_id), constant("c"))
|
||||||
|
])
|
||||||
|
|
||||||
|
text_tdc.update(condition: and_condition)
|
||||||
|
|
||||||
|
or_condition = ds_or([
|
||||||
|
# incorrect: should change ds_eq => ds_include
|
||||||
|
ds_eq(champ_value(multiple_stable_id), constant("a"))
|
||||||
|
])
|
||||||
|
|
||||||
|
integer_tdc.update(condition: or_condition)
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
run_task
|
||||||
|
expected_and_condition = ds_and([
|
||||||
|
ds_include(champ_value(multiple_stable_id), constant("a")),
|
||||||
|
ds_include(champ_value(multiple_stable_id), constant("b")),
|
||||||
|
ds_eq(champ_value(simple_stable_id), constant("c"))
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(text_tdc.condition).to eq(expected_and_condition)
|
||||||
|
|
||||||
|
expected_or_condition = ds_or([
|
||||||
|
ds_include(champ_value(multiple_stable_id), constant("a"))
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(integer_tdc.condition).to eq(expected_or_condition)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'test revision scope' do
|
||||||
|
let(:procedure) do
|
||||||
|
types_de_champ_public = [
|
||||||
|
{ type: :drop_down_list, options: [:a, :b, :c] },
|
||||||
|
{ type: :text }
|
||||||
|
]
|
||||||
|
create(:procedure, types_de_champ_public:)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:initial_condition) { ds_eq(champ_value(drop_down_stable_id), constant('a')) }
|
||||||
|
|
||||||
|
def drop_down_stable_id = procedure.reload.draft_types_de_champ_public.first.stable_id
|
||||||
|
def draft_text = procedure.reload.draft_types_de_champ_public.last
|
||||||
|
def published_text = procedure.reload.published_types_de_champ_public.last
|
||||||
|
|
||||||
|
before do
|
||||||
|
draft_text.update(condition: initial_condition)
|
||||||
|
|
||||||
|
procedure.publish!
|
||||||
|
procedure.reload
|
||||||
|
|
||||||
|
draft_drop_down = procedure.draft_revision.find_and_ensure_exclusive_use(drop_down_stable_id)
|
||||||
|
draft_drop_down.update(type_champ: 'multiple_drop_down_list')
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(draft_text.condition).to eq(initial_condition)
|
||||||
|
expect(published_text.condition).to eq(initial_condition)
|
||||||
|
|
||||||
|
run_task
|
||||||
|
|
||||||
|
# the text condition is invalid for the draft revision
|
||||||
|
expect(draft_text.condition).to eq(ds_include(champ_value(drop_down_stable_id), constant('a')))
|
||||||
|
# the published_text condition is untouched as it s still valid
|
||||||
|
expect(published_text.condition).to eq(initial_condition)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'test champ change' do
|
||||||
|
let!(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :multiple_drop_down_list, options: ['a'] }, { type: :text }]) }
|
||||||
|
let!(:dossier) { create(:dossier, procedure:) }
|
||||||
|
|
||||||
|
def multiple_stable_id = procedure.reload.published_types_de_champ_public.first.stable_id
|
||||||
|
def text_tdc = procedure.reload.published_types_de_champ_public.last
|
||||||
|
|
||||||
|
let(:initial_condition) { ds_eq(champ_value(multiple_stable_id), constant('a')) }
|
||||||
|
let(:fixed_condition) { ds_include(champ_value(multiple_stable_id), constant('a')) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
text_tdc.update(condition: initial_condition)
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(dossier.reload.champs_public.last.type_de_champ.condition).to eq(initial_condition)
|
||||||
|
|
||||||
|
run_task
|
||||||
|
|
||||||
|
expect(dossier.reload.champs_public.last.type_de_champ.condition).to eq(fixed_condition)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
29
spec/models/champs/email_champ_spec.rb
Normal file
29
spec/models/champs/email_champ_spec.rb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
describe Champs::EmailChamp do
|
||||||
|
subject { build(:champ_email, value: value).tap(&:valid?) }
|
||||||
|
|
||||||
|
describe '#valid?' do
|
||||||
|
context 'when the value is an email' do
|
||||||
|
let(:value) { 'jean@dupont.fr' }
|
||||||
|
|
||||||
|
it { is_expected.to be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the value is not an email' do
|
||||||
|
let(:value) { 'jean@' }
|
||||||
|
|
||||||
|
it { is_expected.to_not be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the value is blank' do
|
||||||
|
let(:value) { '' }
|
||||||
|
|
||||||
|
it { is_expected.to_not be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the value is nil' do
|
||||||
|
let(:value) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to be_valid }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -81,6 +81,16 @@ RSpec.describe GeoArea, type: :model do
|
||||||
let(:geo_area) { build(:geo_area, :invalid_right_hand_rule_polygon, champ: nil) }
|
let(:geo_area) { build(:geo_area, :invalid_right_hand_rule_polygon, champ: nil) }
|
||||||
it { expect(geo_area.errors).to have_key(:geometry) }
|
it { expect(geo_area.errors).to have_key(:geometry) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "nil" do
|
||||||
|
let(:geo_area) { build(:geo_area, geometry: nil) }
|
||||||
|
it { expect(geo_area.errors).to have_key(:geometry) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "allow empty {}" do
|
||||||
|
let(:geo_area) { build(:geo_area, geometry: {}) }
|
||||||
|
it { expect(geo_area.errors).not_to have_key(:geometry) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,19 @@ describe Logic::Eq do
|
||||||
}
|
}
|
||||||
expect(ds_eq(constant(true), constant(1)).errors).to eq([expected])
|
expect(ds_eq(constant(true), constant(1)).errors).to eq([expected])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
multiple_drop_down = create(:type_de_champ_multiple_drop_down_list)
|
||||||
|
first_option = multiple_drop_down.drop_down_list_enabled_non_empty_options.first
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
operator_name: "Logic::Eq",
|
||||||
|
stable_id: multiple_drop_down.stable_id,
|
||||||
|
type: :required_include
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(ds_eq(champ_value(multiple_drop_down.stable_id), constant(first_option)).errors([multiple_drop_down])).to eq([expected])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#==' do
|
describe '#==' do
|
||||||
|
|
|
@ -17,6 +17,19 @@ describe Logic::NotEq do
|
||||||
}
|
}
|
||||||
expect(ds_not_eq(constant(true), constant(1)).errors).to eq([expected])
|
expect(ds_not_eq(constant(true), constant(1)).errors).to eq([expected])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
multiple_drop_down = create(:type_de_champ_multiple_drop_down_list)
|
||||||
|
first_option = multiple_drop_down.drop_down_list_enabled_non_empty_options.first
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
operator_name: "Logic::NotEq",
|
||||||
|
stable_id: multiple_drop_down.stable_id,
|
||||||
|
type: :required_include
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(ds_not_eq(champ_value(multiple_drop_down.stable_id), constant(first_option)).errors([multiple_drop_down])).to eq([expected])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#==' do
|
describe '#==' do
|
||||||
|
|
|
@ -73,6 +73,7 @@ describe ProcedurePresentation do
|
||||||
{ "label" => 'Demandeur', "table" => 'user', "column" => 'email', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
{ "label" => 'Demandeur', "table" => 'user', "column" => 'email', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
||||||
{ "label" => 'Email instructeur', "table" => 'followers_instructeurs', "column" => 'email', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
{ "label" => 'Email instructeur', "table" => 'followers_instructeurs', "column" => 'email', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
||||||
{ "label" => 'Groupe instructeur', "table" => 'groupe_instructeur', "column" => 'id', 'classname' => '', 'virtual' => false, 'type' => :enum, "scope" => '' },
|
{ "label" => 'Groupe instructeur', "table" => 'groupe_instructeur', "column" => 'id', 'classname' => '', 'virtual' => false, 'type' => :enum, "scope" => '' },
|
||||||
|
{ "label" => 'Avis', "table" => 'avis', "column" => 'id', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
||||||
{ "label" => 'SIREN', "table" => 'etablissement', "column" => 'entreprise_siren', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
{ "label" => 'SIREN', "table" => 'etablissement', "column" => 'entreprise_siren', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
||||||
{ "label" => 'Forme juridique', "table" => 'etablissement', "column" => 'entreprise_forme_juridique', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
{ "label" => 'Forme juridique', "table" => 'etablissement', "column" => 'entreprise_forme_juridique', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
||||||
{ "label" => 'Nom commercial', "table" => 'etablissement', "column" => 'entreprise_nom_commercial', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
{ "label" => 'Nom commercial', "table" => 'etablissement', "column" => 'entreprise_nom_commercial', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '' },
|
||||||
|
|
Loading…
Reference in a new issue