Merge pull request #10370 from tchak/refactor-format-champ-move-to-type_de_champ

refactor(export): move formatting logic to type de champ
This commit is contained in:
Paul Chavard 2024-04-24 11:54:42 +00:00 committed by GitHub
commit fe3dc57fc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 886 additions and 656 deletions

View file

@ -109,24 +109,29 @@ class Champ < ApplicationRecord
[to_s]
end
def to_s
value.present? ? value.to_s : ''
end
def for_export(path = :value)
path == :value ? value.presence : nil
end
def for_api
def valid_value
return unless valid_champ_value?
value
end
def to_s
TypeDeChamp.champ_value(type_champ, self)
end
def for_api
TypeDeChamp.champ_value_for_api(type_champ, self, 1)
end
def for_api_v2
to_s
TypeDeChamp.champ_value_for_api(type_champ, self, 2)
end
def for_export(path = :value)
TypeDeChamp.champ_value_for_export(type_champ, self, path)
end
def for_tag(path = :value)
path == :value && value.present? ? value.to_s : ''
TypeDeChamp.champ_value_for_tag(type_champ, self, path)
end
def main_value_name

View file

@ -38,36 +38,6 @@ class Champs::AddressChamp < Champs::TextChamp
end
end
def to_s
address_label.presence || ''
end
def for_tag(path = :value)
case path
when :value
address_label
when :departement
departement_code_and_name || ''
when :commune
commune_name || ''
end
end
def for_export(path = :value)
case path
when :value
value.present? ? address_label : nil
when :departement
departement_code_and_name
when :commune
commune_name
end
end
def for_api
address_label
end
def code_departement
if full_address?
address.fetch('department_code')

View file

@ -17,28 +17,8 @@ class Champs::BooleanChamp < Champ
end
end
def to_s
processed_value
end
def for_tag(path = :value)
processed_value
end
def for_export(path = :value)
processed_value
end
def for_api_v2
true? ? 'true' : 'false'
end
private
def processed_value
true? ? 'Oui' : 'Non'
end
def set_value_to_nil
self.value = nil
end

View file

@ -83,14 +83,6 @@ class Champs::CarteChamp < Champ
end
end
def for_api
nil
end
def for_export(path = :value)
geo_areas.map(&:label).join("\n")
end
def blank?
geo_areas.blank?
end

View file

@ -1,8 +1,4 @@
class Champs::CheckboxChamp < Champs::BooleanChamp
def for_export(path = :value)
true? ? 'on' : 'off'
end
def mandatory_blank?
mandatory? && (blank? || !true?)
end

View file

@ -34,10 +34,6 @@ class Champs::COJOChamp < Champ
COJOService.new.(accreditation_number:, accreditation_birthdate:)
end
def to_s
"#{accreditation_number} #{accreditation_birthdate}"
end
def accreditation_number_input_id
"#{input_id}-accreditation_number"
end

View file

@ -2,28 +2,6 @@ class Champs::CommuneChamp < Champs::TextChamp
store_accessor :value_json, :code_departement, :code_postal, :code_region
before_save :on_codes_change, if: :should_refresh_after_code_change?
def for_export(path = :value)
case path
when :value
to_s
when :departement
departement_code_and_name || ''
when :code
code || ''
end
end
def for_tag(path = :value)
case path
when :value
to_s
when :departement
departement_code_and_name || ''
when :code
code || ''
end
end
def departement_name
APIGeoService.departement_name(code_departement)
end
@ -60,10 +38,6 @@ class Champs::CommuneChamp < Champs::TextChamp
APIGeoService.safely_normalize_city_name(code_departement, code, safe_to_s)
end
def to_s
code_postal? ? "#{name} (#{code_postal})" : name
end
def code
external_id
end

View file

@ -6,16 +6,6 @@ class Champs::DateChamp < Champ
# Text search is pretty useless for dates so were not including these champs
end
def to_s
value.present? ? I18n.l(Time.zone.parse(value), format: '%d %B %Y') : ""
rescue ArgumentError
value.presence || "" # old dossiers can have not parseable dates
end
def for_tag(path = :value)
to_s if path == :value
end
private
def convert_to_iso8601

View file

@ -6,14 +6,6 @@ class Champs::DatetimeChamp < Champ
# Text search is pretty useless for datetimes so were not including these champs
end
def to_s
value.present? ? I18n.l(Time.zone.parse(value)) : ""
end
def for_tag(path = :value)
value.present? ? I18n.l(Time.zone.parse(value)) : ""
end
private
def convert_to_iso8601

View file

@ -17,14 +17,6 @@ class Champs::DecimalNumberChamp < Champ
}
}, if: :validate_champ_value_or_prefill?
def for_export(path = :value)
processed_value
end
def for_api
processed_value
end
private
def format_value
@ -32,10 +24,4 @@ class Champs::DecimalNumberChamp < Champ
self.value = value.tr(",", ".")
end
def processed_value
return unless valid_champ_value?
value&.to_f
end
end

View file

@ -5,36 +5,6 @@ class Champs::DepartementChamp < Champs::TextChamp
validate :external_id_in_departement_codes, if: -> { validate_champ_value_or_prefill? && !external_id.nil? }
before_save :store_code_region
def for_export(path = :value)
case path
when :code
code
when :value
name
end
end
def to_s
formatted_value
end
def for_tag(path = :value)
case path
when :code
code
when :value
formatted_value
end
end
def for_api
formatted_value
end
def for_api_v2
formatted_value.tr('', '-')
end
def selected
code
end
@ -71,10 +41,6 @@ class Champs::DepartementChamp < Champs::TextChamp
private
def formatted_value
blank? ? "" : "#{code} #{name}"
end
def value_in_departement_names
return if value.in?(APIGeoService.departements.pluck(:name))

View file

@ -7,28 +7,6 @@ class Champs::EpciChamp < Champs::TextChamp
validate :external_id_in_departement_epci_codes, if: -> { !(code_departement.nil? || external_id.nil?) && validate_champ_value_or_prefill? }
validate :value_in_departement_epci_names, if: -> { !(code_departement.nil? || external_id.nil? || value.nil?) && validate_champ_value_or_prefill? }
def for_export(path = :value)
case path
when :value
value
when :code
code
when :departement
departement_code_and_name
end
end
def for_tag(path = :value)
case path
when :value
value
when :code
code
when :departement
departement_code_and_name
end
end
def departement_name
APIGeoService.departement_name(code_departement)
end

View file

@ -8,20 +8,4 @@ class Champs::IntegerNumberChamp < Champ
object.errors.generate_message(:value, :not_an_integer)
}
}, if: :validate_champ_value_or_prefill?
def for_export(path = :value)
processed_value
end
def for_api
processed_value
end
private
def processed_value
return unless valid_champ_value?
value&.to_i
end
end

View file

@ -37,36 +37,6 @@ class Champs::LinkedDropDownListChamp < Champ
:primary_value
end
def to_s
value.present? ? [primary_value, secondary_value].filter(&:present?).join(' / ') : ""
end
def for_tag(path = :value)
case path
when :primary
primary_value
when :secondary
secondary_value
when :value
value.present? ? [primary_value, secondary_value].filter(&:present?).join(' / ') : ""
end
end
def for_export(path = :value)
case path
when :primary
primary_value
when :secondary
secondary_value
when :value
value.present? ? "#{primary_value || ''};#{secondary_value || ''}" : nil
end
end
def for_api
value.present? ? { primary: primary_value, secondary: secondary_value } : nil
end
def blank?
primary_value.blank? ||
(has_secondary_options_for_primary? && secondary_value.blank?)

View file

@ -19,18 +19,6 @@ class Champs::MultipleDropDownListChamp < Champ
value.blank? ? [] : JSON.parse(value)
end
def to_s
selected_options.join(', ')
end
def for_tag(path = :value)
selected_options.join(', ')
end
def for_export(path = :value)
value.present? ? selected_options.join(', ') : nil
end
def render_as_checkboxes?
enabled_non_empty_options.size <= THRESHOLD_NB_OPTIONS_AS_CHECKBOX
end

View file

@ -11,28 +11,6 @@ class Champs::PaysChamp < Champs::TextChamp
validates :value, inclusion: APIGeoService.countries.pluck(:name), allow_nil: false, allow_blank: false
end
def for_export(path = :value)
case path
when :code
code
when :value
name
end
end
def to_s
name
end
def for_tag(path = :value)
case path
when :code
code
when :value
name
end
end
def selected
code || value
end
@ -55,6 +33,10 @@ class Champs::PaysChamp < Champs::TextChamp
end
end
def blank?
value.blank? && external_id.blank?
end
def code
external_id || APIGeoService.country_code(value)
end

View file

@ -1,41 +1,9 @@
class Champs::PhoneChamp < Champs::TextChamp
# We want to allow:
# * international (e164) phone numbers
# * “french format” (ten digits with a leading 0)
# * DROM numbers
#
# However, we need to special-case some ten-digit numbers,
# because the ARCEP assigns some blocks of "O6 XX XX XX XX" numbers to DROM operators.
# Guadeloupe | GP | +590 | 0690XXXXXX, 0691XXXXXX
# Guyane | GF | +594 | 0694XXXXXX
# Martinique | MQ | +596 | 0696XXXXXX, 0697XXXXXX
# Réunion | RE | +262 | 0692XXXXXX, 0693XXXXXX
# Mayotte | YT | +262 | 0692XXXXXX, 0693XXXXXX
# Nouvelle-Calédonie | NC | +687 |
# Polynésie française | PF | +689 | 40XXXXXX, 45XXXXXX, 87XXXXXX, 88XXXXXX, 89XXXXXX
#
# Cf: Plan national de numérotation téléphonique,
# https://www.arcep.fr/uploads/tx_gsavis/05-1085.pdf “Numéros mobiles à 10 chiffres”, page 6
#
# See issue #6996.
DEFAULT_COUNTRY_CODES = [:FR, :GP, :GF, :MQ, :RE, :YT, :NC, :PF].freeze
validates :value,
phone: {
possible: true,
allow_blank: true,
message: I18n.t(:not_a_phone, scope: 'activerecord.errors.messages')
},
if: -> { !Phonelib.valid_for_countries?(value, DEFAULT_COUNTRY_CODES) && validate_champ_value_or_prefill? }
def to_s
return '' if value.blank?
if Phonelib.valid_for_countries?(value, DEFAULT_COUNTRY_CODES)
Phonelib.parse_for_countries(value, DEFAULT_COUNTRY_CODES).full_national
else
# When he phone number is possible for the default countries, but not strictly valid,
# `full_national` could mess up the formatting. In this case just return the original.
value
end
end
if: -> { !Phonelib.valid_for_countries?(value, TypesDeChamp::PhoneTypeDeChamp::DEFAULT_COUNTRY_CODES) && validate_champ_value_or_prefill? }
end

View file

@ -25,20 +25,4 @@ class Champs::PieceJustificativeChamp < Champ
def blank?
piece_justificative_file.blank?
end
def for_export(path = :value)
piece_justificative_file.map { _1.filename.to_s }.join(', ')
end
def for_api
return nil unless piece_justificative_file.attached?
# API v1 don't support multiple PJ
attachment = piece_justificative_file.first
return nil if attachment.nil?
if attachment.virus_scanner.safe? || attachment.virus_scanner.pending?
attachment.url
end
end
end

View file

@ -2,24 +2,6 @@ class Champs::RegionChamp < Champs::TextChamp
validate :value_in_region_names, if: -> { !value.nil? && validate_champ_value_or_prefill? }
validate :external_id_in_region_codes, if: -> { !external_id.nil? && validate_champ_value_or_prefill? }
def for_export(path = :value)
case path
when :value
name
when :code
code
end
end
def for_tag(path = :value)
case path
when :value
name
when :code
code
end
end
def selected
code
end

View file

@ -15,10 +15,6 @@ class Champs::RNAChamp < Champ
title.present? ? "#{value} (#{title})" : value
end
def for_export(path = :value)
identifier
end
def search_terms
etablissement.present? ? etablissement.search_terms : [value]
end

View file

@ -25,36 +25,6 @@ class Champs::RNFChamp < Champ
rnf_id.blank?
end
def for_export(path = :value)
case path
when :value
rnf_id
when :departement
departement_code_and_name
when :code_insee
commune&.fetch(:code)
when :address
full_address
when :nom
title
end
end
def for_tag(path = :value)
case path
when :value
rnf_id
when :departement
departement_code_and_name || ''
when :code_insee
commune&.fetch(:code) || ''
when :address
full_address || ''
when :nom
title || ''
end
end
def code_departement
address.present? && address['departmentCode']
end

View file

@ -1,8 +1,4 @@
class Champs::TextareaChamp < Champs::TextChamp
def for_export(path = :value)
value.present? ? ActionView::Base.full_sanitizer.sanitize(value) : nil
end
def remaining_characters
character_limit_base - character_count if character_count >= character_limit_threshold_75
end

View file

@ -19,12 +19,4 @@ class Champs::TitreIdentiteChamp < Champ
def blank?
piece_justificative_file.blank?
end
def for_export(path = :value)
piece_justificative_file.attached? ? "présent" : "absent"
end
def for_api
nil
end
end

View file

@ -25,7 +25,7 @@ module DossierChampsConcern
types_de_champ.flat_map do |type_de_champ|
champ = champ_for_export(type_de_champ, row_id)
type_de_champ.libelles_for_export.map do |(libelle, path)|
[libelle, champ&.for_export(path)]
[libelle, TypeDeChamp.champ_value_for_export(type_de_champ.type_champ, champ, path)]
end
end
end

View file

@ -250,7 +250,7 @@ class TypeDeChamp < ApplicationRecord
def params_for_champ
{
private: private?,
type: "Champs::#{type_champ.classify}Champ",
type: self.class.type_champ_to_champ_class_name(type_champ),
stable_id:,
stream: 'main'
}
@ -463,10 +463,6 @@ class TypeDeChamp < ApplicationRecord
!private?
end
def self.type_champ_to_class_name(type_champ)
"TypesDeChamp::#{type_champ.classify}TypeDeChamp"
end
def filename_for_attachement(attachment_sym)
attachment = send(attachment_sym)
if attachment.attached?
@ -687,6 +683,56 @@ class TypeDeChamp < ApplicationRecord
end
end
class << self
def champ_value(type_champ, champ)
dynamic_type_class = type_champ_to_class_name(type_champ).constantize
# special case for linked drop down champ it's blank implementation is not what you think
if champ.blank? && (type_champ != TypeDeChamp.type_champs.fetch(:linked_drop_down_list) || champ.nil? || champ.value.blank?)
dynamic_type_class.champ_default_value
else
dynamic_type_class.champ_value(champ)
end
end
def champ_value_for_api(type_champ, champ, version = 2)
dynamic_type_class = type_champ_to_class_name(type_champ).constantize
# special case for linked drop down champ it's blank implementation is not what you think
if champ.blank? && (type_champ != TypeDeChamp.type_champs.fetch(:linked_drop_down_list) || champ.nil? || champ.value.blank?)
dynamic_type_class.champ_default_api_value(version)
else
dynamic_type_class.champ_value_for_api(champ, version)
end
end
def champ_value_for_export(type_champ, champ, path = :value)
dynamic_type_class = type_champ_to_class_name(type_champ).constantize
# special case for linked drop down champ it's blank implementation is not what you think
if champ.blank? && (type_champ != TypeDeChamp.type_champs.fetch(:linked_drop_down_list) || champ.nil? || champ.value.blank?)
dynamic_type_class.champ_default_export_value(path)
else
dynamic_type_class.champ_value_for_export(champ, path)
end
end
def champ_value_for_tag(type_champ, champ, path = :value)
dynamic_type_class = type_champ_to_class_name(type_champ).constantize
# special case for linked drop down champ it's blank implementation is not what you think
if champ.blank? && (type_champ != TypeDeChamp.type_champs.fetch(:linked_drop_down_list) || champ.nil? || champ.value.blank?)
''
else
dynamic_type_class.champ_value_for_tag(champ, path)
end
end
def type_champ_to_champ_class_name(type_champ)
"Champs::#{type_champ.classify}Champ"
end
def type_champ_to_class_name(type_champ)
"TypesDeChamp::#{type_champ.classify}TypeDeChamp"
end
end
private
DEFAULT_EMPTY = ['']

View file

@ -4,6 +4,38 @@ class TypesDeChamp::AddressTypeDeChamp < TypesDeChamp::TextTypeDeChamp
[[path[:libelle], path[:path]]]
end
class << self
def champ_value(champ)
champ.address_label.presence || ''
end
def champ_value_for_api(champ, version = 2)
champ_value(champ)
end
def champ_value_for_tag(champ, path = :value)
case path
when :value
champ_value(champ)
when :departement
champ.departement_code_and_name || ''
when :commune
champ.commune_name || ''
end
end
def champ_value_for_export(champ, path = :value)
case path
when :value
champ_value(champ)
when :departement
champ.departement_code_and_name
when :commune
champ.commune_name
end
end
end
private
def paths

View file

@ -17,4 +17,14 @@ class TypesDeChamp::CarteTypeDeChamp < TypesDeChamp::TypeDeChampBase
end
def tags_for_template = [].freeze
class << self
def champ_value_for_api(champ, version = 2)
nil
end
def champ_value_for_export(champ, path = :value)
champ.geo_areas.map(&:label).join("\n")
end
end
end

View file

@ -8,4 +8,44 @@ class TypesDeChamp::CheckboxTypeDeChamp < TypesDeChamp::TypeDeChampBase
filter_value
end
end
class << self
def champ_value(champ)
champ.true? ? 'Oui' : 'Non'
end
def champ_value_for_tag(champ, path = :value)
champ_value(champ)
end
def champ_value_for_export(champ, path = :value)
champ.true? ? 'on' : 'off'
end
def champ_value_for_api(champ, version = 2)
case version
when 2
champ.true? ? 'true' : 'false'
else
super
end
end
def champ_default_value
'Non'
end
def champ_default_export_value(path = :value)
'off'
end
def champ_default_api_value(version = 2)
case version
when 2
'false'
else
nil
end
end
end
end

View file

@ -1,2 +1,7 @@
class TypesDeChamp::COJOTypeDeChamp < TypesDeChamp::TextTypeDeChamp
class << self
def champ_value(champ)
"#{champ.accreditation_number} #{champ.accreditation_birthdate}"
end
end
end

View file

@ -1,4 +1,32 @@
class TypesDeChamp::CommuneTypeDeChamp < TypesDeChamp::TypeDeChampBase
class << self
def champ_value_for_export(champ, path = :value)
case path
when :value
champ_value(champ)
when :departement
champ.departement_code_and_name || ''
when :code
champ.code || ''
end
end
def champ_value_for_tag(champ, path = :value)
case path
when :value
champ_value(champ)
when :departement
champ.departement_code_and_name || ''
when :code
champ.code || ''
end
end
def champ_value(champ)
champ.code_postal? ? "#{champ.name} (#{champ.code_postal})" : champ.name
end
end
private
def paths

View file

@ -1,2 +1,9 @@
class TypesDeChamp::DateTypeDeChamp < TypesDeChamp::TypeDeChampBase
class << self
def champ_value(champ)
I18n.l(Time.zone.parse(champ.value), format: '%d %B %Y')
rescue ArgumentError
champ.value.presence || "" # old dossiers can have not parseable dates
end
end
end

View file

@ -1,2 +1,7 @@
class TypesDeChamp::DatetimeTypeDeChamp < TypesDeChamp::TypeDeChampBase
class << self
def champ_value(champ)
I18n.l(Time.zone.parse(champ.value))
end
end
end

View file

@ -1,2 +1,26 @@
class TypesDeChamp::DecimalNumberTypeDeChamp < TypesDeChamp::TypeDeChampBase
class << self
def champ_value_for_export(champ, path = :value)
champ_formatted_value(champ)
end
def champ_value_for_api(champ, version = 2)
case version
when 1
champ_formatted_value(champ)
else
super
end
end
def champ_default_export_value(path = :value)
0
end
private
def champ_formatted_value(champ)
champ.valid_value&.to_f
end
end
end

View file

@ -3,6 +3,39 @@ class TypesDeChamp::DepartementTypeDeChamp < TypesDeChamp::TextTypeDeChamp
APIGeoService.departement_name(filter_value).presence || filter_value
end
class << self
def champ_value(champ)
"#{champ.code} #{champ.name}"
end
def champ_value_for_export(champ, path = :value)
case path
when :code
champ.code
when :value
champ.name
end
end
def champ_value_for_tag(path = :value)
case path
when :code
champ.code
when :value
champ_value(champ)
end
end
def champ_value_for_api(champ, version = 2)
case version
when 2
champ_value(champ).tr('', '-')
else
champ_value(champ)
end
end
end
private
def paths

View file

@ -1,4 +1,28 @@
class TypesDeChamp::EpciTypeDeChamp < TypesDeChamp::TextTypeDeChamp
class << self
def champ_value_for_export(champ, path = :value)
case path
when :value
champ_value(champ)
when :code
champ.code
when :departement
champ.departement_code_and_name
end
end
def champ_value_for_tag(champ, path = :value)
case path
when :value
champ_value(champ)
when :code
champ.code
when :departement
champ.departement_code_and_name
end
end
end
private
def paths

View file

@ -1,2 +1,26 @@
class TypesDeChamp::IntegerNumberTypeDeChamp < TypesDeChamp::TypeDeChampBase
class << self
def champ_value_for_export(champ, path = :value)
champ_formatted_value(champ)
end
def champ_value_for_api(champ, version = 2)
case version
when 1
champ_formatted_value(champ)
else
super
end
end
def champ_default_export_value(path = :value)
0
end
private
def champ_formatted_value(champ)
champ.valid_value&.to_i
end
end
end

View file

@ -30,6 +30,43 @@ class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBas
secondary_options
end
class << self
def champ_value(champ)
[champ.primary_value, champ.secondary_value].filter(&:present?).join(' / ')
end
def champ_value_for_tag(champ, path = :value)
case path
when :primary
champ.primary_value
when :secondary
champ.secondary_value
when :value
champ_value(champ)
end
end
def champ_value_for_export(champ, path = :value)
case path
when :primary
champ.primary_value
when :secondary
champ.secondary_value
when :value
"#{champ.primary_value || ''};#{champ.secondary_value || ''}"
end
end
def champ_value_for_api(champ, version = 2)
case version
when 1
{ primary: champ.primary_value, secondary: champ.secondary_value }
else
super
end
end
end
private
def paths

View file

@ -1,2 +1,15 @@
class TypesDeChamp::MultipleDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBase
class << self
def champ_value(champ)
champ.selected_options.join(', ')
end
def champ_value_for_tag(champ, path = :value)
champ.selected_options.join(', ')
end
def champ_value_for_export(champ, path = :value)
champ.selected_options.join(', ')
end
end
end

View file

@ -1,4 +1,28 @@
class TypesDeChamp::PaysTypeDeChamp < TypesDeChamp::TextTypeDeChamp
class << self
def champ_value(champ)
champ.name
end
def champ_value_for_export(champ, path = :value)
case path
when :value
champ_value(champ)
when :code
champ.code
end
end
def champ_value_for_tag(champ, path = :value)
case path
when :value
champ_value(champ)
when :code
champ.code
end
end
end
private
def paths

View file

@ -1,2 +1,34 @@
class TypesDeChamp::PhoneTypeDeChamp < TypesDeChamp::TextTypeDeChamp
# We want to allow:
# * international (e164) phone numbers
# * “french format” (ten digits with a leading 0)
# * DROM numbers
#
# However, we need to special-case some ten-digit numbers,
# because the ARCEP assigns some blocks of "O6 XX XX XX XX" numbers to DROM operators.
# Guadeloupe | GP | +590 | 0690XXXXXX, 0691XXXXXX
# Guyane | GF | +594 | 0694XXXXXX
# Martinique | MQ | +596 | 0696XXXXXX, 0697XXXXXX
# Réunion | RE | +262 | 0692XXXXXX, 0693XXXXXX
# Mayotte | YT | +262 | 0692XXXXXX, 0693XXXXXX
# Nouvelle-Calédonie | NC | +687 |
# Polynésie française | PF | +689 | 40XXXXXX, 45XXXXXX, 87XXXXXX, 88XXXXXX, 89XXXXXX
#
# Cf: Plan national de numérotation téléphonique,
# https://www.arcep.fr/uploads/tx_gsavis/05-1085.pdf “Numéros mobiles à 10 chiffres”, page 6
#
# See issue #6996.
DEFAULT_COUNTRY_CODES = [:FR, :GP, :GF, :MQ, :RE, :YT, :NC, :PF].freeze
class << self
def champ_value(champ)
if Phonelib.valid_for_countries?(champ.value, DEFAULT_COUNTRY_CODES)
Phonelib.parse_for_countries(champ.value, DEFAULT_COUNTRY_CODES).full_national
else
# When he phone number is possible for the default countries, but not strictly valid,
# `full_national` could mess up the formatting. In this case just return the original.
champ.value
end
end
end
end

View file

@ -4,4 +4,22 @@ class TypesDeChamp::PieceJustificativeTypeDeChamp < TypesDeChamp::TypeDeChampBas
end
def tags_for_template = [].freeze
class << self
def champ_value_for_export(champ, path = :value)
champ.piece_justificative_file.map { _1.filename.to_s }.join(', ')
end
def champ_value_for_api(champ, version = 2)
return if version == 2
# API v1 don't support multiple PJ
attachment = champ.piece_justificative_file.first
return if attachment.nil?
if attachment.virus_scanner.safe? || attachment.virus_scanner.pending?
attachment.url
end
end
end
end

View file

@ -3,6 +3,30 @@ class TypesDeChamp::RegionTypeDeChamp < TypesDeChamp::TextTypeDeChamp
APIGeoService.region_name(filter_value).presence || filter_value
end
class << self
def champ_value(champ)
champ.name
end
def champ_value_for_export(champ, path = :value)
case path
when :value
champ_value(champ)
when :code
champ.code
end
end
def champ_value_for_tag(champ, path = :value)
case path
when :value
champ_value(champ)
when :code
champ.code
end
end
end
private
def paths

View file

@ -2,4 +2,10 @@ class TypesDeChamp::RNATypeDeChamp < TypesDeChamp::TypeDeChampBase
def estimated_fill_duration(revision)
FILL_DURATION_MEDIUM
end
class << self
def champ_value_for_export(champ, path = :value)
champ.identifier
end
end
end

View file

@ -1,4 +1,36 @@
class TypesDeChamp::RNFTypeDeChamp < TypesDeChamp::TextTypeDeChamp
class << self
def champ_value_for_export(champ, path = :value)
case path
when :value
champ.rnf_id
when :departement
champ.departement_code_and_name
when :code_insee
champ.commune&.fetch(:code)
when :address
champ.full_address
when :nom
champ.title
end
end
def champ_value_for_tag(champ, path = :value)
case path
when :value
champ.rnf_id
when :departement
champ.departement_code_and_name || ''
when :code_insee
champ.commune&.fetch(:code) || ''
when :address
champ.full_address || ''
when :nom
champ.title || ''
end
end
end
private
def paths

View file

@ -2,4 +2,10 @@ class TypesDeChamp::TextareaTypeDeChamp < TypesDeChamp::TextTypeDeChamp
def estimated_fill_duration(revision)
FILL_DURATION_MEDIUM
end
class << self
def champ_value_for_export(champ, path = :value)
ActionView::Base.full_sanitizer.sanitize(champ.value)
end
end
end

View file

@ -7,4 +7,18 @@ class TypesDeChamp::TitreIdentiteTypeDeChamp < TypesDeChamp::TypeDeChampBase
end
def tags_for_template = [].freeze
class << self
def champ_value_for_export(champ, path = :value)
champ.piece_justificative_file.attached? ? "présent" : "absent"
end
def champ_value_for_api(champ, version = 2)
nil
end
def champ_default_export_value(path = :value)
"absent"
end
end
end

View file

@ -56,6 +56,46 @@ class TypesDeChamp::TypeDeChampBase
human_value
end
class << self
def champ_value(champ)
champ.value.present? ? champ.value.to_s : champ_default_value
end
def champ_value_for_api(champ, version = 2)
case version
when 2
champ_value(champ)
else
champ.valid_value.presence || champ_default_api_value(version)
end
end
def champ_value_for_export(champ, path = :value)
path == :value ? champ.valid_value.presence : champ_default_export_value(path)
end
def champ_value_for_tag(champ, path = :value)
path == :value ? champ_value(champ) : nil
end
def champ_default_value
''
end
def champ_default_export_value(path = :value)
nil
end
def champ_default_api_value(version = 2)
case version
when 2
''
else
nil
end
end
end
private
def paths

View file

@ -19,4 +19,50 @@ class TypesDeChamp::YesNoTypeDeChamp < TypesDeChamp::CheckboxTypeDeChamp
human_value
end
end
class << self
def champ_value(champ)
champ_formatted_value(champ)
end
def champ_value_for_tag(champ, path = :value)
champ_formatted_value(champ)
end
def champ_value_for_export(champ, path = :value)
champ_formatted_value(champ)
end
def champ_value_for_api(champ, version = 2)
case version
when 2
champ.true? ? 'true' : 'false'
else
super
end
end
def champ_default_value
'Non'
end
def champ_default_export_value(path = :value)
'Non'
end
def champ_default_api_value(version = 2)
case version
when 2
'false'
else
nil
end
end
private
def champ_formatted_value(champ)
champ.true? ? 'Oui' : 'Non'
end
end
end

View file

@ -49,6 +49,7 @@ class DossierProjectionService
individual_last_name = { TABLE => 'individual', COLUMN => 'nom' }
sva_svr_decision_on_field = { TABLE => 'self', COLUMN => 'sva_svr_decision_on' }
dossier_corrections = { TABLE => 'dossier_corrections', COLUMN => 'resolved_at' }
champ_value = champ_value_formatter(dossiers_ids, fields)
([state_field, archived_field, sva_svr_decision_on_field, hidden_by_user_at_field, hidden_by_administration_at_field, for_tiers_field, individual_first_name, individual_last_name, batch_operation_field, dossier_corrections] + fields) # the view needs state and archived dossier attributes
.each { |f| f[:id_value_h] = {} }
.group_by { |f| f[TABLE] } # one query per table
@ -64,7 +65,7 @@ class DossierProjectionService
.group_by(&:stable_id) # the champs are redispatched to their respective fields
.map do |stable_id, champs|
field = fields.find { |f| f[COLUMN] == stable_id.to_s }
field[:id_value_h] = champs.to_h { |c| [c.dossier_id, c.to_s] }
field[:id_value_h] = champs.to_h { |c| [c.dossier_id, champ_value.(c)] }
end
when 'self'
Dossier
@ -159,4 +160,27 @@ class DossierProjectionService
)
end
end
class << self
private
def champ_value_formatter(dossiers_ids, fields)
stable_ids = fields.filter { _1[TABLE].in?(['type_de_champ', 'type_de_champ_private']) }.map { _1[COLUMN] }
revision_ids_by_dossier_ids = Dossier.where(id: dossiers_ids).pluck(:id, :revision_id).to_h
stable_ids_and_types_champ_by_revision_ids = ProcedureRevisionTypeDeChamp.includes(:type_de_champ)
.where(revision_id: revision_ids_by_dossier_ids.values.uniq, type_de_champ: { stable_id: stable_ids })
.pluck(:revision_id, 'type_de_champ.stable_id', 'type_de_champ.type_champ')
.group_by(&:first)
.transform_values { _1.map { |_, stable_id, type_champ| [stable_id, type_champ] }.to_h }
stable_ids_and_types_champ_by_dossier_ids = revision_ids_by_dossier_ids.transform_values { stable_ids_and_types_champ_by_revision_ids[_1] }.compact
-> (champ) {
type_champ = stable_ids_and_types_champ_by_dossier_ids.fetch(champ.dossier_id, {})[champ.stable_id]
if type_champ.present? && TypeDeChamp.type_champ_to_champ_class_name(type_champ) == champ.type
TypeDeChamp.champ_value(type_champ, champ)
else
''
end
}
end
end
end

View file

@ -142,10 +142,7 @@ describe Champ do
end
describe 'for_export' do
let(:type_de_champ) { create(:type_de_champ) }
let(:champ) { type_de_champ.champ.build(value: value) }
before { champ.save }
let(:champ) { create(:champ_text, value: value) }
context 'when type_de_champ is text' do
let(:value) { '123' }
@ -154,14 +151,14 @@ describe Champ do
end
context 'when type_de_champ is textarea' do
let(:type_de_champ) { create(:type_de_champ_textarea) }
let(:champ) { create(:champ_textarea, value: value) }
let(:value) { '<b>gras<b>' }
it { expect(champ.for_export).to eq('gras') }
end
context 'when type_de_champ is yes_no' do
let(:type_de_champ) { create(:type_de_champ_yes_no) }
let(:champ) { create(:champ_yes_no, value: value) }
context 'if yes' do
let(:value) { 'true' }
@ -182,20 +179,27 @@ describe Champ do
end
end
context 'when type_de_champ is multiple_drop_down_list' do
let(:champ) { create(:champ_multiple_drop_down_list, value:) }
let(:value) { '["Crétinier", "Mousserie"]' }
it { expect(champ.for_export).to eq('Crétinier, Mousserie') }
end
end
describe '#search_terms' do
let(:champ) { type_de_champ.champ.build(value: value) }
subject { champ.search_terms }
context 'for adresse champ' do
let(:type_de_champ) { build(:type_de_champ_address) }
let(:champ) { create(:champ_address, value:) }
let(:value) { "10 rue du Pinson qui Piaille" }
it { is_expected.to eq([value]) }
end
context 'for checkbox champ' do
let(:libelle) { 'majeur' }
let(:type_de_champ) { build(:type_de_champ_checkbox, libelle: libelle) }
let(:libelle) { champ.libelle }
let(:champ) { create(:champ_checkbox, value:) }
context 'when the box is checked' do
let(:value) { 'true' }
@ -211,77 +215,74 @@ describe Champ do
end
context 'for civilite champ' do
let(:type_de_champ) { build(:type_de_champ_civilite) }
let(:champ) { create(:champ_civilite, value:) }
let(:value) { "M." }
it { is_expected.to eq([value]) }
end
context 'for date champ' do
let(:type_de_champ) { build(:type_de_champ_date) }
let(:champ) { create(:champ_date, value:) }
let(:value) { "2018-07-30" }
it { is_expected.to be_nil }
end
context 'for date time champ' do
let(:type_de_champ) { build(:type_de_champ_datetime) }
let(:champ) { create(:champ_datetime, value:) }
let(:value) { "2018-04-29 09:00" }
it { is_expected.to be_nil }
end
context 'for département champ' do
let(:type_de_champ) { build(:type_de_champ_departements) }
let(:champ) { create(:champ_departements, value:) }
let(:value) { "69" }
it { is_expected.to eq(['69 Rhône']) }
end
context 'for dossier link champ' do
let(:type_de_champ) { build(:type_de_champ_dossier_link) }
let(:champ) { create(:champ_dossier_link, value:) }
let(:value) { "9103132886" }
it { is_expected.to eq([value]) }
end
context 'for drop down list champ' do
let(:type_de_champ) { build(:type_de_champ_dossier_link) }
let(:champ) { create(:champ_dossier_link, value:) }
let(:value) { "HLM" }
it { is_expected.to eq([value]) }
end
context 'for email champ' do
let(:type_de_champ) { build(:type_de_champ_email) }
let(:champ) { build(:champ_email, value:) }
let(:value) { "machin@example.com" }
it { is_expected.to eq([value]) }
end
context 'for explication champ' do
let(:type_de_champ) { build(:type_de_champ_explication) }
let(:value) { nil }
let(:champ) { build(:champ_explication) }
it { is_expected.to be_nil }
end
context 'for header section champ' do
let(:type_de_champ) { build(:type_de_champ_header_section) }
let(:value) { nil }
let(:champ) { build(:champ_header_section) }
it { is_expected.to be_nil }
end
context 'for linked drop down list champ' do
let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list) }
let(:champ) { type_de_champ.champ.build(primary_value: "hello", secondary_value: "world") }
let(:champ) { create(:champ_linked_drop_down_list, primary_value: "hello", secondary_value: "world") }
it { is_expected.to eq(["hello", "world"]) }
end
context 'for multiple drop down list champ' do
let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list) }
let(:champ) { build(:champ_multiple_drop_down_list, value:) }
context 'when there are multiple values selected' do
let(:value) { JSON.generate(['goodbye', 'cruel', 'world']) }
@ -297,43 +298,41 @@ describe Champ do
end
context 'for number champ' do
let(:type_de_champ) { build(:type_de_champ_number) }
let(:champ) { build(:champ_number, value:) }
let(:value) { "1234" }
it { is_expected.to eq([value]) }
end
context 'for pays champ' do
let(:type_de_champ) { build(:type_de_champ_pays) }
let(:champ) { build(:champ_pays, value:) }
let(:value) { "FR" }
it { is_expected.to eq(['France']) }
end
context 'for phone champ' do
let(:type_de_champ) { build(:type_de_champ_phone) }
let(:champ) { build(:champ_phone, value:) }
let(:value) { "06 06 06 06 06" }
it { is_expected.to eq([value]) }
end
context 'for pièce justificative champ' do
let(:type_de_champ) { build(:type_de_champ_piece_justificative) }
let(:champ) { build(:champ_piece_justificative, value:) }
let(:value) { nil }
it { is_expected.to be_nil }
end
context 'for region champ' do
let(:type_de_champ) { build(:type_de_champ_regions) }
let(:champ) { build(:champ_regions, value:) }
let(:value) { "11" }
it { is_expected.to eq(['Île-de-France']) }
end
context 'for siret champ' do
let(:type_de_champ) { build(:type_de_champ_siret) }
context 'when there is an etablissement' do
let(:etablissement) do
build(
@ -368,36 +367,36 @@ describe Champ do
association_date_publication: "1962-05-31"
)
end
let(:champ) { type_de_champ.champ.build(value: etablissement.siret, etablissement: etablissement) }
let(:champ) { create(:champ_siret, value: etablissement.siret, etablissement:) }
it { is_expected.to eq([etablissement.entreprise_siren, etablissement.entreprise_numero_tva_intracommunautaire, etablissement.entreprise_forme_juridique, etablissement.entreprise_forme_juridique_code, etablissement.entreprise_nom_commercial, etablissement.entreprise_raison_sociale, etablissement.entreprise_siret_siege_social, etablissement.entreprise_nom, etablissement.entreprise_prenom, etablissement.association_rna, etablissement.association_titre, etablissement.association_objet, etablissement.siret, etablissement.enseigne, etablissement.naf, etablissement.libelle_naf, etablissement.adresse, etablissement.code_postal, etablissement.localite, etablissement.code_insee_localite]) }
end
context 'when there is no etablissement' do
let(:siret) { "35130347400024" }
let(:champ) { type_de_champ.champ.build(value: siret) }
let(:champ) { create(:champ_siret, value:, etablissement: nil) }
let(:value) { "35130347400024" }
it { is_expected.to eq([siret]) }
it { is_expected.to eq([value]) }
end
end
context 'for text champ' do
let(:type_de_champ) { build(:type_de_champ_text) }
let(:champ) { build(:champ_text, value:) }
let(:value) { "Blah" }
it { is_expected.to eq([value]) }
end
context 'for text area champ' do
let(:type_de_champ) { build(:type_de_champ_textarea) }
let(:champ) { build(:champ_textarea, value:) }
let(:value) { "Bla\nBlah de bla." }
it { is_expected.to eq([value]) }
end
context 'for yes/no champ' do
let(:type_de_champ) { build(:type_de_champ_yes_no, libelle: libelle) }
let(:libelle) { 'avec enfant à charge' }
let(:champ) { build(:champ_yes_no, value:) }
let(:libelle) { champ.libelle }
context 'when the box is checked' do
let(:value) { "true" }
@ -413,18 +412,9 @@ describe Champ do
end
end
context 'when type_de_champ is multiple_drop_down_list' do
let(:type_de_champ) { create(:type_de_champ_multiple_drop_down_list) }
let(:value) { '["Crétinier", "Mousserie"]' }
it { expect(champ.for_export).to eq('Crétinier, Mousserie') }
end
end
describe '#enqueue_virus_scan' do
context 'when type_champ is type_de_champ_piece_justificative' do
let(:type_de_champ) { create(:type_de_champ_piece_justificative) }
let(:champ) { build(:champ_piece_justificative, type_de_champ: type_de_champ) }
let(:champ) { build(:champ_piece_justificative) }
context 'and there is a blob' do
before do

View file

@ -1,5 +1,5 @@
describe Champs::AddressChamp do
let(:champ) { Champs::AddressChamp.new(value: value, data: data, type_de_champ: create(:type_de_champ_address)) }
let(:champ) { build(:champ_address, value:, data:) }
let(:value) { '' }
let(:data) { nil }

View file

@ -1,5 +1,5 @@
describe Champs::CarteChamp do
let(:champ) { Champs::CarteChamp.new(geo_areas: geo_areas, type_de_champ: create(:type_de_champ_carte)) }
let(:champ) { build(:champ_carte, geo_areas:) }
let(:value) { '' }
let(:coordinates) { [[[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]]] }
let(:geo_json) do

View file

@ -61,7 +61,7 @@ describe Champs::DepartementChamp, type: :model do
end
describe 'value' do
let(:champ) { described_class.new }
let(:champ) { build(:champ_departements, value: nil) }
it 'with code having 2 chars' do
champ.value = '01'

View file

@ -144,16 +144,19 @@ describe Champs::EpciChamp, type: :model do
end
describe 'value' do
let(:champ) { described_class.new }
let(:champ) { build(:champ_epci, external_id: nil, value: nil) }
let(:epci) { APIGeoService.epcis('01').first }
it 'with departement and code' do
champ.code_departement = '01'
champ.value = '200042935'
expect(champ.external_id).to eq('200042935')
expect(champ.value).to eq('CA Haut - Bugey Agglomération')
expect(champ.selected).to eq('200042935')
expect(champ.code).to eq('200042935')
champ.value = epci[:code]
expect(champ.blank?).to be_falsey
expect(champ.external_id).to eq(epci[:code])
expect(champ.value).to eq(epci[:name])
expect(champ.selected).to eq(epci[:code])
expect(champ.code).to eq(epci[:code])
expect(champ.departement?).to be_truthy
expect(champ.to_s).to eq('CA Haut - Bugey Agglomération')
expect(champ.to_s).to eq(epci[:name])
end
end
end

View file

@ -1,13 +1,13 @@
describe Champs::LinkedDropDownListChamp do
describe '#unpack_value' do
let(:champ) { described_class.new(value: '["tata", "tutu"]') }
let(:champ) { build(:champ_linked_drop_down_list, value: '["tata", "tutu"]') }
it { expect(champ.primary_value).to eq('tata') }
it { expect(champ.secondary_value).to eq('tutu') }
end
describe '#pack_value' do
let(:champ) { described_class.new(primary_value: 'tata', secondary_value: 'tutu') }
let(:champ) { build(:champ_linked_drop_down_list, primary_value: 'tata', secondary_value: 'tutu') }
before { champ.save }
@ -15,7 +15,7 @@ describe Champs::LinkedDropDownListChamp do
end
describe '#primary_value=' do
let!(:champ) { described_class.new(primary_value: 'tata', secondary_value: 'tutu') }
let!(:champ) { build(:champ_linked_drop_down_list, primary_value: 'tata', secondary_value: 'tutu') }
before { champ.primary_value = '' }
@ -23,7 +23,7 @@ describe Champs::LinkedDropDownListChamp do
end
describe '#to_s' do
let(:champ) { described_class.new(primary_value: primary_value, secondary_value: secondary_value) }
let(:champ) { build(:champ_linked_drop_down_list, value: [primary_value, secondary_value].to_json) }
let(:primary_value) { nil }
let(:secondary_value) { nil }
@ -48,22 +48,28 @@ describe Champs::LinkedDropDownListChamp do
end
describe 'for_export' do
let(:champ) { build(:champ_linked_drop_down_list, value:) }
let(:value) { [primary_value, secondary_value].to_json }
let(:primary_value) { nil }
let(:secondary_value) { nil }
subject { champ.for_export }
context 'with no value' do
let(:champ) { described_class.new }
let(:value) { nil }
it { is_expected.to be_nil }
end
context 'with primary value' do
let(:champ) { described_class.new(primary_value: 'primary') }
let(:primary_value) { 'primary' }
it { is_expected.to eq('primary;') }
end
context 'with secondary value' do
let(:champ) { described_class.new(primary_value: 'primary', secondary_value: 'secondary') }
let(:primary_value) { 'primary' }
let(:secondary_value) { 'secondary' }
it { is_expected.to eq('primary;secondary') }
end

View file

@ -1,5 +1,5 @@
describe Champs::PaysChamp, type: :model do
let(:champ) { described_class.new }
let(:champ) { build(:champ_pays, value: nil) }
describe 'value' do
it 'with code' do

View file

@ -47,7 +47,7 @@ describe Champs::PieceJustificativeChamp do
context 'without attached file' do
before { champ_pj.piece_justificative_file.purge }
it { is_expected.to eq('') }
it { is_expected.to eq(nil) }
end
end

View file

@ -1,5 +1,5 @@
describe Champs::PoleEmploiChamp, type: :model do
let(:champ) { described_class.new }
let(:champ) { build(:champ_pole_emploi) }
describe 'identifiant' do
before do

View file

@ -1,7 +1,7 @@
describe Champs::RegionChamp, type: :model do
describe 'validations' do
describe 'external link' do
let(:champ) { build(:champ_regions, external_id: external_id) }
let(:champ) { build(:champ_regions, value: nil, external_id: external_id) }
subject { champ.validate(:champs_public_value) }
context 'when nil' do
let(:external_id) { nil }
@ -29,7 +29,7 @@ describe Champs::RegionChamp, type: :model do
end
describe 'value' do
let(:champ) { create(:champ_regions) }
let(:champ) { create(:champ_regions, value: nil) }
subject { champ.validate(:champs_public_value) }
before { champ.update_columns(value: value) }
@ -61,7 +61,7 @@ describe Champs::RegionChamp, type: :model do
end
describe 'value' do
let(:champ) { described_class.new }
let(:champ) { build(:champ_regions, value: nil) }
it 'with code' do
champ.value = '01'

View file

@ -104,7 +104,7 @@ describe ChampSerializer do
context 'when type champ is siret' do
let(:etablissement) { create(:etablissement) }
let(:champ) { create(:type_de_champ_siret).champ.create(etablissement: etablissement, value: etablissement.siret) }
let(:champ) { create(:champ_siret, etablissement:, value: etablissement.siret) }
it {
is_expected.to include(value: etablissement.siret)

View file

@ -214,7 +214,7 @@ describe DossierProjectionService do
let(:dossier) { create(:dossier, procedure: procedure) }
let(:column) { dossier.procedure.active_revision.types_de_champ_public.first.stable_id.to_s }
before { dossier.champs_public.first.update(data: { 'label' => '18 a la bonne rue', 'departement' => 'd' }) }
before { dossier.champs_public.first.update(value: '18 a la bonne rue', data: { 'label' => '18 a la bonne rue', 'departement' => 'd' }) }
it { is_expected.to eq('18 a la bonne rue') }
end