Merge branch 'main' into feature/prefill_civility

This commit is contained in:
Damien Le Thiec 2022-12-26 11:31:52 +01:00 committed by GitHub
commit 9e083ea3f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 1846 additions and 406 deletions

View file

@ -15,6 +15,10 @@ select,
font-weight: bold;
}
trix-editor.fr-input {
max-height: none;
}
// Fix firefox < 80, Safari < 15.4, Chrome < 83 not supporting "appearance: auto" on inputs
// This rule was set by DSFR for DSFR design, but broke our legacy forms.
// scss-lint:disable DuplicateProperty

View file

@ -12,7 +12,6 @@
a {
color: #FFFFFF;
text-decoration: underline;
}
}

View file

@ -34,11 +34,6 @@
}
}
.afficher-dossiers-supprimes {
display: flex;
justify-content: flex-end;
}
.filter {
display: inline-block;
padding-left: 10px;
@ -106,6 +101,6 @@
// fix/dsfr
.fr-checkbox-group.fix-dsfr-notified-toggle-component {
margin-top: -7px;
margin-top: -0.5rem;
}
}

View file

@ -12,15 +12,15 @@ class Dossiers::NotifiedToggleComponent < ApplicationComponent
end
def active?
sorted_by_notifications? && order_asc?
sorted_by_notifications? && order_desc?
end
def icon_class_name
active? ? 'fr-fi-checkbox' : 'fr-fi-checkbox-blank'
end
def order_asc?
current_order == 'asc'
def order_desc?
current_order == 'desc'
end
def current_order

View file

@ -1,3 +1,13 @@
class EditableChamp::DepartementsComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
private
def options
APIGeoService.departements.map { ["#{_1[:code]} #{_1[:name]}", _1[:code]] }
end
def select_options
{ selected: @champ.selected }.merge(@champ.mandatory? ? { prompt: '' } : { include_blank: '' })
end
end

View file

@ -1,7 +1 @@
= @form.hidden_field :value
= @form.hidden_field :external_id
= react_component("ComboDepartementsSearch",
required: @champ.required?,
id: @champ.input_id,
className: "width-33-desktop width-100-mobile",
describedby: @champ.describedby_id)
= @form.select :value, options, select_options, required: @champ.mandatory?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile"

View file

@ -1,3 +1,18 @@
class EditableChamp::PaysComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
private
def options
options = APIGeoService.countries.map { [_1[:name], _1[:code]] }
# For legacy fields, selected value is non standard country name. Add it to the list.
if (@champ.selected&.size || 0) > 2
options.unshift([@champ.selected, @champ.selected])
end
options
end
def select_options
{ selected: @champ.selected }.merge(@champ.mandatory? ? { prompt: '' } : { include_blank: '' })
end
end

View file

@ -1,7 +1 @@
= @form.hidden_field :value
= @form.hidden_field :external_id
= react_component("ComboPaysSearch",
required: @champ.required?,
id: @champ.input_id,
className: "width-33-desktop width-100-mobile",
describedby: @champ.describedby_id)
= @form.select :value, options, select_options, required: @champ.mandatory?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile"

View file

@ -1,3 +1,13 @@
class EditableChamp::RegionsComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
private
def options
APIGeoService.regions.map { [_1[:name], _1[:code]] }
end
def select_options
{ selected: @champ.selected }.merge(@champ.mandatory? ? { prompt: '' } : { include_blank: '' })
end
end

View file

@ -1,7 +1 @@
= @form.hidden_field :value
= @form.hidden_field :external_id
= react_component("ComboRegionsSearch",
required: @champ.required?,
id: @champ.input_id,
className: "width-33-desktop width-100-mobile",
describedby: @champ.describedby_id)
= @form.select :value, options, select_options, required: @champ.mandatory?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile"

View file

@ -1,20 +0,0 @@
class API::PaysController < ApplicationController
before_action :authenticate_logged_user!
def index
countries = CountriesService.get('FR').zip(CountriesService.get(I18n.locale))
countries = countries.map do |(code, value_fr), (localized_code, localized_value)|
if code != localized_code
raise "Countries lists mismatch. It means i18n_data gem has some internal inconsistencies."
end
{
code: code,
value: value_fr,
label: localized_value
}
end
render json: countries
end
end

View file

@ -54,11 +54,14 @@ class API::V2::Schema < GraphQL::Schema
Types::Champs::DateChampType,
Types::Champs::DatetimeChampType,
Types::Champs::DecimalNumberChampType,
Types::Champs::DepartementChampType,
Types::Champs::DossierLinkChampType,
Types::Champs::IntegerNumberChampType,
Types::Champs::LinkedDropDownListChampType,
Types::Champs::MultipleDropDownListChampType,
Types::Champs::PaysChampType,
Types::Champs::PieceJustificativeChampType,
Types::Champs::RegionChampType,
Types::Champs::RepetitionChampType,
Types::Champs::SiretChampType,
Types::Champs::TextChampType,

View file

@ -442,6 +442,24 @@ class API::V2::StoredQuery
code
}
}
... on DepartementChamp {
departement {
name
code
}
}
... on RegionChamp {
region {
name
code
}
}
... on PaysChamp {
pays {
name
code
}
}
... on SiretChamp {
etablissement {
...PersonneMoraleFragment

View file

@ -685,6 +685,21 @@ type Departement {
name: String!
}
type DepartementChamp implements Champ {
departement: Departement
id: ID!
"""
Libellé du champ.
"""
label: String!
"""
La valeur du champ sous forme texte.
"""
stringValue: String
}
"""
Represents direct upload credentials
"""
@ -2002,6 +2017,26 @@ type ParcelleCadastrale implements GeoArea {
surfaceParcelle: Float! @deprecated(reason: "Utilisez le champ `surface` à la place.")
}
type Pays {
code: String!
name: String!
}
type PaysChamp implements Champ {
id: ID!
"""
Libellé du champ.
"""
label: String!
pays: Pays
"""
La valeur du champ sous forme texte.
"""
stringValue: String
}
type PersonneMorale implements Demandeur {
address: Address!
adresse: String! @deprecated(reason: "Utilisez le champ `address.label` à la place.")
@ -2088,6 +2123,26 @@ type Query {
): GroupeInstructeurWithDossiers!
}
type Region {
code: String!
name: String!
}
type RegionChamp implements Champ {
id: ID!
"""
Libellé du champ.
"""
label: String!
region: Region
"""
La valeur du champ sous forme texte.
"""
stringValue: String
}
type RepetitionChamp implements Champ {
champs: [Champ!]! @deprecated(reason: "Utilisez le champ `rows` à la place.")
id: ID!

View file

@ -31,6 +31,24 @@ module Types
else
Types::Champs::TextChampType
end
when ::Champs::DepartementChamp
if context.has_fragment?(:DepartementChamp)
Types::Champs::DepartementChampType
else
Types::Champs::TextChampType
end
when ::Champs::RegionChamp
if context.has_fragment?(:RegionChamp)
Types::Champs::RegionChampType
else
Types::Champs::TextChampType
end
when ::Champs::PaysChamp
if context.has_fragment?(:PaysChamp)
Types::Champs::PaysChampType
else
Types::Champs::TextChampType
end
when ::Champs::DossierLinkChamp
Types::Champs::DossierLinkChampType
when ::Champs::PieceJustificativeChamp

View file

@ -7,13 +7,8 @@ module Types::Champs
field :code, String, "Le code INSEE", null: false
end
class DepartementType < Types::BaseObject
field :name, String, null: false
field :code, String, null: false
end
field :commune, CommuneType, null: true
field :departement, DepartementType, null: true
field :departement, Types::Champs::DepartementChampType::DepartementType, null: true
def commune
if object.code?

View file

@ -0,0 +1,16 @@
module Types::Champs
class DepartementChampType < Types::BaseObject
implements Types::ChampType
class DepartementType < Types::BaseObject
field :name, String, null: false
field :code, String, null: false
end
field :departement, DepartementType, null: true
def departement
object if object.external_id.present?
end
end
end

View file

@ -0,0 +1,16 @@
module Types::Champs
class PaysChampType < Types::BaseObject
implements Types::ChampType
class PaysType < Types::BaseObject
field :name, String, null: false
field :code, String, null: false
end
field :pays, PaysType, null: true
def pays
object if object.external_id.present?
end
end
end

View file

@ -0,0 +1,16 @@
module Types::Champs
class RegionChampType < Types::BaseObject
implements Types::ChampType
class RegionType < Types::BaseObject
field :name, String, null: false
field :code, String, null: false
end
field :region, RegionType, null: true
def region
object if object.external_id.present?
end
end
end

View file

@ -1,9 +1,7 @@
import React from 'react';
import { QueryClientProvider } from 'react-query';
import { matchSorter } from 'match-sorter';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
import { queryClient } from './shared/queryClient';
type DepartementResult = { code: string; nom: string };
@ -42,13 +40,3 @@ export function ComboDepartementsSearch({
/>
);
}
export default function ComboDepartementsSearchDefault(
params: ComboDepartementsSearchProps
) {
return (
<QueryClientProvider client={queryClient}>
<ComboDepartementsSearch {...params} />
</QueryClientProvider>
);
}

View file

@ -1,20 +0,0 @@
import React from 'react';
import { QueryClientProvider } from 'react-query';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
import { queryClient } from './shared/queryClient';
export default function ComboPaysSearch(
props: ComboSearchProps<{ code: string; value: string; label: string }>
) {
return (
<QueryClientProvider client={queryClient}>
<ComboSearch
{...props}
scope="pays"
minimumInputLength={0}
transformResult={({ code, value, label }) => [code, value, label]}
/>
</QueryClientProvider>
);
}

View file

@ -1,20 +0,0 @@
import React from 'react';
import { QueryClientProvider } from 'react-query';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
import { queryClient } from './shared/queryClient';
export default function ComboRegionsSearch(
props: ComboSearchProps<{ code: string; nom: string }>
) {
return (
<QueryClientProvider client={queryClient}>
<ComboSearch
{...props}
scope="regions"
minimumInputLength={0}
transformResult={({ code, nom }) => [code, nom]}
/>
</QueryClientProvider>
);
}

View file

@ -0,0 +1,48 @@
import { ApplicationController } from './application_controller';
import { hide, show } from '@utils';
export class SupportController extends ApplicationController {
static targets = ['inputRadio', 'content'];
declare readonly inputRadioTargets: HTMLInputElement[];
declare readonly contentTargets: HTMLElement[];
connect() {
this.inputRadioTargets.forEach((inputRadio) => {
inputRadio.addEventListener('change', this.onChange.bind(this));
inputRadio.addEventListener('keydown', this.onChange.bind(this));
});
}
onChange(event: Event) {
const target = event.target as HTMLInputElement;
const content = this.getContentForTarget(target);
this.contentTargets.forEach((content) => {
hide(content);
content.setAttribute('aria-hidden', 'true');
});
if (target.checked && content) {
show(content);
content.setAttribute('aria-hidden', 'false');
}
}
getLabelForTarget(target: HTMLInputElement) {
const labelSelector = `label[for="${target.id}"]`;
return document.querySelector(labelSelector);
}
getContentForTarget(target: HTMLInputElement) {
const label = this.getLabelForTarget(target);
if (!label) {
return null;
}
const contentSelector = label.getAttribute('aria-controls');
if (contentSelector) {
return document.getElementById(contentSelector);
}
}
}

View file

@ -14,7 +14,6 @@ import { registerControllers } from '../shared/stimulus-loader';
import '../new_design/form-validation';
import '../new_design/procedure-context';
import '../new_design/procedure-form';
import '../new_design/support';
import {
toggleCondidentielExplanation,

View file

@ -8,6 +8,7 @@
/* Verify README of each component to insert them in the expected order. */
@import '@gouvfr/dsfr/dist/component/alert/alert.css';
@import '@gouvfr/dsfr/dist/component/radio/radio.css';
@import '@gouvfr/dsfr/dist/component/badge/badge.css';
@import '@gouvfr/dsfr/dist/component/breadcrumb/breadcrumb.css';
@import '@gouvfr/dsfr/dist/component/callout/callout.css';

View file

@ -1,110 +0,0 @@
//
// This content is inspired by w3c aria example, rewritten for better RGAA compatibility.
// https://www.w3.org/TR/wai-aria-practices-1.1/examples/disclosure/disclosure-faq.html
//
class ButtonExpand {
constructor(domNode) {
this.domNode = domNode;
this.keyCode = Object.freeze({
RETURN: 13
});
this.allButtons = [];
this.controlledNode = false;
var id = this.domNode.getAttribute('aria-controls');
if (id) {
this.controlledNode = document.getElementById(id);
}
this.radioInput = this.domNode.querySelector('input[type="radio"]');
this.hideContent();
this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
this.domNode.addEventListener('click', this.handleClick.bind(this));
}
showContent() {
this.radioInput.checked = true;
if (this.controlledNode) {
this.controlledNode.setAttribute('aria-hidden', 'false');
this.controlledNode.classList.remove('hidden');
}
this.allButtons.forEach((b) => {
if (b != this) {
b.hideContent();
}
});
}
hideContent() {
this.radioInput.checked = false;
if (this.controlledNode) {
this.controlledNode.setAttribute('aria-hidden', 'true');
this.controlledNode.classList.add('hidden');
}
}
toggleExpand() {
if (
this.controlledNode &&
this.controlledNode.getAttribute('aria-hidden') === 'true'
) {
this.showContent();
} else {
this.hideContent();
}
}
setAllButtons(buttons) {
this.allButtons = buttons;
}
handleKeydown(event) {
switch (event.keyCode) {
case this.keyCode.RETURN:
this.showContent();
event.stopPropagation();
event.preventDefault();
break;
default:
break;
}
}
handleClick() {
// NOTE: click event is also fired on input and label activations
// ie., not necessarily by a mouse click but any user inputs, like keyboard navigation with arrows keys.
// Cf https://www.w3.org/TR/2012/WD-html5-20121025/content-models.html#interactive-content
this.showContent();
}
}
/* Initialize Hide/Show Buttons */
if (document.querySelector('#contact-form')) {
window.addEventListener(
'DOMContentLoaded',
function () {
var buttons = document.querySelectorAll('fieldset[name=type] label');
var expandButtons = [];
buttons.forEach((button) => {
var be = new ButtonExpand(button);
expandButtons.push(be);
});
expandButtons.forEach((button) => button.setAllButtons(expandButtons));
},
false
);
}

View file

@ -78,12 +78,15 @@ class BatchOperation < ApplicationRecord
values.push([arel_table[:run_at], Time.zone.now]) if called_for_first_time?
values.push([arel_table[:finished_at], Time.zone.now]) if called_for_last_time?(dossier)
values.push([arel_table[:updated_at], Time.zone.now])
# NOTE: ensure to append BigInteger to SQL array by casting IDs
if success
values.push([arel_table[:success_dossier_ids], Arel::Nodes::NamedFunction.new('array_append', [arel_table[:success_dossier_ids], dossier.id])])
values.push([arel_table[:failed_dossier_ids], Arel::Nodes::NamedFunction.new('array_remove', [arel_table[:failed_dossier_ids], dossier.id])])
values.push([arel_table[:success_dossier_ids], Arel::Nodes::NamedFunction.new('array_append', [arel_table[:success_dossier_ids], Arel::Nodes::SqlLiteral.new("#{dossier.id}::BIGINT")])])
values.push([arel_table[:failed_dossier_ids], Arel::Nodes::NamedFunction.new('array_remove', [arel_table[:failed_dossier_ids], Arel::Nodes::SqlLiteral.new("#{dossier.id}::BIGINT")])])
else
values.push([arel_table[:failed_dossier_ids], Arel::Nodes::NamedFunction.new('array_append', [arel_table[:failed_dossier_ids], dossier.id])])
values.push([arel_table[:failed_dossier_ids], Arel::Nodes::NamedFunction.new('array_append', [arel_table[:failed_dossier_ids], Arel::Nodes::SqlLiteral.new("#{dossier.id}::BIGINT")])])
end
manager.set(values)
ActiveRecord::Base.connection.update(manager.to_sql)
end

View file

@ -220,21 +220,14 @@ class Champ < ApplicationRecord
end
end
def clone(dossier:, parent: nil)
kopy = deep_clone(only: (private? ? [] : [:value, :value_json, :data, :external_id]) + [:private, :row, :type, :type_de_champ_id],
include: private? ? [] : [:etablissement, :geo_areas])
def clone
champ_attributes = [:parent_id, :private, :row, :type, :type_de_champ_id]
value_attributes = private? ? [] : [:value, :value_json, :data, :external_id]
relationships = private? ? [] : [:etablissement, :geo_areas]
kopy.dossier = dossier
kopy.parent = parent if parent
case self
when Champs::RepetitionChamp
kopy.champs = (private? ? champs.where(row: 0) : champs).map do |champ_de_repetition|
champ_de_repetition.clone(dossier: dossier, parent: kopy)
end
when Champs::PieceJustificativeChamp, Champs::TitreIdentiteChamp
PiecesJustificativesService.clone_attachments(self, kopy) if !private? && piece_justificative_file.attached?
deep_clone(only: champ_attributes + value_attributes, include: relationships) do |original, kopy|
PiecesJustificativesService.clone_attachments(original, kopy)
end
kopy
end
private

View file

@ -21,4 +21,48 @@
# type_de_champ_id :integer
#
class Champs::DepartementChamp < Champs::TextChamp
def for_export
[name, code]
end
def to_s
formatted_value
end
def for_tag
formatted_value
end
def selected
code
end
def code
external_id || APIGeoService.departement_code(name)
end
def name
maybe_code_and_name = value&.match(/(\d+) - (.+)/)
if maybe_code_and_name
maybe_code_and_name[2]
else
value
end
end
def value=(code)
if code&.size == 2
self.external_id = code
super(APIGeoService.departement_name(code))
elsif code.blank?
self.external_id = nil
super(nil)
end
end
private
def formatted_value
blank? ? "" : "#{code} #{name}"
end
end

View file

@ -21,19 +21,44 @@
# type_de_champ_id :integer
#
class Champs::PaysChamp < Champs::TextChamp
def localized_value
def for_export
[name, code]
end
def to_s
name
end
def for_tag
name
end
def selected
code || value
end
def value=(code)
if code&.size == 2
self.external_id = code
super(APIGeoService.country_name(code, locale: 'FR'))
elsif code.blank?
self.external_id = nil
super(nil)
elsif code != value
self.external_id = APIGeoService.country_code(code)
super(code)
end
end
def code
external_id || APIGeoService.country_code(value)
end
def name
if external_id
CountriesService.get(I18n.locale)[external_id].to_s
APIGeoService.country_name(external_id)
else
value.present? ? value.to_s : ''
end
end
def to_s
localized_value
end
def for_tag
localized_value
end
end

View file

@ -21,4 +21,29 @@
# type_de_champ_id :integer
#
class Champs::RegionChamp < Champs::TextChamp
def for_export
[name, code]
end
def selected
code
end
def name
value
end
def code
external_id || APIGeoService.region_code(value)
end
def value=(code)
if code&.size == 2
self.external_id = code
super(APIGeoService.region_name(code))
elsif code.blank?
self.external_id = nil
super(nil)
end
end
end

View file

@ -43,7 +43,7 @@ module DossierRebaseConcern
when :drop_down_other
!change[:from] && change[:to]
when :mandatory
change[:from] && !change[:to]
(change[:from] && !change[:to]) || can_change_mandatory?(change)
when :type_champ, :condition
false
else
@ -143,11 +143,7 @@ module DossierRebaseConcern
champ = target_coordinate
.type_de_champ
.build_champ(params)
if parent.is_a?(Dossier)
parent.champs_public << champ
else
parent.champs << champ
end
parent.champs << champ
end
def delete_champs_for_revision(stable_id)
@ -160,4 +156,8 @@ module DossierRebaseConcern
def purge_piece_justificative_file(champ)
ActiveStorage::Attachment.where(id: champ.piece_justificative_file.ids).delete_all
end
def can_change_mandatory?(change)
!champs.filter { _1.stable_id == change[:stable_id] }.any?(&:blank?)
end
end

View file

@ -902,6 +902,7 @@ class Dossier < ApplicationRecord
attestation&.destroy
save!
rebase_later
if !disable_notification
DossierMailer.notify_revert_to_instruction(self).deliver_later
end
@ -1211,27 +1212,30 @@ class Dossier < ApplicationRecord
@sections[champ.parent || (champ.public? ? :public : :private)]
end
# while cloning we do not have champ.id. it comes after transaction
# so we collect a list of jobs to process. then enqueue this list
def clone
cloned_dossier = deep_clone(only: [:autorisation_donnees, :user_id, :revision_id, :groupe_instructeur_id],
include: [:individual, :etablissement]) do |original, kopy|
dossier_attributes = [:autorisation_donnees, :user_id, :revision_id, :groupe_instructeur_id]
relationships = [:individual, :etablissement]
cloned_dossier = deep_clone(only: dossier_attributes, include: relationships) do |original, kopy|
PiecesJustificativesService.clone_attachments(original, kopy)
if original.is_a?(Dossier)
kopy.parent_dossier_id = original.id
kopy.state = Dossier.states.fetch(:brouillon)
kopy.champs_public = original.champs_public.map do |champ|
champ.clone(dossier: kopy)
end
kopy.champs_private = original.champs_private.map do |champ|
champ.clone(dossier: kopy)
cloned_champs = original.champs
.index_by(&:id)
.transform_values(&:clone)
kopy.champs = cloned_champs.values.map do |champ|
champ.dossier = kopy
champ.parent = cloned_champs[champ.parent_id] if champ.child?
champ
end
end
end
transaction do
cloned_dossier.save!
end
cloned_dossier
transaction { cloned_dossier.save! }
cloned_dossier.reload
end
def find_champs_by_stable_ids(stable_ids)

View file

@ -15,7 +15,7 @@ class PrefillDescription < SimpleDelegator
end
def types_de_champ
active_revision.types_de_champ_public.fillable
active_revision.types_de_champ_public.fillable.partition(&:prefillable?).flatten
end
def include?(type_de_champ_id)
@ -31,7 +31,7 @@ class PrefillDescription < SimpleDelegator
end
def prefilled_champs
@prefilled_champs ||= types_de_champ.where(id: selected_type_de_champ_ids)
@prefilled_champs ||= active_fillable_public_types_de_champ.where(id: selected_type_de_champ_ids)
end
private
@ -47,4 +47,8 @@ class PrefillDescription < SimpleDelegator
def example_value(type_de_champ)
I18n.t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}")
end
def active_fillable_public_types_de_champ
active_revision.types_de_champ_public.fillable
end
end

View file

@ -653,7 +653,7 @@ class Procedure < ApplicationRecord
end
def missing_zones?
if feature_enabled?(:zonage)
if Rails.application.config.ds_zonage_enabled
zones.empty?
else
false

View file

@ -293,13 +293,15 @@ class ProcedurePresentation < ApplicationRecord
update!(sort: {
TABLE => table,
COLUMN => column,
ORDER => opposite_order_for(table, column)
ORDER => order.presence || opposite_order_for(table, column)
})
end
def opposite_order_for(table, column)
if sort.values_at(TABLE, COLUMN) == [table, column]
sort['order'] == 'asc' ? 'desc' : 'asc'
elsif [table, column] == ["notifications", "notifications"]
'desc' # default order for notifications
else
'asc'
end

View file

@ -1,2 +1,5 @@
class TypesDeChamp::DepartementTypeDeChamp < TypesDeChamp::TextTypeDeChamp
def libelle_for_export(index)
[libelle, "#{libelle} (Code)"][index]
end
end

View file

@ -1,2 +1,5 @@
class TypesDeChamp::PaysTypeDeChamp < TypesDeChamp::TextTypeDeChamp
def libelle_for_export(index)
[libelle, "#{libelle} (Code)"][index]
end
end

View file

@ -1,2 +1,5 @@
class TypesDeChamp::RegionTypeDeChamp < TypesDeChamp::TextTypeDeChamp
def libelle_for_export(index)
[libelle, "#{libelle} (Code)"][index]
end
end

View file

@ -0,0 +1,77 @@
class APIGeoService
class << self
def countries(locale: I18n.locale)
I18nData.countries(locale)
.merge(get_localized_additional_countries(locale))
.map { |(code, name)| { name:, code: } }
.sort_by { I18n.transliterate(_1[:name]) }
end
def country_name(code, locale: I18n.locale)
countries(locale:).find { _1[:code] == code }&.dig(:name)
end
def country_code(name)
return if name.nil?
code = I18nData.country_code(name) || I18nData.country_code(name.humanize) || I18nData.country_code(name.titleize)
if code.nil?
countries_index_fr[I18n.transliterate(name).upcase]&.dig(:code)
else
code
end
end
def regions
get_from_api_geo(:regions).sort_by { I18n.transliterate(_1[:name]) }
end
def region_name(code)
regions.find { _1[:code] == code }&.dig(:name)
end
def region_code(name)
return if name.nil?
regions.find { _1[:name] == name }&.dig(:code)
end
def departements
[{ code: '99', name: 'Etranger' }] + get_from_api_geo(:departements).sort_by { _1[:code] }
end
def departement_name(code)
departements.find { _1[:code] == code }&.dig(:name)
end
def departement_code(name)
return if name.nil?
departements.find { _1[:name] == name }&.dig(:code)
end
private
def get_from_api_geo(scope)
Rails.cache.fetch("api_geo_#{scope}", expires_in: 1.year) do
response = Typhoeus.get("#{API_GEO_URL}/#{scope}")
JSON.parse(response.body).map(&:symbolize_keys)
.map { { name: _1[:nom].tr("'", ''), code: _1[:code] } }
end
end
def countries_index_fr
Rails.cache.fetch('countries_index_fr', expires_in: 1.year) do
countries(locale: 'FR').index_by { I18n.transliterate(_1[:name]).upcase }
end
end
def get_localized_additional_countries(locale)
additional_countries[locale.to_s.upcase] || {}
end
def additional_countries
{
'FR' => { 'XK' => 'Kosovo' },
'EN' => { 'XK' => 'Kosovo' }
}
end
end
end

View file

@ -1,16 +0,0 @@
class CountriesService
def self.get(locale)
I18nData.countries(locale).merge(get_localized_additional_countries(locale))
end
def self.get_localized_additional_countries(locale)
additional_countries[locale.to_s.upcase] || {}
end
def self.additional_countries
{
'FR' => { 'XK' => 'Kosovo' },
'EN' => { 'XK' => 'Kosovo' }
}
end
end

View file

@ -62,20 +62,24 @@ class PiecesJustificativesService
kopy.piece_justificative_file.attach(attachment.blob)
end
when TypeDeChamp
clone_attachment(original.piece_justificative_template, kopy.piece_justificative_template)
clone_attachment(original, kopy, :piece_justificative_template)
when Procedure
clone_attachment(original.logo, kopy.logo)
clone_attachment(original.notice, kopy.notice)
clone_attachment(original.deliberation, kopy.deliberation)
clone_attachment(original, kopy, :logo)
clone_attachment(original, kopy, :notice)
clone_attachment(original, kopy, :deliberation)
when AttestationTemplate
clone_attachment(original.logo, kopy.logo)
clone_attachment(original.signature, kopy.signature)
clone_attachment(original, kopy, :logo)
clone_attachment(original, kopy, :signature)
when Etablissement
clone_attachment(original, kopy, :entreprise_attestation_sociale)
clone_attachment(original, kopy, :entreprise_attestation_fiscale)
end
end
def self.clone_attachment(original_attachment, copy_attachment)
if original_attachment.attached?
copy_attachment.attach(original_attachment.blob)
def self.clone_attachment(original, kopy, attachment_name)
attachment = original.public_send(attachment_name)
if attachment.attached?
kopy.public_send(attachment_name).attach(attachment.blob)
end
end

View file

@ -191,6 +191,28 @@ class SerializerService
...AddressFragment
}
}
... on CommuneChamp {
commune {
name
code
}
departement {
name
code
}
}
... on DepartementChamp {
departement {
name
code
}
}
... on RegionChamp {
region {
name
code
}
}
}
fragment RepetitionChampFragment on RepetitionChamp {

View file

@ -2,4 +2,5 @@
%nav#header-navigation.fr-nav{ role: 'navigation', 'aria-label': 'Menu principal administrateur' }
%ul.fr-nav__list
%li.fr-nav__item= link_to 'Mes démarches', admin_procedures_path, class:'fr-nav__link', 'aria-current': current_page?(admin_procedures_path) ? 'page' : nil
%li.fr-nav__item= link_to 'Toutes les démarches', all_admin_procedures_path(zone_ids: current_administrateur.zones), class:'fr-nav__link', 'aria-current': current_page?(all_admin_procedures_path) ? 'page' : nil
- if Rails.application.config.ds_zonage_enabled
%li.fr-nav__item= link_to 'Toutes les démarches', all_admin_procedures_path(zone_ids: current_administrateur.zones), class:'fr-nav__link', 'aria-current': current_page?(all_admin_procedures_path) ? 'page' : nil

View file

@ -58,7 +58,7 @@
%h2.fr-mt-5w.fr-mb-3w.fr-h1 Indispensable avant publication
.fr-grid-row.fr-grid-row--gutters
= render Procedure::Card::PresentationComponent.new(procedure: @procedure)
= render Procedure::Card::ZonesComponent.new(procedure: @procedure) if @procedure.feature_enabled?(:zonage)
= render Procedure::Card::ZonesComponent.new(procedure: @procedure) if Rails.application.config.ds_zonage_enabled
= render Procedure::Card::ChampsComponent.new(procedure: @procedure)
= render Procedure::Card::ServiceComponent.new(procedure: @procedure, administrateur: current_administrateur)
= render Procedure::Card::AdministrateursComponent.new(procedure: @procedure)

View file

@ -13,7 +13,7 @@
= f.label :zone do
= t('zone', scope: 'activerecord.attributes.procedure')
- if @procedure.feature_enabled?(:zonage)
- if Rails.application.config.ds_zonage_enabled
= f.collection_check_boxes :zone_ids, Zone.available_at(@procedure.published_or_created_at), :id, :label do |b|
.editable-champ.editable-champ-checkbox
= b.check_box

View file

@ -50,23 +50,27 @@
= "(#{@procedure.duree_conservation_dossiers_dans_ds} mois)"
sera expiré.
= link_to 'En savoir plus', ARCHIVAGE_DOC_URL
.afficher-dossiers-supprimes
= link_to deleted_dossiers_instructeur_procedure_path(@procedure) do
%span.icon.delete
Afficher les dossiers supprimés
- if @statut == 'expirant'
%p.explication-onglet
= t('views.instructeurs.dossiers.tab_explainations.expirant')
- if @filtered_sorted_paginated_ids.present? || @current_filters.count > 0
.flex
.flex-grow
.flex
- if @filtered_sorted_paginated_ids.present? || @current_filters.count > 0
%div
= render partial: "dossiers_filter", locals: { procedure: @procedure, procedure_presentation: @procedure_presentation, current_filters: @current_filters, statut: @statut, filterable_fields_for_select: @filterable_fields_for_select }
.flex-grow
.fr-ml-2w
= render Dossiers::NotifiedToggleComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation)
.flex-grow.text-right
- if @statut == 'archives'
= link_to deleted_dossiers_instructeur_procedure_path(@procedure), class: "fr-link fr-icon-delete-line fr-link--icon-left fr-mr-2w" do
= t('views.instructeurs.dossiers.show_deleted_dossiers')
- if @dossiers_count > 0
.dossiers-export
%span.dossiers-export
= render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count, export_url: method(:download_export_instructeur_procedure_path))
- if @filtered_sorted_paginated_ids.present? || @current_filters.count > 0
- batch_operation_component = Dossiers::BatchOperationComponent.new(statut: @statut, procedure: @procedure)
%div{ data: batch_operation_component.render? ? { controller: 'batch-operation' } : {} }

View file

@ -1,6 +1,8 @@
= turbo_frame_tag "#{dom_id(@prefill_description)}_types_de_champs" do
.fr-grid-row.fr-grid-row--gutters.fr-py-5w
- prefill_description.types_de_champ.each do |type_de_champ|
- prefillable = type_de_champ.prefillable?
.fr-col-md-6.fr-col-12
.card
.card-title.flex.justify-between.align-center
@ -10,7 +12,7 @@
- (prefill_description.selected_type_de_champ_ids - [type_de_champ.id.to_s]).each do |id|
= f.hidden_field :selected_type_de_champ_ids, value: id, multiple: true
= f.submit t("views.prefill_descriptions.edit.champ_remove"), class: 'fr-btn fr-btn--secondary fr-btn--md'
- elsif type_de_champ.prefillable?
- elsif prefillable
- (prefill_description.selected_type_de_champ_ids + [type_de_champ.id.to_s]).each do |id|
= f.hidden_field :selected_type_de_champ_ids, value: id, multiple: true
= f.submit t("views.prefill_descriptions.edit.champ_add"), class: 'fr-btn fr-btn--md'
@ -32,13 +34,13 @@
= t("views.prefill_descriptions.edit.champ_type")
%td
= t("activerecord.attributes.type_de_champ.type_champs.#{type_de_champ.type_champ}")
%tr
%tr{ class: prefillable ? "" : "fr-text-mention--grey" }
%th
= t("views.prefill_descriptions.edit.possible_values.title")
%td
= t("views.prefill_descriptions.edit.possible_values.#{type_de_champ.type_champ}")
%tr
= t("views.prefill_descriptions.edit.possible_values.#{type_de_champ.type_champ}") if prefillable
%tr{ class: prefillable ? "" : "fr-text-mention--grey" }
%th
= t("views.prefill_descriptions.edit.examples.title")
%td
= t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}")
= t("views.prefill_descriptions.edit.examples.#{type_de_champ.type_champ}") if prefillable

View file

@ -1,4 +1,4 @@
- if champ.external_id.present?
= format_text_value("#{champ.external_id} - #{champ}")
= format_text_value("#{champ.external_id} #{champ}")
- else
= format_text_value(champ.to_s)

View file

@ -7,7 +7,7 @@
%h1.new-h1
= t('.contact')
= form_tag contact_path, method: :post, multipart: true, class: 'form' do
= form_tag contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :support } do
.description
%h2= t('.intro_html')
@ -15,59 +15,62 @@
%p.mandatory-explanation= t('asterisk_html', scope: [:utils])
- if !user_signed_in?
.contact-champ
= label_tag :email do
.fr-input-group
= label_tag :email, class: 'fr-label' do
Email
%span.mandatory *
= text_field_tag :email, params[:email], required: true, autocomplete: 'email'
= email_field_tag :email, params[:email], required: true, autocomplete: 'email', class: 'fr-input'
%fieldset.radios.vertical{ name: "type" }
%legend.form-label
%fieldset.fr-fieldset{ name: "type" }
%legend.fr-fieldset__legend
= t('.your_question')
%span.mandatory *
.fr-fieldset__content
- @options.each do |(question, question_type, link)|
.fr-radio-group
= radio_button_tag :type, question_type, false, required: true, data: {"support-target": "inputRadio" }
= label_tag "type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil, class: 'fr-label' } do
= question
- @options.each do |(question, question_type, link)|
= label_tag "type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil } do
= radio_button_tag :type, question_type, false, required: true
= question
- if link.present?
.support.card.featured.mb-4.ml-5.hidden{ id: "card-#{question_type}", "aria-hidden": true }
.card-title
= t('.our_answer')
.card-content
-# i18n-tasks-use t("support.index.#{question_type}.answer_html")
= t('answer_html', scope: [:support, :index, question_type], base_url: APPLICATION_BASE_URL, "link_#{question_type}": link)
- if link.present?
.support.card.featured.mb-4.ml-4.hidden{ id: "card-#{question_type}", "aria-hidden": true , data: { "support-target": "content" } }
.card-title
= t('.our_answer')
.card-content
-# i18n-tasks-use t("support.index.#{question_type}.answer_html")
= t('answer_html', scope: [:support, :index, question_type], base_url: APPLICATION_BASE_URL, "link_#{question_type}": link)
.contact-champ
= label_tag :dossier_id, t('file_number', scope: [:utils])
= text_field_tag :dossier_id, @dossier_id
.fr-input-group
= label_tag :dossier_id, t('file_number', scope: [:utils]), class: 'fr-label'
= text_field_tag :dossier_id, @dossier_id, class: 'fr-input'
.contact-champ
= label_tag :subject do
.fr-input-group
= label_tag :subject, class: 'fr-label' do
= t('subject', scope: [:utils])
%span.mandatory *
= text_field_tag :subject, params[:subject], required: true
= text_field_tag :subject, params[:subject], required: true, class: 'fr-input'
.contact-champ
= label_tag :text do
.fr-input-group
= label_tag :text, class: 'fr-label' do
= t('message', scope: [:utils])
%span.mandatory *
= text_area_tag :text, params[:text], rows: 6, required: true
= text_area_tag :text, params[:text], rows: 6, required: true, class: 'fr-input'
.contact-champ
= label_tag :piece_jointe do
.fr-upload-group
= label_tag :piece_jointe, class: 'fr-label' do
= t('pj', scope: [:utils])
%span.fr-hint-text Taille maximale : 200 Mo. Formats supportés : jpg, png, pdf.
%p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AMELIORATION } }
= t('.notice_pj_product')
%p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AUTRE } }
= t('.notice_pj_other')
= file_field_tag :piece_jointe
= file_field_tag :piece_jointe, class: 'fr-upload', max: 200.megabytes
= hidden_field_tag :tags, @tags&.join(',')
= invisible_captcha
.send-wrapper
= button_tag t('send_mail', scope: [:utils]), type: :submit, class: 'button send primary'
.send-wrapper.fr-my-3w
= button_tag t('send_mail', scope: [:utils]), type: :submit, class: 'fr-btn send'

View file

@ -77,6 +77,8 @@ module TPS
config.ds_opendata_enabled = ENV.fetch('OPENDATA_ENABLED', nil) == 'enabled'
config.ds_zonage_enabled = ENV.fetch("ZONAGE_ENABLED", nil) == "enabled"
config.skylight.probes += [:graphql]
# Custom Configuration

View file

@ -149,6 +149,10 @@ DATAGOUV_API_URL="https://www.data.gouv.fr/api/1"
DATAGOUV_DESCRIPTIF_DEMARCHES_DATASET="datasetid"
DATAGOUV_DESCRIPTIF_DEMARCHES_RESOURCE="resourceid"
# Zonage
ZONAGE_ENABLED='enabled' # zonage disabled by default if `ZONAGE_ENABLED` not set
# SAML
SAML_IDP_CERTIFICATE="idpcertificate"
SAML_IDP_SECRET_KEY="-----BEGIN RSA PRIVATE KEY-----\nblabla+blabla\n-----END RSA PRIVATE KEY-----\n"

View file

@ -80,6 +80,8 @@ Rails.application.configure do
status_visible_duration: 500
}
config.ds_zonage_enabled = true
# BCrypt is slow by design - but during tests we want to make it faster
# to compute hashes of passwords.
silence_warnings do

View file

@ -216,6 +216,7 @@ en:
enabled: "Add this file to the selection for the bulk operation"
disabled: "Impossible to add this file to the selection because it is already in a bulk operation"
personalize: Personalize
show_deleted_dossiers: Show deleted files
follow_file: Follow-up the file
save: Save
stop_follow: No longer follow

View file

@ -210,6 +210,7 @@ fr:
batch_operation:
enabled: "Ajouter ce dossier à la selection pour un traitement de masse"
disabled: "Impossible d'ajouter ce dossier à la selection car il est déjà dans un traitement de masse"
show_deleted_dossiers: Afficher les dossiers supprimés
personalize: Personnaliser
download: Télécharger un dossier
follow_file: Suivre le dossier

View file

@ -1,20 +1,32 @@
fr:
activerecord:
attributes:
mails/received_mail:
subject: Objet de lemail
rich_body: Corps de lemail
errors:
models:
mails/without_continuation_mail:
tags_errors: &tags_errors
champ_missing:
one: contient la balise "%{tags}" qui nexiste pas. Supprimer la balise
other: contient %{count} balises (%{tags}) qui nexistent pas. Supprimer les balises
champ_missing_in_draft_revision:
one: contient la balise "%{tags}" qui a été supprimée mais la suppression nest pas encore publiée. Publier la nouvelle version de la démarche et recommencer
other: contient %{count} balises (%{tags}) qui ont été supprimées mais la suppression nest pas encore publiée. Publier la nouvelle version de la démarche et recommencer
champ_missing_in_published_revision:
one: contient la balise "%{tags}" qui nest pas encore publiée. Publier la nouvelle version de la démarche et recommencer
other: contient %{count} balises (%{tags}) qui ne sont pas encore publiées. Publier la nouvelle version de la démarche et recommencer
champ_missing_in_published_and_draft_revision:
one: contient la balise "%{tags}" qui a été supprimée. Supprimer la balise
other: contient %{count} balises (%{tags}) qui ont été supprimées. Supprimer les balises
champ_missing_in_previous_revision:
one: contient la balise "%{tags}" qui nexiste pas sur un des dossiers en cours de traitement. Supprimer la balise
other: contient %{count} balises (%{tags}) qui nexistent pas sur un des dossiers en cours de traitement. Supprimer les balises
attributes:
subject:
format: Le titre de lemail de notification de classement sans suite de dossier %{message}
champ_missing: réfère au champ "%{tag}" qui nexiste pas
champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression nest pas encore publiée
champ_missing_in_published_revision: réfère au champ "%{tag}" qui nest pas encore publié
champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé
champ_missing_in_previous_revision: réfère au champ "%{tag}" qui nexiste pas sur un des dossiers en cours de traitement
format: Le champ « Objet de lemail » %{message}
<<: *tags_errors
body:
format: Le contenu de lemail de notification de classement sans suite de dossier %{message}
champ_missing: réfère au champ "%{tag}" qui nexiste pas
champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression nest pas encore publiée
champ_missing_in_published_revision: réfère au champ "%{tag}" qui nest pas encore publié
champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé
champ_missing_in_previous_revision: réfère au champ "%{tag}" qui nexiste pas sur un des dossiers en cours de traitement
format: Le champ « Corps de lemail » %{message}
<<: *tags_errors

View file

@ -450,8 +450,8 @@ Rails.application.routes.draw do
collection do
get 'new_from_existing'
post 'search'
get 'all'
get 'administrateurs'
get 'all' if Rails.application.config.ds_zonage_enabled
get 'administrateurs' if Rails.application.config.ds_zonage_enabled
end
member do

View file

@ -1,7 +1,7 @@
namespace :after_party do
desc 'Deployment task: populate_zones'
task populate_zones: :environment do
if Flipper.enabled? :zonage
if Rails.application.config.ds_zonage_enabled
puts "Running deploy task 'populate_zones'"
collectivite = Zone.find_or_create_by!(acronym: 'COLLECTIVITE')
coll_label = collectivite.labels.find_or_initialize_by(designated_on: Date.parse('1977-07-30'))

View file

@ -0,0 +1,24 @@
namespace :after_party do
desc 'Deployment task: schedule_rebase_for_all_dossiers'
task schedule_rebase_for_all_dossiers: :environment do
puts "Running deploy task 'schedule_rebase_for_all_dossiers'"
dossiers = Dossier.joins(:procedure)
.state_not_termine
.state_not_brouillon
.where('revision_id != procedures.published_revision_id')
progress = ProgressReport.new(dossiers.count)
dossiers.find_each do |dossier|
dossier.rebase_later
progress.inc
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
end

View file

@ -0,0 +1,21 @@
namespace :after_party do
desc 'Deployment task: fix_geo_area_missing_source'
task fix_geo_area_missing_source: :environment do
puts "Running deploy task 'fix_geo_area_missing_source'"
geo_areas = GeoArea.where(source: nil)
progress = ProgressReport.new(geo_areas.count)
geo_areas.find_each do |geo_area|
geo_area.source = GeoArea.sources.fetch(:selection_utilisateur)
geo_area.save!
progress.inc
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
end

View file

@ -15,7 +15,7 @@ describe Administrateurs::ProceduresController, type: :controller do
let(:zone_ids) { [zone.id] }
let(:tags) { "[\"planete\",\"environnement\"]" }
describe '#apercu' do
describe '#apercu', vcr: { cassette_name: 'api_geo_all' } do
render_views
let(:procedure) { create(:procedure, :with_all_champs) }

View file

@ -116,6 +116,15 @@ FactoryBot.define do
end
end
trait :with_zone do
zones {
[
create(:zone, labels:
[{ designated_on: Time.zone.now, name: "Ministère 1" }])
]
}
end
trait :routee do
after(:create) do |procedure, _evaluator|
procedure.groupe_instructeurs.create(label: 'deuxième groupe')

79
spec/fixtures/cassettes/api_geo_all.yml vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,41 @@
---
http_interactions:
- request:
method: get
uri: https://geo.api.gouv.fr/regions
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- nginx/1.10.3 (Ubuntu)
Date:
- Tue, 20 Dec 2022 11:55:45 GMT
Content-Type:
- application/json; charset=utf-8
Content-Length:
- '653'
Vary:
- Accept-Encoding
- Origin
X-Powered-By:
- Express
Etag:
- W/"28d-NqjRJu+ph9X/ycpx3D2pjeoeVto"
Strict-Transport-Security:
- max-age=15552000
body:
encoding: ASCII-8BIT
string: !binary |-
W3sibm9tIjoiw45sZS1kZS1GcmFuY2UiLCJjb2RlIjoiMTEifSx7Im5vbSI6IkNlbnRyZS1WYWwgZGUgTG9pcmUiLCJjb2RlIjoiMjQifSx7Im5vbSI6IkJvdXJnb2duZS1GcmFuY2hlLUNvbXTDqSIsImNvZGUiOiIyNyJ9LHsibm9tIjoiTm9ybWFuZGllIiwiY29kZSI6IjI4In0seyJub20iOiJIYXV0cy1kZS1GcmFuY2UiLCJjb2RlIjoiMzIifSx7Im5vbSI6IkdyYW5kIEVzdCIsImNvZGUiOiI0NCJ9LHsibm9tIjoiUGF5cyBkZSBsYSBMb2lyZSIsImNvZGUiOiI1MiJ9LHsibm9tIjoiQnJldGFnbmUiLCJjb2RlIjoiNTMifSx7Im5vbSI6Ik5vdXZlbGxlLUFxdWl0YWluZSIsImNvZGUiOiI3NSJ9LHsibm9tIjoiT2NjaXRhbmllIiwiY29kZSI6Ijc2In0seyJub20iOiJBdXZlcmduZS1SaMO0bmUtQWxwZXMiLCJjb2RlIjoiODQifSx7Im5vbSI6IlByb3ZlbmNlLUFscGVzLUPDtHRlIGQnQXp1ciIsImNvZGUiOiI5MyJ9LHsibm9tIjoiQ29yc2UiLCJjb2RlIjoiOTQifSx7Im5vbSI6Ikd1YWRlbG91cGUiLCJjb2RlIjoiMDEifSx7Im5vbSI6Ik1hcnRpbmlxdWUiLCJjb2RlIjoiMDIifSx7Im5vbSI6Ikd1eWFuZSIsImNvZGUiOiIwMyJ9LHsibm9tIjoiTGEgUsOpdW5pb24iLCJjb2RlIjoiMDQifSx7Im5vbSI6Ik1heW90dGUiLCJjb2RlIjoiMDYifV0=
recorded_at: Tue, 20 Dec 2022 11:55:45 GMT
recorded_with: VCR 6.1.0

676
spec/fixtures/files/pays_dump.json vendored Normal file
View file

@ -0,0 +1,676 @@
[
"ACORES, MADERE",
"Afghanistan",
"AFGHANISTAN",
"Afrique du Sud",
"AFRIQUE DU SUD",
"Åland, Îles",
"ALASKA",
"Albania",
"Albanie",
"ALBANIE",
"Algeria",
"ALGERIE",
"Algérie",
"Allemagne",
"ALLEMAGNE",
"American Samoa",
"Andorra",
"Andorre",
"ANDORRE",
"Angola",
"ANGOLA",
"Anguilla",
"ANGUILLA",
"Antarctique",
"Antigua and Barbuda",
"Antigua-et-Barbuda",
"ANTIGUA-ET-BARBUDA",
"ANTILLES NEERLANDAISES",
"Arabie saoudite",
"ARABIE SAOUDITE",
"Argentina",
"Argentine",
"ARGENTINE",
"Armenia",
"ARMENIE",
"Arménie",
"Aruba",
"ARUBA",
"Australia",
"Australie",
"AUSTRALIE",
"Austria",
"Autriche",
"AUTRICHE",
"AZERBAIDJAN",
"Azerbaïdjan",
"Azerbaijan",
"Bahamas",
"BAHAMAS",
"Bahrain",
"BAHREIN",
"Bahreïn",
"Bangladesh",
"BANGLADESH",
"Barbade",
"BARBADE",
"Barbados",
"Belarus",
"Bélarus",
"Belgique",
"BELGIQUE",
"Belgium",
"Belize",
"BELIZE",
"Benin",
"BENIN",
"Bénin",
"Bermuda",
"Bermudes",
"BERMUDES",
"Bhoutan",
"BHOUTAN",
"Bhutan",
"BIELORUSSIE",
"Birmanie",
"BIRMANIE",
"Bolivia",
"Bolivie",
"BOLIVIE",
"BONAIRE, SAINT EUSTACHE ET SABA",
"Bonaire, Saint-Eustache et Saba",
"Bonaire, Sint Eustatius and Saba",
"Bosnia and Herzegovina",
"BOSNIE-HERZEGOVINE",
"Bosnie-Herzégovine",
"Botswana",
"BOTSWANA",
"Brazil",
"BRESIL",
"Brésil",
"British Indian Ocean Territory",
"Brunei Darussalam",
"Brunéi Darussalam",
"BRUNEI",
"Bulgaria",
"Bulgarie",
"BULGARIE",
"Burkina Faso",
"BURKINA",
"Burundi",
"BURUNDI",
"Cabo Verde",
"CAIMANES (ILES)",
"Cambodge",
"CAMBODGE",
"Cambodia",
"Cameroon",
"CAMEROUN ET TOGO",
"Cameroun",
"CAMEROUN",
"Canada",
"CANADA",
"CANARIES (ILES)",
"Cap-Vert",
"CAP-VERT",
"Cayman Islands",
"CENTRAFRICAINE (REPUBLIQUE)",
"Central African Republic",
"Chad",
"Chile",
"Chili",
"CHILI",
"China",
"Chine",
"CHINE",
"CHRISTMAS (ILE)",
"Christmas, Île",
"Chypre",
"CHYPRE",
"Cocos (Keeling) Islands",
"Cocos (Keeling), Îles",
"Colombia",
"Colombie",
"COLOMBIE",
"Comores",
"COMORES",
"Comoros",
"CONGO (REPUBLIQUE DEMOCRATIQUE)",
"Congo, The Democratic Republic of the",
"Congo",
"CONGO",
"COOK (ILES)",
"COREE (REPUBLIQUE DE)",
"COREE (REPUBLIQUE POPULAIRE DEMOCRATIQUE DE)",
"Corée, République de",
"Corée, République populaire démocratique de",
"COREE",
"Costa Rica",
"COSTA RICA",
"COTE D'IVOIRE",
"Côte d'Ivoire",
"Croatia",
"Croatie",
"CROATIE",
"Cuba",
"CUBA",
"Curaçao",
"CURAÇAO",
"Cyprus",
"Czechia",
"Danemark",
"DANEMARK",
"Denmark",
"Djibouti",
"DJIBOUTI",
"Dominica",
"DOMINICAINE (REPUBLIQUE)",
"Dominican Republic",
"Dominique",
"DOMINIQUE",
"Ecuador",
"Egypt",
"EGYPTE",
"Égypte",
"El Salvador",
"EL SALVADOR",
"EMIRATS ARABES UNIS",
"Émirats arabes unis",
"EQUATEUR",
"Équateur",
"Equatorial Guinea",
"Eritrea",
"ERYTHREE",
"Érythrée",
"Espagne",
"ESPAGNE",
"Estonia",
"Estonie",
"ESTONIE",
"Eswatini",
"ETATS MALAIS NON FEDERES",
"ETATS-UNIS",
"États-Unis",
"Ethiopia",
"ETHIOPIE",
"Éthiopie",
"Falkland Islands (Malvinas)",
"FEROE (ILES)",
"Fidji",
"FIDJI",
"Fiji",
"Finland",
"Finlande",
"FINLANDE",
"France",
"FRANCE",
"French Guiana",
"French Polynesia",
"French Southern Territories",
"Gabon",
"GABON",
"Gambia",
"Gambie",
"GAMBIE",
"Georgia",
"GEORGIE DU SUD ET LES ILES SANDWICH DU SUD",
"Géorgie du Sud et les îles Sandwich du Sud",
"GEORGIE",
"Géorgie",
"Germany",
"Ghana",
"GHANA",
"Gibraltar",
"GIBRALTAR",
"GRECE",
"Grèce",
"Greece",
"Grenada",
"Grenade",
"GRENADE",
"GROENLAND",
"Groënland",
"Guadeloupe",
"GUADELOUPE",
"Guam",
"GUAM",
"Guatemala",
"GUATEMALA",
"Guernesey",
"GUERNESEY",
"Guernsey",
"Guinea",
"GUINEE EQUATORIALE",
"Guinée Équatoriale",
"GUINEE-BISSAU",
"Guinée-Bissau",
"GUINEE",
"Guinée",
"Guyana",
"GUYANA",
"Guyane française",
"GUYANE",
"Haiti",
"HAITI",
"Haïti",
"HAWAII (ILES)",
"HEARD ET MACDONALD (ILES)",
"Holy See (Vatican City State)",
"Honduras",
"HONDURAS",
"Hong Kong",
"HONG-KONG",
"Hongrie",
"HONGRIE",
"Hungary",
"Iceland",
"île Bouvet",
"Île de Man",
"île Norfolk",
"îles Caïmans",
"îles Cook",
"îles Féroé",
"îles Heard-et-MacDonald",
"Îles Mariannes du Nord",
"Îles Marshall",
"Îles mineures éloignées des États-Unis",
"Îles Pitcairn",
"ILES PORTUGAISES DE L'OCEAN INDIEN",
"îles Turques-et-Caïques",
"Îles Vierges britanniques",
"Îles Vierges des États-Unis",
"Inde",
"INDE",
"India",
"Indonesia",
"INDONESIE",
"Indonésie",
"Irak",
"Iran, Islamic Republic of",
"Iran, République islamique d'",
"IRAN",
"Iraq",
"IRAQ",
"Ireland",
"IRLANDE, ou EIRE",
"Irlande",
"Islande",
"ISLANDE",
"Isle of Man",
"Israel",
"ISRAEL",
"Israël",
"Italie",
"ITALIE",
"Italy",
"Jamaica",
"JAMAIQUE",
"Jamaïque",
"Japan",
"Japon",
"JAPON",
"Jersey",
"JERSEY",
"Jordan",
"Jordanie",
"JORDANIE",
"KAMTCHATKA",
"Kazakhstan",
"KAZAKHSTAN",
"Kenya",
"KENYA",
"Kirghizistan",
"KIRGHIZISTAN",
"Korea, Democratic People's Republic of",
"Korea, Republic of",
"Kosovo",
"KOSOVO",
"KOWEIT",
"Koweït",
"Kuwait",
"Kyrgyzstan",
"LA REUNION",
"LABRADOR",
"Lao People's Democratic Republic",
"Lao, République démocratique populaire",
"LAOS",
"Latvia",
"Lebanon",
"Lesotho",
"LESOTHO",
"Lettonie",
"LETTONIE",
"Liban",
"LIBAN",
"Liberia",
"LIBERIA",
"Libéria",
"Libya",
"Libye",
"LIBYE",
"Liechtenstein",
"LIECHTENSTEIN",
"Lithuania",
"Lituanie",
"LITUANIE",
"Luxembourg",
"LUXEMBOURG",
"Macao",
"MACAO",
"Macau",
"MACEDOINE DU NORD (REPUBLIQUE DE)",
"Macédoine du Nord",
"Madagascar",
"MADAGASCAR",
"Malaisie",
"MALAISIE",
"Malawi",
"MALAWI",
"Malaysia",
"Maldives",
"MALDIVES",
"Mali",
"MALI",
"Malouines, Îles (Falkland)",
"MALOUINES, OU FALKLAND (ILES)",
"Malta",
"Malte",
"MALTE",
"MAN (ILE)",
"MARIANNES DU NORD (ILES)",
"Maroc",
"MAROC",
"MARSHALL (ILES)",
"Martinique",
"MARTINIQUE",
"Maurice",
"MAURICE",
"Mauritania",
"Mauritanie",
"MAURITANIE",
"Mauritius",
"Mayotte",
"MAYOTTE",
"Mexico",
"Mexique",
"MEXIQUE",
"Micronésie, États fédérés de",
"Moldavie",
"MOLDAVIE",
"Moldova",
"Monaco",
"MONACO",
"Mongolia",
"Mongolie",
"MONGOLIE",
"Montenegro",
"MONTENEGRO",
"Monténégro",
"MONTSERRAT",
"Morocco",
"Mozambique",
"MOZAMBIQUE",
"Myanmar",
"Namibia",
"Namibie",
"NAMIBIE",
"Nepal",
"NEPAL",
"Népal",
"Netherlands",
"New Caledonia",
"New Zealand",
"Nicaragua",
"NICARAGUA",
"Niger",
"NIGER",
"Nigeria",
"NIGERIA",
"North Macedonia",
"NORVEGE",
"Norvège",
"Norway",
"NOUVELLE-CALEDONIE",
"Nouvelle-Calédonie",
"NOUVELLE-ZELANDE",
"Nouvelle-Zélande",
"OCEAN INDIEN (TERRITOIRE BRITANNIQUE DE L')",
"Oman",
"OMAN",
"Ouganda",
"OUGANDA",
"OUZBEKISTAN",
"Ouzbékistan",
"Pakistan",
"PAKISTAN",
"Palaos",
"PALESTINE (Etat de)",
"Palestine, État de",
"Palestine, State of",
"Panama",
"PANAMA",
"PAPOUASIE-NOUVELLE-GUINEE",
"Papouasie-Nouvelle-Guinée",
"Papua New Guinea",
"Paraguay",
"PARAGUAY",
"Pays-Bas",
"PAYS-BAS",
"PEROU",
"Pérou",
"Peru",
"Philippines",
"PHILIPPINES",
"Poland",
"Pologne",
"POLOGNE",
"POLYNESIE FRANCAISE",
"Polynésie française",
"Porto Rico",
"PORTO RICO",
"Portugal",
"PORTUGAL",
"POSSESSIONS BRITANNIQUES AU PROCHE-ORIENT",
"PROVINCES ESPAGNOLES D'AFRIQUE",
"Puerto Rico",
"Qatar",
"QATAR",
"République centrafricaine",
"REPUBLIQUE DEMOCRATIQUE ALLEMANDE",
"République démocratique du Congo",
"République dominicaine",
"République du Congo",
"REPUBLIQUE FEDERALE D'ALLEMAGNE",
"Réunion, Île de la",
"Réunion",
"Romania",
"Roumanie",
"ROUMANIE",
"Royaume-Uni",
"ROYAUME-UNI",
"Russian Federation",
"Russie, Fédération de",
"RUSSIE",
"Rwanda",
"RWANDA",
"Sahara occidental",
"SAHARA OCCIDENTAL",
"Saint Barthélemy",
"Saint Kitts and Nevis",
"Saint Lucia",
"Saint Martin (French part)",
"Saint Pierre and Miquelon",
"Saint Vincent and the Grenadines",
"SAINT-BARTHELEMY",
"Saint-Barthélemy",
"SAINT-CHRISTOPHE-ET-NIEVES",
"Saint-Christophe-et-Niévès",
"Saint-Marin",
"SAINT-MARIN",
"Saint-Martin (partie française)",
"SAINT-MARTIN (PARTIE NEERLANDAISE)",
"Saint-Martin (partie néerlandaise)",
"SAINT-MARTIN",
"Saint-Pierre-et-Miquelon",
"SAINT-PIERRE-ET-MIQUELON",
"Saint-Siège (état de la cité du Vatican)",
"SAINT-VINCENT-ET-LES GRENADINES",
"Saint-Vincent-et-les-Grenadines",
"SAINTE HELENE, ASCENSION ET TRISTAN DA CUNHA",
"Sainte-Hélène, Ascension et Tristan da Cunha",
"Sainte-Lucie",
"SAINTE-LUCIE",
"SALOMON (ILES)",
"Salomon, Îles",
"Salvador",
"SAMOA AMERICAINES",
"Samoa américaines",
"SAMOA OCCIDENTALES",
"Samoa",
"Sao Tome and Principe",
"SAO TOME-ET-PRINCIPE",
"Sao Tomé-et-Principe",
"Saudi Arabia",
"Senegal",
"SENEGAL",
"Sénégal",
"Serbia",
"Serbie",
"SERBIE",
"Seychelles",
"SEYCHELLES",
"SIBERIE",
"Sierra Leone",
"SIERRA LEONE",
"Singapore",
"Singapour",
"SINGAPOUR",
"Sint Maarten (Dutch part)",
"Slovakia",
"Slovaquie",
"SLOVAQUIE",
"Slovenia",
"SLOVENIE",
"Slovénie",
"Somalia",
"Somalie",
"SOMALIE",
"SOUDAN ANGLO-EGYPTIEN, KENYA, OUGANDA",
"Soudan du Sud",
"SOUDAN DU SUD",
"Soudan",
"SOUDAN",
"South Africa",
"South Georgia and the South Sandwich Islands",
"South Sudan",
"Spain",
"Sri Lanka",
"SRI LANKA",
"Sudan",
"SUEDE",
"Suède",
"Suisse",
"SUISSE",
"Surinam",
"Suriname",
"SURINAME",
"Svalbard and Jan Mayen",
"Svalbard et île Jan Mayen",
"SWAZILAND",
"Sweden",
"Switzerland",
"Syrian Arab Republic",
"SYRIE",
"Syrienne, République arabe",
"Tadjikistan",
"TADJIKISTAN",
"Taiwan",
"TAIWAN",
"Taïwan",
"Tajikistan",
"TANGER",
"Tanzania",
"Tanzanie",
"TANZANIE",
"Tchad",
"TCHAD",
"TCHECOSLOVAQUIE",
"TCHEQUE (REPUBLIQUE)",
"Tchéquie",
"TERR. DES ETATS-UNIS D'AMERIQUE EN AMERIQUE",
"TERR. DES ETATS-UNIS D'AMERIQUE EN OCEANIE",
"TERR. DU ROYAUME-UNI DANS L'ATLANTIQUE SUD",
"TERRE-NEUVE",
"TERRES AUSTRALES FRANCAISES",
"Terres australes françaises",
"Territoire britannique de l'océan Indien",
"TERRITOIRES DU ROYAUME-UNI AUX ANTILLES",
"Thailand",
"THAILANDE",
"Thaïlande",
"Timor oriental",
"TIMOR ORIENTAL",
"Timor-Leste",
"Togo",
"TOGO",
"Tonga",
"TONGA",
"Trinidad and Tobago",
"TRINITE-ET-TOBAGO",
"Trinité-et-Tobago",
"Tunisia",
"Tunisie",
"TUNISIE",
"TURKESTAN RUSSE",
"Turkey",
"Turkmenistan",
"TURKMENISTAN",
"Turkménistan",
"Turks and Caicos Islands",
"TURKS ET CAIQUES (ILES)",
"TURQUIE D'EUROPE",
"Turquie",
"TURQUIE",
"Tuvalu",
"TUVALU",
"Uganda",
"Ukraine",
"UKRAINE",
"United Arab Emirates",
"United Kingdom",
"United States Minor Outlying Islands",
"United States",
"Uruguay",
"URUGUAY",
"Uzbekistan",
"Vanuatu",
"VANUATU",
"VATICAN, ou SAINT-SIEGE",
"Venezuela",
"VENEZUELA",
"Vénézuela",
"VIERGES BRITANNIQUES (ILES)",
"VIERGES DES ETATS-UNIS (ILES)",
"VIET NAM DU NORD",
"VIET NAM DU SUD",
"VIET NAM",
"Viêt Nam",
"Vietnam",
"Virgin Islands, British",
"Virgin Islands, U.S.",
"Wallis et Futuna",
"WALLIS-ET-FUTUNA",
"Western Sahara",
"YEMEN (REPUBLIQUE ARABE DU)",
"YEMEN DEMOCRATIQUE",
"Yemen",
"YEMEN",
"Yémen",
"Zambia",
"Zambie",
"ZAMBIE",
"ZANZIBAR",
"Zimbabwe",
"ZIMBABWE"
]

View file

@ -196,7 +196,7 @@ describe Champ do
end
end
describe '#search_terms' do
describe '#search_terms', vcr: { cassette_name: 'api_geo_all' } do
let(:champ) { type_de_champ.champ.build(value: value) }
subject { champ.search_terms }
@ -247,9 +247,9 @@ describe Champ do
context 'for département champ' do
let(:type_de_champ) { build(:type_de_champ_departements) }
let(:value) { "69 - Rhône" }
let(:value) { "69" }
it { is_expected.to eq([value]) }
it { is_expected.to eq(['69 Rhône']) }
end
context 'for dossier link champ' do
@ -319,9 +319,9 @@ describe Champ do
context 'for pays champ' do
let(:type_de_champ) { build(:type_de_champ_pays) }
let(:value) { "FRANCE" }
let(:value) { "FR" }
it { is_expected.to eq([value]) }
it { is_expected.to eq(['France']) }
end
context 'for phone champ' do
@ -340,9 +340,9 @@ describe Champ do
context 'for region champ' do
let(:type_de_champ) { build(:type_de_champ_regions) }
let(:value) { "Île-de-France" }
let(:value) { "11" }
it { is_expected.to eq([value]) }
it { is_expected.to eq(['Île-de-France']) }
end
context 'for siret champ' do

View file

@ -0,0 +1,64 @@
describe Champs::DepartementChamp, type: :model do
let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) }
before do
allow(Rails).to receive(:cache).and_return(memory_store)
Rails.cache.clear
end
let(:champ) { described_class.new }
describe 'value', vcr: { cassette_name: 'api_geo_departements' } do
it 'with code' do
champ.value = '01'
expect(champ.external_id).to eq('01')
expect(champ.code).to eq('01')
expect(champ.name).to eq('Ain')
expect(champ.value).to eq('Ain')
expect(champ.selected).to eq('01')
expect(champ.to_s).to eq('01 Ain')
end
it 'with nil' do
champ.write_attribute(:value, 'Ain')
champ.write_attribute(:external_id, '01')
champ.value = nil
expect(champ.external_id).to be_nil
expect(champ.code).to be_nil
expect(champ.name).to be_nil
expect(champ.value).to be_nil
expect(champ.selected).to be_nil
expect(champ.to_s).to eq('')
end
it 'with blank' do
champ.write_attribute(:value, 'Ain')
champ.write_attribute(:external_id, '01')
champ.value = ''
expect(champ.external_id).to be_nil
expect(champ.value).to be_nil
expect(champ.selected).to be_nil
expect(champ.to_s).to eq('')
end
it 'with initial nil' do
champ.write_attribute(:value, nil)
expect(champ.external_id).to be_nil
expect(champ.code).to be_nil
expect(champ.name).to be_nil
expect(champ.value).to be_nil
expect(champ.selected).to be_nil
expect(champ.to_s).to eq('')
end
it 'with initial name' do
champ.write_attribute(:value, '01 - Ain')
expect(champ.external_id).to be_nil
expect(champ.code).to eq('01')
expect(champ.name).to eq('Ain')
expect(champ.value).to eq('01 - Ain')
expect(champ.selected).to eq('01')
expect(champ.to_s).to eq('01 Ain')
end
end
end

View file

@ -0,0 +1,71 @@
describe Champs::PaysChamp, type: :model do
let(:champ) { described_class.new }
describe 'value' do
it 'with code' do
champ.value = 'GB'
expect(champ.external_id).to eq('GB')
expect(champ.value).to eq('Royaume-Uni')
expect(champ.selected).to eq('GB')
expect(champ.to_s).to eq('Royaume-Uni')
I18n.with_locale(:en) do
expect(champ.to_s).to eq('United Kingdom')
end
I18n.with_locale(:fr) do
expect(champ.to_s).to eq('Royaume-Uni')
end
end
it 'with name' do
champ.value = 'Royaume-Uni'
expect(champ.external_id).to eq('GB')
expect(champ.value).to eq('Royaume-Uni')
expect(champ.selected).to eq('GB')
expect(champ.to_s).to eq('Royaume-Uni')
end
it 'with nil' do
champ.write_attribute(:value, 'Royaume-Uni')
champ.write_attribute(:external_id, 'GB')
champ.value = nil
expect(champ.external_id).to be_nil
expect(champ.value).to be_nil
expect(champ.selected).to be_nil
expect(champ.to_s).to eq('')
end
it 'with blank' do
champ.write_attribute(:value, 'Royaume-Uni')
champ.write_attribute(:external_id, 'GB')
champ.value = ''
expect(champ.external_id).to be_nil
expect(champ.value).to be_nil
expect(champ.selected).to be_nil
expect(champ.to_s).to eq('')
end
it 'with initial nil' do
champ.write_attribute(:value, nil)
expect(champ.external_id).to be_nil
expect(champ.value).to be_nil
expect(champ.selected).to be_nil
expect(champ.to_s).to eq('')
end
it 'with initial name' do
champ.write_attribute(:value, 'Royaume-Uni')
expect(champ.external_id).to be_nil
expect(champ.value).to eq('Royaume-Uni')
expect(champ.selected).to eq('GB')
expect(champ.to_s).to eq('Royaume-Uni')
end
it 'with initial bad name' do
champ.write_attribute(:value, 'ROYAUME-UNIS')
expect(champ.external_id).to be_nil
expect(champ.value).to eq('ROYAUME-UNIS')
expect(champ.selected).to eq('ROYAUME-UNIS')
expect(champ.to_s).to eq('ROYAUME-UNIS')
end
end
end

View file

@ -0,0 +1,56 @@
describe Champs::RegionChamp, type: :model do
let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) }
before do
allow(Rails).to receive(:cache).and_return(memory_store)
Rails.cache.clear
end
let(:champ) { described_class.new }
describe 'value', vcr: { cassette_name: 'api_geo_regions' } do
it 'with code' do
champ.value = '01'
expect(champ.external_id).to eq('01')
expect(champ.value).to eq('Guadeloupe')
expect(champ.selected).to eq('01')
expect(champ.to_s).to eq('Guadeloupe')
end
it 'with nil' do
champ.write_attribute(:value, 'Guadeloupe')
champ.write_attribute(:external_id, '01')
champ.value = nil
expect(champ.external_id).to be_nil
expect(champ.value).to be_nil
expect(champ.selected).to be_nil
expect(champ.to_s).to eq('')
end
it 'with blank' do
champ.write_attribute(:value, 'Guadeloupe')
champ.write_attribute(:external_id, '01')
champ.value = ''
expect(champ.external_id).to be_nil
expect(champ.value).to be_nil
expect(champ.selected).to be_nil
expect(champ.to_s).to eq('')
end
it 'with initial nil' do
champ.write_attribute(:value, nil)
expect(champ.external_id).to be_nil
expect(champ.value).to be_nil
expect(champ.selected).to be_nil
expect(champ.to_s).to eq('')
end
it 'with initial name' do
champ.write_attribute(:value, 'Guadeloupe')
expect(champ.external_id).to be_nil
expect(champ.value).to eq('Guadeloupe')
expect(champ.selected).to eq('01')
expect(champ.to_s).to eq('Guadeloupe')
end
end
end

View file

@ -72,6 +72,17 @@ describe Dossier do
expect(dossier.pending_changes).not_to be_empty
expect(dossier.can_rebase?).to be_falsey
end
context 'with a value' do
before do
dossier.champs.find_by(type_de_champ: type_de_champ).update(value: 'a value')
end
it 'should be true' do
expect(dossier.pending_changes).not_to be_empty
expect(dossier.can_rebase?).to be_truthy
end
end
end
context 'with type de champ change type' do

View file

@ -17,7 +17,7 @@ RSpec.describe PrefillDescription, type: :model do
describe '#types_de_champ' do
let(:procedure) { create(:procedure) }
let(:type_de_champ) { create(:type_de_champ_text, procedure: procedure) }
let!(:type_de_champ) { create(:type_de_champ_text, procedure: procedure) }
let(:prefill_description) { described_class.new(procedure) }
it { expect(prefill_description.types_de_champ).to match([type_de_champ]) }
@ -32,6 +32,19 @@ RSpec.describe PrefillDescription, type: :model do
it_behaves_like "filters out non fillable types de champ", :type_de_champ_header_section
it_behaves_like "filters out non fillable types de champ", :type_de_champ_explication
context 'when the procedure contains prefillable and non prefillable types de champ' do
let!(:non_prefillable_type_de_champ) { create(:type_de_champ_carte, procedure: procedure) }
let!(:prefillable_type_de_champ) { create(:type_de_champ_decimal_number, procedure: procedure) }
it "sort types de champ by putting prefillable ones first" do
expect(prefill_description.types_de_champ).to eq([
type_de_champ,
prefillable_type_de_champ,
non_prefillable_type_de_champ
])
end
end
end
describe '#include?' do

View file

@ -1243,7 +1243,7 @@ describe Procedure do
describe '.missing_zones?' do
before do
Flipper.enable :zonage
Rails.application.config.ds_zonage_enabled = true
end
let(:procedure) { create(:procedure, zones: []) }

View file

@ -0,0 +1,45 @@
describe APIGeoService do
let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) }
before do
allow(Rails).to receive(:cache).and_return(memory_store)
Rails.cache.clear
end
describe 'pays' do
it 'countrie_code' do
countries = JSON.parse(Rails.root.join('spec/fixtures/files/pays_dump.json').read)
countries_without_code = countries.map { APIGeoService.country_code(_1) }.count(&:nil?)
expect(countries_without_code).to eq(67)
end
describe 'country_name' do
it 'Kosovo' do
expect(APIGeoService.country_code('Kosovo')).to eq('XK')
expect(APIGeoService.country_name('XK')).to eq('Kosovo')
end
it 'Thaïlande' do
expect(APIGeoService.country_code('Thaïlande')).to eq('TH')
expect(APIGeoService.country_name('TH')).to eq('Thaïlande')
end
end
end
describe 'regions', vcr: { cassette_name: 'api_geo_regions' } do
it 'return sorted results' do
expect(APIGeoService.regions.size).to eq(18)
expect(APIGeoService.regions.first).to eq(code: '84', name: 'Auvergne-Rhône-Alpes')
expect(APIGeoService.regions.last).to eq(code: '93', name: 'Provence-Alpes-Côte dAzur')
end
end
describe 'departements', vcr: { cassette_name: 'api_geo_departements' } do
it 'return sorted results' do
expect(APIGeoService.departements.size).to eq(102)
expect(APIGeoService.departements.first).to eq(code: '99', name: 'Etranger')
expect(APIGeoService.departements.second).to eq(code: '01', name: 'Ain')
expect(APIGeoService.departements.last).to eq(code: '976', name: 'Mayotte')
end
end
end

View file

@ -68,8 +68,11 @@ describe ProcedureExportService do
"communes (Code insee)",
"communes (Département)",
"departements",
"departements (Code)",
"regions",
"regions (Code)",
"pays",
"pays (Code)",
"dossier_link",
"piece_justificative",
"rna",
@ -172,8 +175,11 @@ describe ProcedureExportService do
"communes (Code insee)",
"communes (Département)",
"departements",
"departements (Code)",
"regions",
"regions (Code)",
"pays",
"pays (Code)",
"dossier_link",
"piece_justificative",
"rna",
@ -259,8 +265,11 @@ describe ProcedureExportService do
"communes (Code insee)",
"communes (Département)",
"departements",
"departements (Code)",
"regions",
"regions (Code)",
"pays",
"pays (Code)",
"dossier_link",
"piece_justificative",
"rna",

View file

@ -1,4 +1,4 @@
describe 'wcag rules for usager', js: true do
describe 'wcag rules for usager', js: true, vcr: { cassette_name: 'api_geo_all' } do
let(:procedure) { create(:procedure, :published, :with_all_champs, :with_service, :for_individual) }
let(:password) { 'a very complicated password' }
let(:litteraire_user) { create(:user, password: password) }

View file

@ -5,7 +5,7 @@ describe 'As an administrateur, I want to manage the procedures attestation',
let(:administrateur) { create(:administrateur) }
let(:procedure) do
create(:procedure, :with_service, :with_instructeur,
create(:procedure, :with_service, :with_instructeur, :with_zone,
aasm_state: :brouillon,
administrateurs: [administrateur],
libelle: 'libellé de la procédure',

View file

@ -6,7 +6,7 @@ describe 'As an administrateur I wanna clone a procedure', js: true do
let(:administrateur) { create(:administrateur) }
before do
create(:procedure, :with_service, :with_instructeur,
create(:procedure, :with_service, :with_instructeur, :with_zone,
aasm_state: :publiee,
administrateurs: [administrateur],
libelle: 'libellé de la procédure',
@ -29,6 +29,11 @@ describe 'As an administrateur I wanna clone a procedure', js: true do
find("#service .fr-btn").click
click_on "Assigner"
# select zone
find("#zones .fr-btn").click
check Zone.last.current_label
click_on 'Enregistrer'
# then publish
find('#publish-procedure-link').click
expect(find_field('procedure_path').value).to eq 'libelle-de-la-procedure'

View file

@ -10,6 +10,7 @@ describe 'Publishing a procedure', js: true do
:with_path,
:with_type_de_champ,
:with_service,
:with_zone,
instructeurs: instructeurs,
administrateur: administrateur)
end
@ -51,6 +52,7 @@ describe 'Publishing a procedure', js: true do
create(:procedure,
:with_path,
:with_service,
:with_zone,
instructeurs: instructeurs,
administrateur: administrateur,
types_de_champ_public: [{ type: :repetition, libelle: 'Enfants', children: [] }, { type: :drop_down_list, libelle: 'Civilité', options: [] }],

View file

@ -44,7 +44,7 @@ describe 'fetch API Particulier Data', js: true do
context 'when an administrateur is logged in' do
let(:procedure) do
create(:procedure, :with_service, :with_instructeur,
create(:procedure, :with_service, :with_instructeur, :with_zone,
aasm_state: :brouillon,
administrateurs: [administrateur],
libelle: 'libellé de la procédure',

View file

@ -1,43 +1,63 @@
describe "procedure sort" do
describe "procedure sort", js: true do
let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure, :published, :with_type_de_champ, instructeurs: [instructeur]) }
let!(:new_unfollow_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) }
let!(:followed_dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) }
let!(:new_unfollow_dossier_2) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) }
let!(:followed_dossier_2) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) }
before do
instructeur.follow(followed_dossier)
followed_dossier.champs_public.first.update(value: '123')
instructeur.follow(followed_dossier_2)
followed_dossier.champs_public.first.update(value: '123') # touch the dossier
login_as(instructeur.user, scope: :user)
visit instructeur_procedure_path(procedure)
visit instructeur_procedure_path(procedure, statut: "suivis")
end
scenario "should be able to sort with header" do
all(".dossiers-table tbody tr:nth-child(1) .number-col a", text: new_unfollow_dossier_2.id)
all(".dossiers-table tbody tr:nth-child(2) .number-col a", text: followed_dossier.id)
all(".dossiers-table tbody tr:nth-child(3) .number-col a", text: new_unfollow_dossier.id)
# sorted by notifications (updated_at desc) by default, filtered by followed
expect(all(".dossiers-table tbody tr").count).to eq(2)
expect(find(".dossiers-table tbody tr:nth-child(1) .number-col a").text).to eq(followed_dossier.id.to_s)
expect(find(".dossiers-table tbody tr:nth-child(2) .number-col a").text).to eq(followed_dossier_2.id.to_s)
find("thead .number-col a").click # reverse id filter
find("thead .number-col a").click # sort by id asc
all(".dossiers-table tbody tr:nth-child(1) .number-col a", text: new_unfollow_dossier.id)
all(".dossiers-table tbody tr:nth-child(2) .number-col a", text: followed_dossier.id)
all(".dossiers-table tbody tr:nth-child(3) .number-col a", text: new_unfollow_dossier_2.id)
expect(find(".dossiers-table tbody tr:nth-child(1) .number-col a").text).to eq(followed_dossier.id.to_s)
expect(find(".dossiers-table tbody tr:nth-child(2) .number-col a").text).to eq(followed_dossier_2.id.to_s)
find("thead .number-col a").click # reverse order - sort by id desc
expect(find(".dossiers-table tbody tr:nth-child(1) .number-col a").text).to eq(followed_dossier_2.id.to_s)
expect(find(".dossiers-table tbody tr:nth-child(2) .number-col a").text).to eq(followed_dossier.id.to_s)
end
scenario "should be able to sort with direct link to notificaiton filter" do
# dossier sorted by id
check "Remonter les dossiers avec une notification"
scenario "should be able to sort with direct link to notification sort" do
# the real input checkbox is hidden - DSFR set a fake checkbox with a label, so we can't use "check/uncheck" methods
# but we can assert on the hidden checkbox state
expect(page).to have_checked_field("Remonter les dossiers avec une notification")
# sort by notification
all(".dossiers-table tbody tr:nth-child(1) .number-col a", text: followed_dossier.id)
all(".dossiers-table tbody tr:nth-child(2) .number-col a", text: new_unfollow_dossier.id)
all(".dossiers-table tbody tr:nth-child(3) .number-col a", text: new_unfollow_dossier_2.id)
find("label", text: "Remonter les dossiers avec une notification").click # reverse order - sort by updated_at asc
uncheck "Remonter les dossiers avec une notification"
expect(page).not_to have_checked_field("Remonter les dossiers avec une notification")
expect(find(".dossiers-table tbody tr:nth-child(1) .number-col a").text).to eq(followed_dossier_2.id.to_s)
expect(find(".dossiers-table tbody tr:nth-child(2) .number-col a").text).to eq(followed_dossier.id.to_s)
all(".dossiers-table tbody tr:nth-child(1) .number-col a", text: new_unfollow_dossier_2.id)
all(".dossiers-table tbody tr:nth-child(2) .number-col a", text: followed_dossier.id)
all(".dossiers-table tbody tr:nth-child(3) .number-col a", text: new_unfollow_dossier.id)
find("label", text: "Remonter les dossiers avec une notification").click # set order back - sort by updated_at desc
expect(page).to have_checked_field("Remonter les dossiers avec une notification")
expect(find(".dossiers-table tbody tr:nth-child(1) .number-col a").text).to eq(followed_dossier.id.to_s)
expect(find(".dossiers-table tbody tr:nth-child(2) .number-col a").text).to eq(followed_dossier_2.id.to_s)
end
scenario "should be able to sort back by notification filter after any other sort" do
find("thead .number-col a").click # sort by id asc
expect(page).not_to have_checked_field("Remonter les dossiers avec une notification")
find("label", text: "Remonter les dossiers avec une notification").click # sort by updated_at desc
expect(page).to have_checked_field("Remonter les dossiers avec une notification")
expect(find(".dossiers-table tbody tr:nth-child(1) .number-col a").text).to eq(followed_dossier.id.to_s)
expect(find(".dossiers-table tbody tr:nth-child(2) .number-col a").text).to eq(followed_dossier_2.id.to_s)
end
end

View file

@ -1,4 +1,4 @@
describe 'Accessing the /patron page:' do
describe 'Accessing the /patron page:', vcr: { cassette_name: 'api_geo_all' } do
scenario 'I can display a page with all form fields and UI elements' do
visit patron_path
expect(page).to have_text('Icônes')

View file

@ -1,6 +1,6 @@
describe 'The routing', js: true do
let(:password) { 'a very complicated password' }
let(:procedure) { create(:procedure, :with_type_de_champ, :with_service, :for_individual) }
let(:procedure) { create(:procedure, :with_type_de_champ, :with_service, :for_individual, :with_zone) }
let(:administrateur) { create(:administrateur, procedures: [procedure]) }
let(:scientifique_user) { create(:user, password: password) }
let(:litteraire_user) { create(:user, password: password) }

View file

@ -6,7 +6,14 @@ describe 'The user' do
let(:user_dossier) { user.dossiers.first }
let!(:dossier_to_link) { create(:dossier) }
scenario 'fill a dossier', js: true do
let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) }
before do
allow(Rails).to receive(:cache).and_return(memory_store)
Rails.cache.clear
end
scenario 'fill a dossier', js: true, vcr: { cassette_name: 'api_geo_all' } do
log_in(user, procedure)
fill_individual
@ -33,9 +40,9 @@ describe 'The user' do
select_combobox('multiple_choice_drop_down_list_long', 'alp', 'alpha')
select_combobox('multiple_choice_drop_down_list_long', 'cha', 'charly')
select_combobox('pays', 'aust', 'Australie')
select_combobox('regions', 'Ma', 'Martinique')
select_combobox('departements', 'Ai', '02 - Aisne')
select('Australie', from: form_id_for('pays'))
select('Martinique', from: form_id_for('regions'))
select('02 Aisne', from: form_id_for('departements'))
select_combobox('communes', 'Ai', '02 - Aisne', check: false)
select_combobox('communes', 'Ambl', 'Ambléon (01300)')
@ -65,7 +72,7 @@ describe 'The user' do
expect(JSON.parse(champ_value_for('multiple_drop_down_list'))).to match(['val1', 'val3'])
expect(champ_value_for('pays')).to eq('Australie')
expect(champ_value_for('regions')).to eq('Martinique')
expect(champ_value_for('departements')).to eq('02 - Aisne')
expect(champ_value_for('departements')).to eq('Aisne')
expect(champ_value_for('communes')).to eq('Ambléon (01300)')
expect(champ_value_for('dossier_link')).to eq('123')
expect(champ_value_for('piece_justificative')).to be_nil # antivirus hasn't approved the file yet
@ -86,10 +93,10 @@ describe 'The user' do
expect(page).to have_checked_field('val1')
expect(page).to have_checked_field('val3')
expect(page).to have_selected_value('simple_choice_drop_down_list_long', selected: 'bravo')
expect(page).to have_selected_value('pays', selected: 'Australie')
expect(page).to have_selected_value('regions', selected: 'Martinique')
expect(page).to have_selected_value('departements', selected: '02 Aisne')
check_selected_value('multiple_choice_drop_down_list_long', with: ['alpha', 'charly'])
check_selected_value('pays', with: 'Australie')
check_selected_value('regions', with: 'Martinique')
check_selected_value('departements', with: '02 - Aisne')
check_selected_value('communes', with: 'Ambléon (01300)')
expect(page).to have_field('dossier_link', with: '123')
expect(page).to have_text('file.pdf')

View file

@ -3,7 +3,7 @@ describe 'administrateurs/procedures/zones.html.haml' do
let(:populate_zones_task) { Rake::Task['after_party:populate_zones'] }
before do
Flipper.enable(:zonage)
Rails.application.config.ds_zonage_enabled = true
populate_zones_task.invoke
end