diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f33954ca61..9e2c14e703 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -134,7 +134,14 @@ jobs:
path: node_modules
- name: Install JS dependencies
run: yarn install
+ - name: Cache precompiled assets
+ id: cache-precompiled-assets
+ uses: actions/cache@v4
+ with:
+ key: assets-${{ hashFiles('yarn.lock', 'app/javascript/**') }}
+ path: app/assets/builds
- name: Precompile assets
+ if: steps.cache-precompiled-assets.outputs.cache-hit != 'true' # Skip if cache was restored
run: yarn run build
- name: Prepare runtime log cache key
run: ls spec/**/*.rb > tmp/spec_files.txt
@@ -202,7 +209,14 @@ jobs:
path: node_modules
- name: Install JS dependencies
run: yarn install
+ - name: Cache precompiled assets
+ id: cache-precompiled-assets
+ uses: actions/cache@v4
+ with:
+ key: assets-${{ hashFiles('yarn.lock', 'app/javascript/**') }}
+ path: app/assets/builds
- name: Precompile assets
+ if: steps.cache-precompiled-assets.outputs.cache-hit != 'true' # Skip if cache was restored
run: yarn run build
- name: Prepare runtime log cache key
run: "ls spec/features/${{ matrix.dirname }}/**/*.rb > tmp/feature_spec_${{ matrix.dirname }}_files.txt"
diff --git a/Gemfile.lock b/Gemfile.lock
index 01cbdf8d33..a5f376ada5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -711,7 +711,7 @@ GEM
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
uniform_notifier (1.16.0)
- uri (0.13.0)
+ uri (1.0.3)
validate_email (0.1.6)
activemodel (>= 3.0)
mail (>= 2.2.5)
diff --git a/app/controllers/admin/organisations_controller.rb b/app/controllers/admin/organisations_controller.rb
index 9cbdf53fd4..d3813c4d40 100644
--- a/app/controllers/admin/organisations_controller.rb
+++ b/app/controllers/admin/organisations_controller.rb
@@ -2,7 +2,6 @@ class Admin::OrganisationsController < AgentAuthController
respond_to :html, :json
before_action :set_organisation, except: :index
- before_action :follow_unique, only: :index
def index
@organisations_by_territory = policy_scope(current_agent.organisations, policy_scope_class: Agent::OrganisationPolicy::Scope)
@@ -73,11 +72,4 @@ def organisation_params
def new_organisation_params
params.require(:organisation).permit(:name, :territory_id)
end
-
- def follow_unique
- accessible_organisations = policy_scope(Organisation, policy_scope_class: Agent::OrganisationPolicy::Scope)
- return if params[:follow_unique].blank? || accessible_organisations.count != 1
-
- redirect_to admin_organisation_agent_agenda_path(accessible_organisations.first, current_agent)
- end
end
diff --git a/app/controllers/agent_connect_controller.rb b/app/controllers/agent_connect_controller.rb
index 638e959f20..c5d6222ca0 100644
--- a/app/controllers/agent_connect_controller.rb
+++ b/app/controllers/agent_connect_controller.rb
@@ -14,7 +14,7 @@ def auth
redirect_to auth_client.redirect_url(agent_connect_callback_url), allow_other_host: true
end
- def callback
+ def callback # rubocop:disable Metrics/PerceivedComplexity
callback_client = AgentConnectOpenIdClient::Callback.new(
session_state: session.delete(:agent_connect_state),
params_state: params[:state],
@@ -33,6 +33,10 @@ def callback
# voir https://github.com/numerique-gouv/agentconnect-documentation/blob/main/doc_fs/donnees_fournies.md#le-champ-sub
agent = Agent.active.find_by(email: callback_client.user_email)
+ if current_domain.allow_agent_creation_with_agent_connect
+ agent ||= Agent.new(email: callback_client.user_email, password: SecureRandom.base64(32))
+ end
+
if agent
agent.update!(
connected_with_agent_connect: true,
diff --git a/app/controllers/agents/pages_controller.rb b/app/controllers/agents/pages_controller.rb
new file mode 100644
index 0000000000..dfaa24fc15
--- /dev/null
+++ b/app/controllers/agents/pages_controller.rb
@@ -0,0 +1,23 @@
+class Agents::PagesController < AgentAuthController
+ layout "application"
+
+ CONTACT_TEAM_URL = "https://cal.com/forms/937585aa-48a4-4efd-a642-961fad79c9c5".freeze
+
+ def home
+ skip_authorization
+
+ accessible_organisations = policy_scope(Organisation, policy_scope_class: Agent::OrganisationPolicy::Scope)
+
+ if accessible_organisations.count == 1
+ redirect_to admin_organisation_agent_agenda_path(accessible_organisations.first, current_agent)
+ elsif accessible_organisations.count > 1
+ redirect_to admin_organisations_path
+ end
+ end
+
+ private
+
+ def pundit_user
+ AgentContext.new(current_agent)
+ end
+end
diff --git a/app/controllers/super_admins/comptes_controller.rb b/app/controllers/super_admins/comptes_controller.rb
index 9114d98f45..ed92fcb9ca 100644
--- a/app/controllers/super_admins/comptes_controller.rb
+++ b/app/controllers/super_admins/comptes_controller.rb
@@ -1,5 +1,14 @@
module SuperAdmins
class ComptesController < SuperAdmins::ApplicationController
+ def new
+ @agent = Agent.find_by(id: params[:agent_id])
+ if @agent
+ authorize_resource(@agent)
+ end
+
+ super
+ end
+
def create
compte_params[:agent][:invited_by] = current_super_admin
compte = Compte.new(compte_params, current_domain)
@@ -24,7 +33,7 @@ def compte_params
territory: %i[name departement_number],
organisation: %i[name ants_connectable],
lieu: %i[address latitude longitude],
- agent: %i[first_name last_name email service_ids]
+ agent: %i[id first_name last_name email service_ids]
)
end
end
diff --git a/app/form_models/compte.rb b/app/form_models/compte.rb
index 9eb3c0f027..2e091f9b10 100644
--- a/app/form_models/compte.rb
+++ b/app/form_models/compte.rb
@@ -26,10 +26,7 @@ def save!
organisation.save!
lieu.save!
- self.agent = Agent.invite!(@attributes[:agent].merge(
- password: SecureRandom.base64(32),
- roles_attributes: [{ organisation: organisation, access_level: AgentRole::ACCESS_LEVEL_ADMIN }]
- ))
+ self.agent = find_or_invite_agent(organisation)
agent.services.each do |service|
TerritoryService.create!(service: service, territory: territory)
@@ -65,6 +62,23 @@ def to_s
private
+ def find_or_invite_agent(organisation)
+ if @attributes.dig(:agent, :id)
+ Agent.find(@attributes.dig(:agent, :id)).tap do |agent|
+ agent.update(
+ @attributes[:agent].merge(
+ roles_attributes: [{ organisation: organisation, access_level: AgentRole::ACCESS_LEVEL_ADMIN }]
+ )
+ )
+ end
+ else
+ Agent.invite!(@attributes[:agent].merge(
+ roles_attributes: [{ organisation: organisation, access_level: AgentRole::ACCESS_LEVEL_ADMIN }],
+ password: SecureRandom.base64(32)
+ ))
+ end
+ end
+
def create_mairie_motifs!
service = Service.find_by(name: Service::MAIRIE)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 39965623f8..7fb377bfdb 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -113,9 +113,12 @@ def boolean_attribute_tag(object, attribute_name)
boolean_tag(value) { object.class.human_attribute_value(attribute_name, value) }
end
- def object_attribute_tag(object, attribute_name, value = nil)
+ def object_attribute_tag(object, attribute_name, value = :delegate_to_object)
name = object.class.human_attribute_name(attribute_name)
- value ||= object.human_attribute_value(attribute_name)
+
+ if value == :delegate_to_object
+ value = object.human_attribute_value(attribute_name)
+ end
tag.strong(tag.span(name) + tag.span(" : ")) +
tag.span(value.presence || "Non renseigné", class: class_names("text-muted": value.blank?))
diff --git a/app/javascript/stylesheets/administrate/base/_forms.scss b/app/javascript/stylesheets/administrate/base/_forms.scss
index 695b440f86..e2e637fc2d 100644
--- a/app/javascript/stylesheets/administrate/base/_forms.scss
+++ b/app/javascript/stylesheets/administrate/base/_forms.scss
@@ -107,3 +107,7 @@ select {
outline-offset: $focus-outline-offset;
}
}
+
+.form-group {
+ margin: 8px 0;
+}
diff --git a/app/jobs/create_crisp_ticket_job.rb b/app/jobs/create_crisp_ticket_job.rb
index 98f929730b..6f4ee8768f 100644
--- a/app/jobs/create_crisp_ticket_job.rb
+++ b/app/jobs/create_crisp_ticket_job.rb
@@ -32,6 +32,9 @@ def perform(nickname:, email:, phone:, subject:, message:, role:, domain:)
domain,
],
subject:,
+ device: {
+ locales: ["fr"],
+ },
}
)
end
diff --git a/app/models/agent.rb b/app/models/agent.rb
index 216dc9b00e..af0f0e0e06 100644
--- a/app/models/agent.rb
+++ b/app/models/agent.rb
@@ -100,7 +100,7 @@ def timeout_in = 14.days # Used by Devise's :timeoutable
# * it validates :email (the invite_key) specifically with Devise.email_regexp.
validates :first_name, presence: true, unless: -> { allow_blank_name || is_an_intervenant? }
validates :last_name, presence: true, unless: -> { allow_blank_name }
- validates :agent_services, presence: true
+ validates :agent_services, presence: true, unless: -> { roles.none? }
# Hooks
before_destroy :prevent_destroy_if_rdvs
diff --git a/app/models/domain.rb b/app/models/domain.rb
index 62ec2640c7..4823b80c88 100644
--- a/app/models/domain.rb
+++ b/app/models/domain.rb
@@ -16,6 +16,7 @@
:support_email,
:secretariat_email,
:verticale,
+ :allow_agent_creation_with_agent_connect,
keyword_init: true
)
@@ -36,6 +37,7 @@ class Domain
france_connect_enabled: true,
support_email: "support@rdv-solidarites.fr",
verticale: :rdv_solidarites,
+ allow_agent_creation_with_agent_connect: false,
secretariat_email: "secretariat-auto@rdv-solidarites.fr"
# secretariat_email est utilisé comme adresse de "Reply-To" pour les e-mails
# qui contiennent des ICS. Lorsque l'événement ICS est acceptée par le
@@ -58,6 +60,7 @@ class Domain
france_connect_enabled: false,
support_email: "support@rdv-aide-numerique.fr",
verticale: :rdv_aide_numerique,
+ allow_agent_creation_with_agent_connect: false,
secretariat_email: "secretariat-auto@rdv-solidarites.fr"
),
@@ -76,6 +79,7 @@ class Domain
france_connect_enabled: true,
support_email: "support@rdv-service-public.fr",
verticale: :rdv_mairie,
+ allow_agent_creation_with_agent_connect: true,
secretariat_email: "secretariat-auto@rdv-service-public.fr"
),
].freeze
diff --git a/app/services/admin_creates_agent.rb b/app/services/admin_creates_agent.rb
index b077d23c8f..23993464e9 100644
--- a/app/services/admin_creates_agent.rb
+++ b/app/services/admin_creates_agent.rb
@@ -12,6 +12,9 @@ def call
@agent = find_agent
if @agent
+ if @agent.services.none?
+ @agent.update(service_ids: @agent_params[:service_ids])
+ end
add_agent_to_organisations
@warning_message = self.class.check_agent_service(@agent, @agent_params[:service_ids])
elsif @access_level == "intervenant"
diff --git a/app/services/concerns/users/creneaux_wizard_concern.rb b/app/services/concerns/users/creneaux_wizard_concern.rb
index e00586c644..d3b12364cc 100644
--- a/app/services/concerns/users/creneaux_wizard_concern.rb
+++ b/app/services/concerns/users/creneaux_wizard_concern.rb
@@ -1,8 +1,7 @@
module Users::CreneauxWizardConcern
extend ActiveSupport::Concern
- # *** Method that outputs the next step for the user to complete its rdv journey ***
- # *** It is used in #to_partial_path to render the matching partial view ***
+ # *** Method that outputs the current step for the user to complete its rdv journey ***
def current_step
if departement.blank?
:address_selection
@@ -23,10 +22,6 @@ def start_date
query_params[:date]&.to_date || super
end
- def to_partial_path
- "search/#{current_step}"
- end
-
def wizard_after_creneau_selection_path(params)
url_helpers = Rails.application.routes.url_helpers
if @prescripteur
@@ -48,7 +43,7 @@ def unique_motifs_by_name_and_location_type
# Retourne une liste d'organisations et leur prochaine dispo, ordonnées par date de prochaine dispo
def next_availability_by_motifs_organisations
@next_availability_by_motifs_organisations ||= matching_motifs.to_h do |motif|
- [motif.organisation, creneaux_search_for(nil, date_range, motif).next_availability]
+ [motif.organisation, creneaux_search_for(nil, motif).next_availability]
end.compact.sort_by(&:last).to_h
end
@@ -72,7 +67,7 @@ def next_availability_by_lieux
return @next_availability_by_lieux if @next_availability_by_lieux
next_availability_by_lieux = Lieu.with_open_slots_for_motifs(matching_motifs).includes(:organisation).to_h do |lieu|
- next_availability = creneaux_search_for(lieu, date_range, matching_motifs.where(organisation: lieu.organisation).first).next_availability
+ next_availability = creneaux_search_for(lieu, matching_motifs.where(organisation: lieu.organisation).first).next_availability
[lieu, next_availability]
end.compact
diff --git a/app/services/invitation_search_context.rb b/app/services/invitation_search_context.rb
index 4761d01b63..411919c971 100644
--- a/app/services/invitation_search_context.rb
+++ b/app/services/invitation_search_context.rb
@@ -35,20 +35,4 @@ def matching_motifs
Motif.available_for_booking.where(organisation_id: @organisation_ids).joins(:organisation)
)
end
-
- def contactable_organisations
- @contactable_organisations ||= Organisation.where(id: @organisation_ids).contactable
- end
-
- def organisations_emails
- contactable_organisations.where.not(email: [nil, ""]).pluck(:email).join(",")
- end
-
- def motif_category_name
- @motif_category_short_name.present? ? MotifCategory.find_by(short_name: @motif_category_short_name)&.name : nil
- end
-
- private
-
- attr_reader :referent_ids, :lieu_id
end
diff --git a/app/services/merge_users_service.rb b/app/services/merge_users_service.rb
index 71206fa72d..cc80a12fa0 100644
--- a/app/services/merge_users_service.rb
+++ b/app/services/merge_users_service.rb
@@ -25,8 +25,8 @@ def merge_annotations
current_territory = @organisation.territory
annotation_to_merge = @user_to_merge.annotations.find_by(territory: current_territory)
- @user_target.annotate!(annotation_to_merge.content, territory: current_territory)
- annotation_to_merge.destroy!
+ @user_target.annotate!(annotation_to_merge&.content, territory: current_territory)
+ annotation_to_merge&.destroy!
end
def merge_user_attributes
diff --git a/app/services/search_context.rb b/app/services/search_context.rb
index 4bcc242727..0335a6ca0b 100644
--- a/app/services/search_context.rb
+++ b/app/services/search_context.rb
@@ -25,7 +25,7 @@ def available_collective_rdvs
end
def creneaux_search
- creneaux_search_for(lieu, date_range, first_matching_motif)
+ creneaux_search_for(lieu, first_matching_motif)
end
def first_matching_motif
@@ -54,9 +54,7 @@ def filter_motifs(available_motifs)
private
- def referent_ids
- raise NoMethodError
- end
+ attr_reader :referent_ids, :lieu_id
def matching_motifs
raise NoMethodError
@@ -74,11 +72,7 @@ def street_ban_id
raise NoMethodError
end
- def lieu_id
- raise NoMethodError
- end
-
- def creneaux_search_for(lieu, date_range, motif)
+ def creneaux_search_for(lieu, motif)
CreneauxSearch::ForUser.new(
user: @user,
motif: motif,
diff --git a/app/services/users/contactable_organisations.rb b/app/services/users/contactable_organisations.rb
new file mode 100644
index 0000000000..8534ffc3cb
--- /dev/null
+++ b/app/services/users/contactable_organisations.rb
@@ -0,0 +1,18 @@
+class Users::ContactableOrganisations
+ def initialize(organisation_ids, motif_category_short_name)
+ @organisation_ids = organisation_ids
+ @motif_category_short_name = motif_category_short_name
+ end
+
+ def organisations
+ @organisations ||= Organisation.where(id: @organisation_ids).contactable
+ end
+
+ def organisations_emails
+ organisations.where.not(email: [nil, ""]).pluck(:email).join(",")
+ end
+
+ def motif_category_name
+ @motif_category_short_name.present? ? MotifCategory.find_by(short_name: @motif_category_short_name)&.name : nil
+ end
+end
diff --git a/app/services/web_invitation_search_context.rb b/app/services/web_invitation_search_context.rb
index fb5820bab2..9eeb4ec47a 100644
--- a/app/services/web_invitation_search_context.rb
+++ b/app/services/web_invitation_search_context.rb
@@ -1,6 +1,6 @@
class WebInvitationSearchContext < InvitationSearchContext
include Users::CreneauxWizardConcern
- attr_reader :errors, :query_params, :address, :latitude, :longitude
+ attr_reader :errors, :query_params, :address, :latitude, :longitude, :organisation_ids, :motif_category_short_name
def initialize(user:, query_params: {})
super
diff --git a/app/services/web_search_context.rb b/app/services/web_search_context.rb
index b8f450c7ca..77fa1d965e 100644
--- a/app/services/web_search_context.rb
+++ b/app/services/web_search_context.rb
@@ -13,7 +13,6 @@ def initialize(user:, query_params: {})
@user_selected_organisation_id = query_params[:user_selected_organisation_id]
@external_organisation_ids = query_params[:external_organisation_ids]
@motif_id = query_params[:motif_id]
- @motif_category_short_name = query_params[:motif_category_short_name]
@motif_name_with_location_type = query_params[:motif_name_with_location_type]
@service_id = query_params[:service_id]
@lieu_id = query_params[:lieu_id]
@@ -68,8 +67,6 @@ def motif_param_present?
private
- attr_reader :referent_ids, :lieu_id
-
def matching_motifs
@matching_motifs ||= filter_motifs(geo_search.available_motifs)
end
diff --git a/app/views/admin/participations/_user_details.html.slim b/app/views/admin/participations/_user_details.html.slim
index 32df7ec260..9a3edab811 100644
--- a/app/views/admin/participations/_user_details.html.slim
+++ b/app/views/admin/participations/_user_details.html.slim
@@ -11,7 +11,7 @@ div
li= object_attribute_tag(user, :address)
li= object_attribute_tag(user, :email, clickable_user_email(user))
- if current_territory.enable_notes_field?
- li= object_attribute_tag(user, :annotation_content, formatted_user_annotation(user, current_territory).to_s)
+ li= object_attribute_tag(user, :annotation_content, formatted_user_annotation(user, current_territory))
- if current_territory.enable_logement_field
li= object_attribute_tag(user, :logement)
- Territory::SOCIAL_FIELD_TOGGLES.each do |toggle, field_name|
diff --git a/app/views/admin/prescription/search_creneau.html.slim b/app/views/admin/prescription/search_creneau.html.slim
index f858cbf2d3..6272bc1566 100644
--- a/app/views/admin/prescription/search_creneau.html.slim
+++ b/app/views/admin/prescription/search_creneau.html.slim
@@ -9,4 +9,4 @@ h1 Nouveau RDV par prescription
|> Pas d'adresse
= link_to("ajouter une addresse", edit_admin_organisation_user_path(current_organisation, @context.user))
section.container
- = render partial: @context.to_partial_path, locals: { context: @context }
+ = render partial: "search/#{@context.current_step}", locals: { context: @context }
diff --git a/app/views/admin/users/_responsible_information.html.slim b/app/views/admin/users/_responsible_information.html.slim
index d078d854ec..63d51b3a17 100644
--- a/app/views/admin/users/_responsible_information.html.slim
+++ b/app/views/admin/users/_responsible_information.html.slim
@@ -21,7 +21,7 @@ ul.list-unstyled.mb-5
| En attente de confirmation pour #{user.unconfirmed_email}
- if current_territory.enable_notes_field?
- li.mb-2= object_attribute_tag(user, :annotation_content, formatted_user_annotation(user, current_territory).to_s)
+ li.mb-2= object_attribute_tag(user, :annotation_content, formatted_user_annotation(user, current_territory))
- if current_territory.any_social_field_enabled?
h4.card-title.mb-3 Informations sociales
diff --git a/app/views/agents/pages/home.html.slim b/app/views/agents/pages/home.html.slim
new file mode 100644
index 0000000000..a52c2d285f
--- /dev/null
+++ b/app/views/agents/pages/home.html.slim
@@ -0,0 +1,17 @@
+.fr-container.fr-pb-8w
+ h1.fr-h3.fr-pb-2w Bienvenue !
+ p.fr-py-1w Pour commencer, aidez-nous à en savoir plus :
+
+ p.fr-pt-1w.fr-pb-2w
+ | Votre structure utilise déjà #{current_domain.name} et vous souhaitez disposer d’un accès ?
+ br
+ | Vos collègues peuvent vous inviter depuis le menu “Paramètres > Agents > Ajouter un agent”.
+
+ p.fr-py-1w
+ | Votre structure n’utilise pas #{current_domain.name} et vous souhaitez créer un compte ?
+ br
+ | Nous vous invitons à contacter notre équipe. Nous organiserons un temps d’échanges pour vous présenter la solution et créer le compte de votre structure.
+
+ ul.fr-btns-group.fr-btns-group--center.fr-btns-group--inline-md.fr-mb-8w
+ li
+ = link_to "Contacter notre équipe", "https://cal.com/team/rdv-service-public/temps-d-echanges", class: "fr-btn"
diff --git a/app/views/dsfr/rdv_mairie/homepage.html.slim b/app/views/dsfr/rdv_mairie/homepage.html.slim
index 067b1ee4f0..df014efbba 100644
--- a/app/views/dsfr/rdv_mairie/homepage.html.slim
+++ b/app/views/dsfr/rdv_mairie/homepage.html.slim
@@ -1,4 +1,4 @@
-- contact_team_url = "https://cal.com/forms/937585aa-48a4-4efd-a642-961fad79c9c5"
+- contact_team_url = Agents::PagesController::CONTACT_TEAM_URL
.fr-py-8w.rdv-background-color-alt-blue-ecume
.fr-container
@@ -14,7 +14,7 @@
| Éviter les rendez-vous non honorés, gagner du temps au quotidien et améliorer la relation entre les agents et les usagers
ul.fr-btns-group.fr-btns-group--center.fr-btns-group--inline-md.fr-mb-0
li
- = link_to "Contacter notre équipe", contact_team_url, class: "fr-btn fr-mb-0"
+ = link_to "Demander une démo", contact_team_url, class: "fr-btn fr-mb-0"
.fr-container.fr-py-8w
.fr-grid-row.fr-grid-row--gutters.fr-grid-row--center
@@ -417,5 +417,5 @@ hr
h2 Plus de RDV, moins de lapins
p
| Votre agenda vous remerciera.
- .fr-my-4w= link_to "Contacter notre équipe", contact_team_url, class: "fr-btn"
+ .fr-my-4w= link_to "Demander une démo", contact_team_url, class: "fr-btn"
= link_to "Revenir en haut de la page", "#header", class: "fr-link fr-icon-arrow-up-line fr-link--icon-left"
diff --git a/app/views/search/_nothing_to_show_invitation.html.slim b/app/views/search/_nothing_to_show_invitation.html.slim
index 021ee918c2..6571f80ee7 100644
--- a/app/views/search/_nothing_to_show_invitation.html.slim
+++ b/app/views/search/_nothing_to_show_invitation.html.slim
@@ -1,17 +1,19 @@
-- if context.contactable_organisations.present?
- - if context.contactable_organisations.one?
+- contactable_organisations = Users::ContactableOrganisations.new(context.organisation_ids, context.motif_category_short_name)
+
+- if contactable_organisations.organisations.present?
+ - if contactable_organisations.organisations.one?
p Vous pouvez contacter l'organisation pour demander l'ouverture de créneaux.
- else
p Vous pouvez contacter les organisations suivantes pour leur demander d'ouvrir des créneaux.
.row
- - context.contactable_organisations.each do |organisation|
+ - contactable_organisations.organisations.each do |organisation|
= render "nothing_to_show_contactable_organisation", organisation: organisation
p Ou leur envoyer un email en cliquant sur le bouton ci-dessous.
-- email = context.organisations_emails.presence || "support@rdv-insertion.fr"
+- email = contactable_organisations.organisations_emails.presence || "support@rdv-insertion.fr"
= mail_to email,
- subject: "[Problème Invitation. Créneaux Indisponibles, motif : #{context.motif_category_name}]",
- cc: "#{'support@rdv-insertion.fr' unless context.organisations_emails.empty?}",
+ subject: "[Problème Invitation. Créneaux Indisponibles, motif : #{contactable_organisations.motif_category_name}]",
+ cc: "#{'support@rdv-insertion.fr' unless contactable_organisations.organisations_emails.empty?}",
class: "fr-btn fr-btn--icon-left fr-icon-mail-line" do
| Envoyer une demande d'ouverture de créneaux
diff --git a/app/views/search/search_rdv.html.slim b/app/views/search/search_rdv.html.slim
index 138b602eb2..65745866cc 100644
--- a/app/views/search/search_rdv.html.slim
+++ b/app/views/search/search_rdv.html.slim
@@ -9,4 +9,4 @@
- else
.fr-container
section.py-4.fr-col-12.fr-col-lg-10.fr-col-offset-lg-1
- = render @context, context: @context
+ = render partial: "search/#{@context.current_step}", locals: { context: @context }
diff --git a/app/views/static_pages/politique_de_confidentialite.html.slim b/app/views/static_pages/politique_de_confidentialite.html.slim
index e9b9832619..a9de423bd8 100644
--- a/app/views/static_pages/politique_de_confidentialite.html.slim
+++ b/app/views/static_pages/politique_de_confidentialite.html.slim
@@ -162,20 +162,25 @@
th Garanties
tbody
tr
- td Outscale SASU
+ td Scalingo
td Hébergement
td France
- td = link_to "CGV Outscale", "https://fr.outscale.com/wp-content/uploads/2020/10/Outscale-CGV-2020-09.pdf", target: "_blank"
+ td = link_to " Contrat de Gestion des Traitements de Données Personnelles Scalingo", "https://scalingo.com/fr/contrat-gestion-traitements-donnees-personnelles", target: "_blank"
tr
- td Sendinblue
+ td Brevo
td Envoi de mails
td France
- td = link_to "RGPD Brevo", "https://fr.sendinblue.com/rgpd/", target: "_blank"
+ td = link_to "Politique de Confidentialité Brevo", "https://www.brevo.com/fr/legal/privacypolicy/", target: "_blank"
tr
td LinkMobility
td Envoi de SMS
td France
td = link_to "Politique de confidentialité Link Mobility", "https://www.linkmobility.com/fr/legal/politique-de-confidentialite", target: "_blank"
+ tr
+ td Crisp
+ td Service support (email et formulaire de contact)
+ td Pays-Bas
+ td = link_to "Politique de confidentialité Crisp", "https://storage.crisp.chat/public/documents/Crisp%20Privacy%20Policy%20FR.pdf", target: "_blank"
h3 Sécurité et confidentialité des données
diff --git a/app/views/super_admins/agents/show.html.slim b/app/views/super_admins/agents/show.html.slim
index c0becefa68..67960bc7f6 100644
--- a/app/views/super_admins/agents/show.html.slim
+++ b/app/views/super_admins/agents/show.html.slim
@@ -7,8 +7,11 @@ header.main-content__header role="banner"
=> link_to "Inviter", invite_super_admins_agent_path(page.resource), method: :post, class: "button", data: { disable_with: "Veuillez patienter…"} if accessible_action?(page.resource, :invite)
- if sign_in_as_allowed?
=> link_to "Se logger en tant que", sign_in_as_super_admins_agent_path(page.resource), class: "button" if accessible_action?(page.resource, :sign_in_as)
- => link_to(t("administrate.actions.edit_resource", name: page.page_title), [:edit, namespace, page.resource], class: "button") if accessible_action?(page.resource, :edit)
+ => link_to("Modifier", [:edit, namespace, page.resource], class: "button") if accessible_action?(page.resource, :edit)
=> link_to("Migrer", new_super_admins_agent_migration_path(agent_id: page.resource.id), class: "button")
+ - if page.resource.roles.none?
+ = link_to("Ouvrir un compte", new_super_admins_compte_path(agent_id: page.resource.id), class: "button")
+
section.main-content__body
dl
- page.attributes.each do |attribute|
diff --git a/app/views/super_admins/comptes/new.html.slim b/app/views/super_admins/comptes/new.html.slim
index 1d74443e7e..1f541d8868 100644
--- a/app/views/super_admins/comptes/new.html.slim
+++ b/app/views/super_admins/comptes/new.html.slim
@@ -12,13 +12,14 @@ section.main-content__body
p
| Ce formulaire vous permet de créer un territoire, une organisation, et un lieu pour un agent qui en sera admin (généralement la personne référente du projet).
p
- | Un motif d'exemple "Mon premier motif" sera créé, sauf s'il s'agit d'une ouverture de compte pour une mairie.
+ | Des motifs "Suivi de dossier" seront créés par défaut
= simple_form_for([namespace, page.resource], html: { class: "form" }) do |f|
= f.simple_fields_for :territory do |ff|
= ff.input :name, label: "Nom du territoire"
= f.simple_fields_for :organisation do |ff|
- = ff.input :name, label: "Nom de la première organisation et du premier lieu"
+ div[style="margin: 8px 0"]
+ = ff.input :name, label: "Nom de la première organisation et du premier lieu"
div[style="margin: 16px 0 8px 0"]
= ff.input :ants_connectable, as: :boolean, label: "Autoriser le branchement au moteur de recherche de l'ANTS", hint: "En cochant cette case, vous permettrez à cette organisation (probablement une mairie) d'apparaitre sur https://rendezvouspasseport.ants.gouv.fr/"
@@ -32,11 +33,14 @@ section.main-content__body
h2[style="margin: 32px 0 16px 0"] Admin de territoire
= f.simple_fields_for :agent do |ff|
- = ff.input :first_name, label: "Prénom"
- = ff.input :last_name, label: "Nom"
- = ff.input :email, label: "Adresse mail", hint: "Une invitation sera envoyée automatiquement"
- - # rubocop:disable Rails/OutputSafety
- = ff.input :service_ids, label: "Service", collection: Service.all, hint: "Si nécessaire, vous pouvez #{link_to('créer un nouveau service', new_super_admins_service_path, target: :blank)}".html_safe
- - # rubocop:enable Rails/OutputSafety
+ - if @agent
+ p = @agent.full_name
+ p = @agent.email
+ = ff.hidden_field :id, value: @agent.id
+ - else
+ = ff.input :first_name, label: "Prénom"
+ = ff.input :last_name, label: "Nom"
+ = ff.input :email, label: "Adresse mail", hint: "Une invitation sera envoyée automatiquement"
+ = ff.input :service_ids, label: "Service", collection: Service.all, hint: sanitize("Si nécessaire, vous pouvez #{link_to('créer un nouveau service', new_super_admins_service_path, target: :blank)}")
= f.submit
diff --git a/app/views/users/relatives/_form_fields.html.slim b/app/views/users/relatives/_form_fields.html.slim
index ef0cb41ac8..fdf2ef2f1f 100644
--- a/app/views/users/relatives/_form_fields.html.slim
+++ b/app/views/users/relatives/_form_fields.html.slim
@@ -1,9 +1,9 @@
-.form-row
- .col-md-6= f.input :first_name
- .col-md-6= f.input :last_name
+.fr-grid-row.fr-grid-row--gutters.fr-mb-5v
+ .fr-col-md-6= f.dsfr_text_field :first_name
+ .fr-col-md-6= f.dsfr_text_field :last_name
- if f.object.ants_pre_demande_number_required
- = f.input :ants_pre_demande_number, required: true, hint: t("simple_form.hints.user.ants_pre_demande_number_html"), input_html: {style: "text-transform: uppercase;"}
+ = f.dsfr_text_field :ants_pre_demande_number, required: true, hint: t("simple_form.hints.user.ants_pre_demande_number_html"), input_html: {style: "text-transform: uppercase;"}
= f.hidden_field :ants_pre_demande_number_required, value: "true"
- = f.input :ants_meeting_point_id, as: :hidden
+ = f.hidden_field :ants_meeting_point_id
- if current_domain != Domain::RDV_MAIRIE
- = f.input :birth_date, as: :string, input_html: { type: "date" }
+ = f.dsfr_text_field :birth_date, type: :date, input_html: { type: "date" }
diff --git a/app/views/users/relatives/edit.html.slim b/app/views/users/relatives/edit.html.slim
index d769e20acc..b9c786a334 100644
--- a/app/views/users/relatives/edit.html.slim
+++ b/app/views/users/relatives/edit.html.slim
@@ -2,12 +2,12 @@
.card
.card-body
- = simple_form_for @form, url: relative_path(@form.user) do |f|
+ = form_for @form, url: relative_path(@form.user), builder: Dsfr::FormBuilder do |f|
= render "model_errors", model: @form, f: f
= render "form_fields", f: f
.d-flex.justify-content-between
- = link_to "Supprimer", relative_path(@form.user), method: :delete, class: "btn btn-outline-danger", data: { confirm: "Confirmez-vous la suppression de ce proche ?"}
+ = link_to "Supprimer", relative_path(@form.user), method: :delete, class: "fr-btn fr-btn--icon-left fr-btn--secondary fr-icon-delete-line", data: { confirm: "Confirmez-vous la suppression de ce proche ?"}
.d-flex.justify-content-end
- = link_to "Annuler", users_informations_path, class: "btn btn-link"
- = f.button :submit, class: "btn"
+ = link_to "Annuler", users_informations_path, class: "fr-btn fr-btn--tertiary fr-mr-2v"
+ = f.button :submit, class: "fr-btn"
diff --git a/app/views/users/relatives/new.html.slim b/app/views/users/relatives/new.html.slim
index 845f70e181..8cab262e93 100644
--- a/app/views/users/relatives/new.html.slim
+++ b/app/views/users/relatives/new.html.slim
@@ -1,10 +1,11 @@
- content_for(:title) do
h1.rdv-color-white Ajouter un proche
-= simple_form_for @form, url: relatives_path, remote: true, data: { modal: true } do |f|
+= form_for @form, url: relatives_path, remote: true, data: { modal: true }, builder: Dsfr::FormBuilder do |f|
= render "model_errors", model: @form, f: f
= render "form_fields", f: f
+ // Note : on laisse le row et le col pour le moment car on va passer la modale au DSFR dans une PR suivante
.row
.col.rdv-text-align-right
- button.btn.btn-link data-dismiss="modal" type="button" Annuler
- = f.button :submit
+ button.fr-btn.fr-btn--secondary.fr-mr-2v data-dismiss="modal" type="button" Annuler
+ = f.button :submit, class: "fr-btn fr-btn--primary"
diff --git a/app/views/welcome/super_admin.html.slim b/app/views/welcome/super_admin.html.slim
index f1e71d4474..d4fc909b26 100644
--- a/app/views/welcome/super_admin.html.slim
+++ b/app/views/welcome/super_admin.html.slim
@@ -1,15 +1,13 @@
.fr-mb-3w
- .row.justify-content-center.pt-6
- .col-3
- = link_to "Accéder à superadmin", "/omniauth/github", class: "btn btn-primary", method: :post
+ .rdv-text-align-center
+ = link_to "Accéder à superadmin", "/omniauth/github", class: "fr-btn", method: :post
- if Rails.env.development?
- .row.justify-content-center.mt-3
- .col-3
- h5 Liens utiles
- ul
- li = link_to "Letter Opener", letter_opener_web_path
- li = link_to "Mailer Previews", "/rails/mailers"
- li = link_to "SMS Previews", "/lapin/sms_preview"
- li = link_to "Routes", "/rails/info/routes"
- li = link_to "Rails properties", "/rails/info/properties"
+ .fr-col-lg-3.fr-col-offset-lg-5.fr-mt-3w
+ h5 Liens utiles
+ ul
+ li = link_to "Letter Opener", letter_opener_web_path
+ li = link_to "Mailer Previews", "/rails/mailers"
+ li = link_to "SMS Previews", "/lapin/sms_preview"
+ li = link_to "Routes", "/rails/info/routes"
+ li = link_to "Rails properties", "/rails/info/properties"
diff --git a/config/routes.rb b/config/routes.rb
index 9862271b80..fc61ea1de1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -290,7 +290,7 @@
end
end
authenticated :agent do
- root to: "admin/organisations#index", as: :authenticated_agent_root, defaults: { follow_unique: "1" }
+ root to: "agents/pages#home", as: :authenticated_agent_root
end
scope path: "prescripteur", as: "prescripteur", controller: "prescripteur_rdv_wizard" do
diff --git a/db/seeds/ccas.rb b/db/seeds/ccas.rb
index c4e193abdb..e4bb8eaf4b 100644
--- a/db/seeds/ccas.rb
+++ b/db/seeds/ccas.rb
@@ -49,3 +49,18 @@
user.skip_confirmation!
user.save!
+
+# Un agent pour tester l'absence d'orga et de services
+agent = Agent.new(
+ email: "bob-sans-orga@demo.rdv-solidarites.fr",
+ uid: "bob-sans-orga@demo.rdv-solidarites.fr",
+ first_name: "Bob",
+ last_name: "Sans Organisation",
+ password: "Rdvservicepublictest1!",
+ services: [],
+ invitation_accepted_at: 1.day.ago,
+ roles_attributes: [],
+ agent_territorial_access_rights_attributes: []
+)
+agent.skip_confirmation!
+agent.save!
diff --git a/docs/architecture-technique.md b/docs/architecture-technique.md
index 1f59890930..08b96f5333 100644
--- a/docs/architecture-technique.md
+++ b/docs/architecture-technique.md
@@ -85,6 +85,7 @@ Ces choix techniques sont aussi influencés par la culture de la communauté Rub
| Navigateur redirigé par App Rails | API Microsoft | HTTPS (OAuth) | 443 | Amsterdam, Pays-Bas | login.microsoftonline.com |
| App Rails | API dédoublonnage ANTS | HTTPS | 443 | Paris, France | api-coordination.rendezvouspasseport.ants.gouv.fr/api |
| Moteur de recherche ANTS | App Rails | HTTPS | 443 | Paris, France | rdv.anct.gouv.fr/api/ants/availableTimeSlots |
+| App Rails | Crisp | HTTPS | 443 | Amsterdam, Pays-Bas | api.crisp.chat |
##### Webhooks
@@ -111,11 +112,11 @@ plusieurs tables dans la base de données de RDV Insertion.
### Inventaire des dépendances
-| Nom de l’applicatif | Service | Version | Commentaires |
-|---------------------|------------------|-----------|-----------------------------------------------------------------|
-| Serveur web | Rails @ Scalingo | Rails 7 | Voir ci-dessous pour le détail des librairies |
-| BDD métier | PostgreSQL | `14.10.0` | Stockage des données métier, voir [db/schema.rb](/db/schema.rb) |
-| BDD technique | Redis | `7.2.3` | Stockage du cache |
+| Nom de l’applicatif | Service | Version | Commentaires |
+|---------------------|------------------|----------|-----------------------------------------------------------------|
+| Serveur web | Rails @ Scalingo | Rails 7 | Voir ci-dessous pour le détail des librairies |
+| BDD métier | PostgreSQL | `16.6.0` | Stockage des données métier, voir [db/schema.rb](/db/schema.rb) |
+| BDD technique | Redis | `7.2.5` | Stockage du cache |
La liste des librairies Ruby est disponible dans :
- [Gemfile](/Gemfile) pour la liste des dépendances directes et la description de la fonctionnalité de chacune des gems
diff --git a/public/robots.txt b/public/robots.txt
index fd4868ccc6..ee34469857 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,4 +1,11 @@
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
User-agent: *
-Disallow: /prendre_rdv_prescripteur/
-Disallow: /prescripteur/
+Disallow: /
+Allow: /$
+Allow: /mds
+Allow: /accessibility
+Allow: /mentions_legales
+Allow: /cgu
+Allow: /politique_de_confidentialite
+Allow: /domaines
+Allow: /aide
diff --git a/spec/factories/agent.rb b/spec/factories/agent.rb
index c9917a1967..6ccb6d5a8d 100644
--- a/spec/factories/agent.rb
+++ b/spec/factories/agent.rb
@@ -12,8 +12,17 @@
transient do
service { build(:service) }
+ no_services { false }
+
+ trait :no_services do
+ no_services { true }
+ end
end
after(:build) do |agent, evaluator|
+ next if evaluator.no_services
+ next if agent.agent_services.any?
+ next if agent.services.any?
+
if agent.agent_services.empty? && agent.services.empty?
agent.services = if evaluator.service
[evaluator.service]
diff --git a/spec/features/agents/account/agent_can_reset_his_password_spec.rb b/spec/features/agents/account/agent_can_reset_his_password_spec.rb
index 25d0b76e6d..db782ac20a 100644
--- a/spec/features/agents/account/agent_can_reset_his_password_spec.rb
+++ b/spec/features/agents/account/agent_can_reset_his_password_spec.rb
@@ -22,7 +22,6 @@
fill_in "Mot de passe", with: "correct H0rse battery! staple"
expect { click_on "Enregistrer" }.to change { agent.reload.encrypted_password }
expect(page).to have_content("Votre mot de passe a été édité avec succès")
- expect(page).to have_link("Vos organisations")
end
it "works when using the user's password reset form" do
@@ -39,6 +38,5 @@
fill_in "Mot de passe", with: "correct H0rse battery! staple"
expect { click_on "Enregistrer" }.to change { agent.reload.encrypted_password }
expect(page).to have_content("Votre mot de passe a été édité avec succès")
- expect(page).to have_link("Vos organisations")
end
end
diff --git a/spec/features/agents/agent_can_see_users_rdv_spec.rb b/spec/features/agents/agent_can_see_users_rdv_spec.rb
index 90991ad2b0..ae0f41afe6 100644
--- a/spec/features/agents/agent_can_see_users_rdv_spec.rb
+++ b/spec/features/agents/agent_can_see_users_rdv_spec.rb
@@ -1,27 +1,26 @@
RSpec.describe "can see users' RDV" do
- let!(:organisation) { create(:organisation) }
- let!(:service) { create(:service) }
- let!(:agent) { create(:agent, basic_role_in_organisations: [organisation], service: service) }
- let!(:user) { create(:user, first_name: "Tanguy", last_name: "Laverdure", organisations: [organisation]) }
- let!(:motif) { create(:motif, organisation: organisation, service: service) }
-
- before do
- login_as(agent, scope: :agent)
- visit authenticated_agent_root_path
- end
-
context "with no RDV" do
- before do
- visit admin_organisation_user_path(organisation, user)
- end
+ let!(:organisation) { create(:organisation) }
+ let!(:service) { create(:service) }
+ let!(:agent) { create(:agent, basic_role_in_organisations: [organisation], service: service) }
+ let!(:user) { create(:user, first_name: "Tanguy", last_name: "Laverdure", organisations: [organisation]) }
+ let!(:motif) { create(:motif, organisation: organisation, service: service) }
it do
+ login_as(agent, scope: :agent)
+ visit admin_organisation_user_path(organisation, user)
expect(page).to have_content("À venir\n0 RDV")
expect(page).to have_content("aucun RDV")
end
end
context "with one RDV" do
+ let!(:organisation) { create(:organisation) }
+ let!(:service) { create(:service) }
+ let!(:agent) { create(:agent, basic_role_in_organisations: [organisation], service: service) }
+ let!(:user) { create(:user, first_name: "Tanguy", last_name: "Laverdure", organisations: [organisation]) }
+ let!(:motif) { create(:motif, organisation: organisation, service: service) }
+
let!(:rdv) { create :rdv, :future, users: [user], organisation: organisation, motif: motif, agents: [agent] }
before do
@@ -29,11 +28,12 @@
create(:rdv, :past, status: :excused, users: [user], organisation: organisation, motif: motif, agents: [agent])
create(:rdv, :past, status: :revoked, users: [user], organisation: organisation, motif: motif, agents: [agent])
create(:rdv, :past, status: :noshow, users: [user], organisation: organisation, motif: motif, agents: [agent])
-
- visit admin_organisation_user_path(organisation, user)
end
it do
+ login_as(agent, scope: :agent)
+ visit admin_organisation_user_path(organisation, user)
+
expect(page).to have_content("Excusé\n1 RDV")
expect(page).to have_content("Vu\n1 RDV")
expect(page).to have_content("Annulé par un agent\n1 ")
@@ -45,4 +45,33 @@
expect(page).to have_content("Le #{I18n.l(rdv.starts_at, format: :human)} (durée : #{rdv.duration_in_min} minutes)")
end
end
+
+ describe "displaying annotations" do
+ let!(:territory) { create(:territory, enable_notes_field: true) }
+ let!(:organisation) { create(:organisation, territory:) }
+ let!(:responsible) { create(:user, organisations: [organisation]) }
+ let!(:relative) { create(:user, organisations: [organisation], responsible:) }
+
+ let!(:agent) { create(:agent, basic_role_in_organisations: [organisation]) }
+
+ it "works" do
+ login_as(agent, scope: :agent)
+ visit admin_organisation_user_path(organisation, relative)
+ # On visite pour vérifier que la page ne crash pas en l'absence d'annotation, voir #5133.
+ expect(page).to have_content("Informations de votre proche")
+
+ responsible.annotate!("Ce responsable est très responsable", territory:)
+ relative.annotate!("Ce proche est très proche", territory:)
+
+ # Sur la page du proche, on affiche les remarques du responsable ET du proche
+ visit admin_organisation_user_path(organisation, relative)
+ expect(page).to have_content("Ce responsable est très responsable")
+ expect(page).to have_content("Ce proche est très proche")
+
+ # Sur la page du responsable, on affiche uniquement la remarque du responsable
+ visit admin_organisation_user_path(organisation, responsible)
+ expect(page).to have_content("Ce responsable est très responsable")
+ expect(page).not_to have_content("Ce proche est très proche")
+ end
+ end
end
diff --git a/spec/services/admin_creates_agent_spec.rb b/spec/services/admin_creates_agent_spec.rb
new file mode 100644
index 0000000000..4437927ff6
--- /dev/null
+++ b/spec/services/admin_creates_agent_spec.rb
@@ -0,0 +1,27 @@
+RSpec.describe AdminCreatesAgent do
+ context "when inviting an agent that doesn't have any services" do
+ let(:agent) do
+ create(:agent, :no_services, organisations: [])
+ end
+ let(:service1) { create(:service) }
+ let(:service2) { create(:service) }
+ let(:organisation) do
+ create(:organisation)
+ end
+ let(:admin) do
+ create(:agent, admin_role_in_organisations: [organisation])
+ end
+
+ it "adds the services to the agent" do
+ described_class.new(
+ agent_params: { email: agent.email, service_ids: [service1.id, service2.id] },
+ current_agent: admin,
+ organisations: [organisation],
+ access_level: :basic
+ ).call
+
+ expect(agent.reload.services).to contain_exactly(service1, service2)
+ expect(agent.organisations).to eq [organisation]
+ end
+ end
+end
diff --git a/spec/services/merge_users_service_spec.rb b/spec/services/merge_users_service_spec.rb
index 444fed6f72..3d3f4947ae 100644
--- a/spec/services/merge_users_service_spec.rb
+++ b/spec/services/merge_users_service_spec.rb
@@ -4,8 +4,8 @@
# defaults
let!(:organisation) { create(:organisation) }
let(:attributes_to_merge) { [] }
- let(:user_target) { create(:user, organisations: [organisation]) }
- let(:user_to_merge) { create(:user, organisations: [organisation]) }
+ let!(:user_target) { create(:user, organisations: [organisation]) }
+ let!(:user_to_merge) { create(:user, organisations: [organisation]) }
context "simply merge first_name" do
let(:user_target) { create(:user, first_name: "Jean", last_name: "PAUL", email: "jean@paul.fr", organisations: [organisation]) }
@@ -179,8 +179,8 @@
let!(:organisation2) { create(:organisation) }
let!(:agent1) { create(:agent, basic_role_in_organisations: [organisation]) }
let!(:agent2) { create(:agent, basic_role_in_organisations: [organisation2]) }
- let(:user_target) { create(:user, referent_agents: [agent1], organisations: [organisation]) }
- let(:user_to_merge) { create(:user, referent_agents: [agent2], organisations: [organisation, organisation2]) }
+ let!(:user_target) { create(:user, referent_agents: [agent1], organisations: [organisation]) }
+ let!(:user_to_merge) { create(:user, referent_agents: [agent2], organisations: [organisation, organisation2]) }
it "does not move the agent from the other orga anything" do
perform
@@ -249,6 +249,46 @@
end
end
+ context "only user to merge has an annotation" do
+ before do
+ user_to_merge.annotations.create!(territory: organisation.territory, content: "user to merge")
+ end
+
+ it "deletes the annotation along with the merged user by default" do
+ expect { perform }.to change(Annotation, :count).by(-1).and(change(User, :count).by(-1))
+ expect(user_target.annotations.find_by(territory: organisation.territory)).to be_nil
+ end
+
+ context "when merging annotations" do
+ let(:attributes_to_merge) { [:annotation_content] }
+
+ it "moves the annotation to the target user" do
+ perform
+ expect(user_target.annotation_for(organisation.territory)).to eq("user to merge")
+ end
+ end
+ end
+
+ context "only target user has an annotation" do
+ before do
+ user_target.annotations.create!(territory: organisation.territory, content: "target user")
+ end
+
+ it "keeps the annotation by default" do
+ expect { perform }.not_to change(Annotation, :count)
+ expect(user_target.annotation_for(organisation.territory)).to eq("target user")
+ end
+
+ context "when merging annotations" do
+ let(:attributes_to_merge) { [:annotation_content] }
+
+ it "deletes the annotation of the target user, to replace it with with the absence of annotation of the user to merge" do
+ expect { perform }.to change(Annotation, :count).by(-1)
+ expect(user_target.annotations.find_by(territory: organisation.territory)).to be_nil
+ end
+ end
+ end
+
context "when one user is connected by FranceConnect" do
it "keep FranceConnect attributes when merged user logged once with franceconnect" do
user_to_merge = create(:user, logged_once_with_franceconnect: true, franceconnect_openid_sub: "unechainedecharacteres", organisations: [organisation])