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/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 7bb4888865..896abc8262 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -12,7 +12,7 @@ class Admin::UsersController < AgentAuthController family_situation number_of_children notify_by_sms notify_by_email case_number address_details - notes logement ants_pre_demande_number notification_email + logement ants_pre_demande_number notification_email ].freeze PERMITTED_NESTED_ATTRIBUTES = { @@ -51,7 +51,7 @@ def create prepare_create authorize(@user, policy_class: Agent::UserPolicy) @user.skip_confirmation_notification! - user_persisted = @user_form.save + user_persisted = @user_form.save(annotation_content: params.dig(:user, :annotation_content), current_territory:) if invite_user?(@user, params) @user.invite!(domain: current_domain) @@ -83,7 +83,7 @@ def update @user.assign_attributes(user_params) @user_form = user_form_object @user.skip_reconfirmation! if @user.encrypted_password.blank? - user_updated = @user_form.save + user_updated = @user_form.save(annotation_content: params.dig(:user, :annotation_content), current_territory:) if from_modal? respond_modal_with @user_form, location: modal_return_location elsif user_updated 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/static_pages_controller.rb b/app/controllers/static_pages_controller.rb index b4824f5223..2cfe2af22d 100644 --- a/app/controllers/static_pages_controller.rb +++ b/app/controllers/static_pages_controller.rb @@ -1,7 +1,10 @@ class StaticPagesController < ApplicationController def mds - redirect_to root_path unless current_domain == Domain::RDV_SOLIDARITES - render layout: "application_base" + if current_domain == Domain::RDV_SOLIDARITES + render layout: "application_base" + else + redirect_to root_path + end end def accessibility; 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/admin/user_form.rb b/app/form_models/admin/user_form.rb index e8877c8139..312776a411 100644 --- a/app/form_models/admin/user_form.rb +++ b/app/form_models/admin/user_form.rb @@ -20,8 +20,15 @@ def valid? super && user.valid? # order is important here end - def save - valid? && user.save + def save(annotation_content:, current_territory:) + return false unless valid? + + user.transaction do + if user.save + user.annotate!(annotation_content, territory: current_territory) + true + end + end end private 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/form_models/merge_users_form.rb b/app/form_models/merge_users_form.rb index 3512999537..b928cac5c7 100644 --- a/app/form_models/merge_users_form.rb +++ b/app/form_models/merge_users_form.rb @@ -92,7 +92,11 @@ def attributes_to_merge end def values_for(attribute) - [user1&.send(attribute), user2&.send(attribute)] + if attribute == :annotation_content + [user1.annotation_for(@organisation.territory), user2.annotation_for(@organisation.territory)] + else + [user1&.send(attribute), user2&.send(attribute)] + end end def different_users? 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/helpers/users_helper.rb b/app/helpers/users_helper.rb index 5b5d521c3d..20dc22be12 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -116,8 +116,9 @@ def clickable_user_phone_number(user) user.responsible_phone_number.present? ? link_to(user.responsible_phone_number, "tel:#{user.responsible_or_self.phone_number_formatted}") : nil end - def formatted_user_notes(user) - user.notes.present? ? simple_format(user.notes) : nil + def formatted_user_annotation(user, current_territory) + annotation = user.annotation_for(current_territory) + annotation.present? ? simple_format(annotation) : nil end def user_soft_delete_confirm_message(user) @@ -180,10 +181,10 @@ def default_service_selection_from(source) :responsible end - def user_merge_attribute_value(user, attribute) + def user_merge_attribute_value(user, attribute, current_territory) return birth_date_and_age(user) if attribute == :birth_date return user.responsible&.full_name if attribute == :responsible_id - return formatted_user_notes(user) if attribute == :notes + return formatted_user_annotation(user, current_territory) if attribute == :annotation_content return user&.human_attribute_value(:logement) if attribute == :logement user.send(attribute) 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/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/annotation.rb b/app/models/annotation.rb index bb77f8a0a4..040f31a30c 100644 --- a/app/models/annotation.rb +++ b/app/models/annotation.rb @@ -4,4 +4,14 @@ class Annotation < ApplicationRecord validates :content, presence: true validates :territory_id, uniqueness: { scope: :user_id } + + def self.upsert!(content, user:, territory:) + if content.present? + annotation = find_or_initialize_by(user:, territory:) + annotation.content = content + annotation.save! + else + find_by(user:, territory:)&.destroy! + end + end end 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/models/territory.rb b/app/models/territory.rb index b95999f201..4185b7d82d 100644 --- a/app/models/territory.rb +++ b/app/models/territory.rb @@ -91,7 +91,7 @@ class Territory < ApplicationRecord }.freeze OPTIONAL_FIELD_TOGGLES = { - enable_notes_field: :notes, + enable_notes_field: :annotation_content, enable_logement_field: :logement, }.merge(SOCIAL_FIELD_TOGGLES).freeze diff --git a/app/models/user.rb b/app/models/user.rb index af60a46a1b..d5b05deb15 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,5 @@ class User < ApplicationRecord - self.ignored_columns += %i[invitations_count] + self.ignored_columns += %i[invitations_count notes] # Mixins has_paper_trail( @@ -77,8 +77,6 @@ def self.search_options # Hooks before_save :set_email_to_null_if_blank before_save :clear_notification_email_if_email_present - after_create :create_annotations # backfill temporaire, première étape de migration - after_update :sync_annotations # backfill temporaire, première étape de migration # Scopes default_scope { where(deleted_at: nil) } @@ -265,6 +263,14 @@ def cleanup_annotations annotations.where.not(territory: territories.reload).each(&:destroy!) end + def annotation_for(territory) + annotations.find_by(territory: territory)&.content + end + + def annotate!(content, territory:) + Annotation.upsert!(content, user: self, territory:) + end + protected def generate_rdv_invitation_token @@ -298,30 +304,6 @@ def set_email_to_null_if_blank self.email = nil if email.blank? end - def create_annotations - return if notes.blank? - - territories.distinct.map.each do |territory| - annotation = annotations.find_or_initialize_by(territory:) - annotation.content = notes - annotation.save! - end - end - - def sync_annotations - return unless notes_previously_changed? - - if notes.present? - territories.distinct.map.each do |territory| - annotation = annotations.find_or_initialize_by(territory:) - annotation.content = notes - annotation.save! - end - else - annotations.destroy_all - end - end - def clear_notification_email_if_email_present self.notification_email = nil if email.present? end 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 868d27e719..71206fa72d 100644 --- a/app/services/merge_users_service.rb +++ b/app/services/merge_users_service.rb @@ -8,6 +8,7 @@ def initialize(user_target, user_to_merge, attributes_to_merge, organisation) def perform User.transaction do + merge_annotations if @attributes_to_merge.delete(:annotation_content) merge_user_attributes merge_organisations merge_rdvs @@ -20,6 +21,14 @@ def perform private + 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! + end + def merge_user_attributes @attributes_to_merge.each do |attribute| @user_target.send("#{attribute}=", @user_to_merge.send(attribute)) 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/merge_users/new.html.slim b/app/views/admin/merge_users/new.html.slim index cbe51784ba..9737f40946 100644 --- a/app/views/admin/merge_users/new.html.slim +++ b/app/views/admin/merge_users/new.html.slim @@ -27,8 +27,8 @@ td= render "user_selection", user: @merge_users_form.user2, attribute: "user2_id", f: f, other_user_id: @merge_users_form.user1&.id - @merge_users_form.available_attributes.each do |attribute| - - value1 = user_merge_attribute_value(@merge_users_form.user1, attribute) if @merge_users_form.user1 - - value2 = user_merge_attribute_value(@merge_users_form.user2, attribute) if @merge_users_form.user2 + - value1 = user_merge_attribute_value(@merge_users_form.user1, attribute, current_territory) if @merge_users_form.user1 + - value2 = user_merge_attribute_value(@merge_users_form.user2, attribute, current_territory) if @merge_users_form.user2 tr class=@merge_users_form.attribute_comparison(attribute) td= User.human_attribute_name(attribute) diff --git a/app/views/admin/participations/_user_card.html.slim b/app/views/admin/participations/_user_card.html.slim index 1413ecef4a..4754ac4e18 100644 --- a/app/views/admin/participations/_user_card.html.slim +++ b/app/views/admin/participations/_user_card.html.slim @@ -3,11 +3,11 @@ .card-body.p-2 .mb-1 = render "admin/users/identity", user: participation.user - - notes = participation.user.notes - - if current_territory.enable_notes_field? && notes.present? + - annotation = participation.user.annotation_for(current_territory) + - if current_territory.enable_notes_field? && annotation.present? .participation_notes i.fa.fa-info-circle.text-primary-blue.mr-2 - = simple_format(notes, {}, wrapper_tag: :span) + = simple_format(annotation, {}, wrapper_tag: :span) div = render "admin/participations/notifications_email", participation: participation div diff --git a/app/views/admin/participations/_user_details.html.slim b/app/views/admin/participations/_user_details.html.slim index 3ee4fcbd91..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, :notes, formatted_user_notes(user)) + 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/rdvs/_users_table.html.slim b/app/views/admin/rdvs/_users_table.html.slim index 5331b7c896..22bbc41524 100644 --- a/app/views/admin/rdvs/_users_table.html.slim +++ b/app/views/admin/rdvs/_users_table.html.slim @@ -2,7 +2,7 @@ table.table.light-gray-table thead tr - / Fixed width because of user profile notes field that could messed up the table. + / Fixed width because of user profile annotation field that could messed up the table. th style="max-width: 30%" Participant - if rdv.requires_ants_predemande_number? th =t("activerecord.attributes.user.ants_pre_demande_number") diff --git a/app/views/admin/rdvs/print/_user_details.html.slim b/app/views/admin/rdvs/print/_user_details.html.slim index 568963ede5..2f9d5fb487 100644 --- a/app/views/admin/rdvs/print/_user_details.html.slim +++ b/app/views/admin/rdvs/print/_user_details.html.slim @@ -1,6 +1,6 @@ div= object_attribute_tag(user, :address) - if current_territory.enable_notes_field? - div= object_attribute_tag(user, :notes) + div= object_attribute_tag(user, :annotation_content, user.annotation_for(current_territory).to_s) - if current_territory.enable_logement_field div= object_attribute_tag(user, :logement) - Territory::SOCIAL_FIELD_TOGGLES.each do |toggle, field_name| diff --git a/app/views/admin/users/_form.html.slim b/app/views/admin/users/_form.html.slim index d0809b372c..d95ec8e828 100644 --- a/app/views/admin/users/_form.html.slim +++ b/app/views/admin/users/_form.html.slim @@ -41,7 +41,7 @@ - unless current_domain == Domain::RDV_MAIRIE = date_input(f, :birth_date, **input_opts[:relative]) - if current_territory.enable_notes_field? - = f.input :notes, **input_opts[:relative].deep_merge(input_html: { rows: 6 }) + = f.input :annotation_content, **input_opts[:relative].deep_merge(input_html: { rows: 6, value: user.annotation_for(current_territory) }) - if user.new_record? .form-row.mb-2 *div_opts[:relative] diff --git a/app/views/admin/users/_responsible_form_fields.html.slim b/app/views/admin/users/_responsible_form_fields.html.slim index b6487275b6..7121889b83 100644 --- a/app/views/admin/users/_responsible_form_fields.html.slim +++ b/app/views/admin/users/_responsible_form_fields.html.slim @@ -60,7 +60,7 @@ = f.input :case_number, **input_opts - if current_territory.enable_notes_field - = f.input :notes, **input_opts.deep_merge(input_html: { rows: 6 }) + = f.input :annotation_content, **input_opts.deep_merge(input_html: { rows: 6, value: user.annotation_for(current_territory)} ) - if current_territory.enable_caisse_affiliation_field || current_territory.enable_affiliation_number_field .form-row diff --git a/app/views/admin/users/_responsible_information.html.slim b/app/views/admin/users/_responsible_information.html.slim index 7662636e98..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, :notes, formatted_user_notes(user)) + 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/admin/users/show.html.slim b/app/views/admin/users/show.html.slim index e6b9f8cb00..ab8fee8577 100644 --- a/app/views/admin/users/show.html.slim +++ b/app/views/admin/users/show.html.slim @@ -48,7 +48,7 @@ li= object_attribute_tag(@user, :ants_pre_demande_number) li= object_attribute_tag(@user, :birth_date, birth_date_and_age(@user)) - if current_territory.enable_notes_field? - li= object_attribute_tag(@user, :notes, formatted_user_notes(@user)) + li= object_attribute_tag(@user, :annotation_content, formatted_user_annotation(@user, current_territory)) .row.mt-3 .col.text-left 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/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/anonymizer.yml b/config/anonymizer.yml index 498fa2d8a6..a09e2e740b 100644 --- a/config/anonymizer.yml +++ b/config/anonymizer.yml @@ -72,7 +72,6 @@ tables: - invitation_limit - reset_password_sent_at - invitation_sent_at - - invitations_count - invited_by_id - invited_by_type - invited_through @@ -99,7 +98,6 @@ tables: - microsoft_graph_token - refresh_microsoft_graph_token - remember_created_at - - inclusion_connect_open_id_sub non_anonymized_column_names: - reset_password_sent_at - last_sign_in_at @@ -111,7 +109,6 @@ tables: - invitation_limit - invited_by_type - invited_by_id - - invitations_count - provider - rdv_notifications_level - allow_password_change @@ -246,7 +243,6 @@ tables: - invitation_limit - invited_by_type - invited_by_id - - invitations_count - status - created_by_id - created_by_type diff --git a/config/locales/models/user.fr.yml b/config/locales/models/user.fr.yml index 82de097cd1..fbf871e027 100644 --- a/config/locales/models/user.fr.yml +++ b/config/locales/models/user.fr.yml @@ -23,7 +23,7 @@ fr: notify_by_sms: "Accepte les notifications par SMS" notify_by_email: "Accepte les notifications par email" created_through: "Origine du compte" - notes: Remarques + annotation_content: Remarques ants_pre_demande_number: &ants_pre_demande_number_label Numéro de pré-demande ANTS user/logements: sdf: Sans domicile fixe 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/migrate/20250227165927_remove_invitations_count_columns.rb b/db/migrate/20250227165927_remove_invitations_count_columns.rb new file mode 100644 index 0000000000..7da6b14e32 --- /dev/null +++ b/db/migrate/20250227165927_remove_invitations_count_columns.rb @@ -0,0 +1,17 @@ +class RemoveInvitationsCountColumns < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + def change + safety_assured do + remove_index :agents, :invitations_count, algorithm: :concurrently + remove_index :users, :invitations_count, algorithm: :concurrently + + remove_column :agents, :invitations_count, :integer, default: 0 + remove_column :users, :invitations_count, :integer, default: 0 + remove_column :participations, :invitations_count, :integer, default: 0 + + remove_index :agents, :inclusion_connect_open_id_sub, unique: true, where: "inclusion_connect_open_id_sub IS NOT NULL", algorithm: :concurrently + remove_column :agents, :inclusion_connect_open_id_sub, :string + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 2e9e9f5e38..485ee60c67 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_02_25_184048) do +ActiveRecord::Schema[7.1].define(version: 2025_02_27_165927) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "pgcrypto" @@ -209,7 +209,6 @@ t.integer "invitation_limit" t.string "invited_by_type" t.bigint "invited_by_id" - t.integer "invitations_count", default: 0 t.datetime "deleted_at" t.string "email_original" t.string "provider", default: "email", null: false @@ -229,16 +228,13 @@ t.text "refresh_microsoft_graph_token" t.boolean "outlook_disconnect_in_progress", default: false, null: false t.datetime "account_deletion_warning_sent_at", comment: "Quand le compte de l'agent est inactif depuis bientôt deux ans, on lui envoie un mail qui le prévient que sont compte sera bientôt supprimé, et qu'il doit se connecter à nouveau s'il souhaite conserver son compte. On enregistre la date d'envoi de cet email ici pour s'assure qu'on lui laisse un délai d'au moins un mois pour réagir.\n" - t.string "inclusion_connect_open_id_sub" t.boolean "connected_with_agent_connect", default: false, null: false t.index ["account_deletion_warning_sent_at"], name: "index_agents_on_account_deletion_warning_sent_at" t.index ["calendar_uid"], name: "index_agents_on_calendar_uid", unique: true t.index ["confirmation_token"], name: "index_agents_on_confirmation_token", unique: true t.index ["email"], name: "index_agents_on_email", unique: true, where: "(email IS NOT NULL)" t.index ["external_id"], name: "index_agents_on_external_id", unique: true - t.index ["inclusion_connect_open_id_sub"], name: "index_agents_on_inclusion_connect_open_id_sub", unique: true, where: "(inclusion_connect_open_id_sub IS NOT NULL)" t.index ["invitation_token"], name: "index_agents_on_invitation_token", unique: true - t.index ["invitations_count"], name: "index_agents_on_invitations_count" t.index ["invited_by_id"], name: "index_agents_on_invited_by_id" t.index ["invited_by_type", "invited_by_id"], name: "index_agents_on_invited_by_type_and_invited_by_id" t.index ["last_name"], name: "index_agents_on_last_name" @@ -534,7 +530,6 @@ t.integer "invitation_limit" t.string "invited_by_type" t.bigint "invited_by_id" - t.integer "invitations_count", default: 0 t.enum "status", default: "unknown", null: false, enum_type: "rdv_status" t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -780,7 +775,6 @@ t.integer "invitation_limit" t.string "invited_by_type" t.bigint "invited_by_id" - t.integer "invitations_count", default: 0 t.integer "caisse_affiliation" t.string "affiliation_number" t.integer "family_situation" @@ -815,7 +809,6 @@ t.index ["first_name"], name: "index_users_on_first_name" t.index ["franceconnect_openid_sub"], name: "index_users_on_franceconnect_openid_sub", where: "(franceconnect_openid_sub IS NOT NULL)" t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true - t.index ["invitations_count"], name: "index_users_on_invitations_count" t.index ["invited_by_id"], name: "index_users_on_invited_by_id" t.index ["invited_by_type", "invited_by_id"], name: "index_users_on_invited_by_type_and_invited_by_id" t.index ["last_name"], name: "index_users_on_last_name" 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/db/seeds/rdv_insertion.rb b/db/seeds/rdv_insertion.rb index 905c2cd91c..30134d54f0 100644 --- a/db/seeds/rdv_insertion.rb +++ b/db/seeds/rdv_insertion.rb @@ -341,7 +341,7 @@ # !!! Le lien d'invitation est disponible dans la note des users # jean.rsavalence@testinvitation.fr et jean.rsaAuxerre@testinvitation.fr - user.update!(notes: link) + user.annotate!(link, territory: organisation.territory) end rdv_insertion_logo_base64 = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgaWQ9IkNhbHF1ZV8xIgogICB2aWV3Qm94PSIwIDAgNDgzLjMzIDQ4My4zMyIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNDgzLjMyOTk5IgogICBoZWlnaHQ9IjQ4My4zMjk5OSIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzMSI+CiAgICA8c3R5bGUKICAgICAgIGlkPSJzdHlsZTEiPi5jbHMtMXtmaWxsOiNlYzRjNGM7fS5jbHMtMntmaWxsOiMwODNiNjY7fTwvc3R5bGU+CiAgPC9kZWZzPgogIDxnCiAgICAgaWQ9ImcxOCI+CiAgICA8ZwogICAgICAgaWQ9ImcxOSIKICAgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMC45MTcyMTMsLTAuMDAzMDUwMDYpIj4KICAgICAgPHBhdGgKICAgICAgICAgY2xhc3M9ImNscy0yIgogICAgICAgICBkPSJtIDE5My45Nyw0MTEuMzkgOC4wMywtMS45NyBjIDAsMCAxOS4xNiwtNy42OSAyNC41NCwtMzYuNzEgNS4zOCwtMjkuMDEgMzAuNTMsLTQ4LjU2IDU4LjY2LC00MC4yNyAyMC4yNCwtMi44OCA1LjQyLC0zMy45NyAtMjUuNjYsLTI1LjU0IC0yNS42OCw2Ljk3IC0zOC42NCwxMi45NiAtNDguNywxMC4wOCAtMy4zMSwtMC45NiAtNS44MSwtMy42NyAtNi41MSwtNy4wNCAtOC4zOCwtNDAuNyAtMjUuMTcsLTEyMS45NiAtMjcuMTcsLTEyOC45NiAtMi40LC04LjM2IC03LjkzLC0xMC44IC0xMy45MiwtOS42MyAtNy4wMywwLjg5IC03LjQ4LDguMTkgLTcuNjMsMTIuOTEgMCw2LjA2IDQuMDksNTQuMTggNi4yOCw3OS40NyAtMS45MiwtNy45NyAtNi4zOSwtMTUuODkgLTE2LjYsLTE1LjAxIC0xMC40OCwwLjg4IC0xMi4zNSwxMy44MSAtMTIuMTEsMjQuMjUgLTIuMTIsLTguMjIgLTcuNDQsLTE2LjQ5IC0yMC42NiwtMTIuNDkgLTQuNjQsMS4zOSAtNi43OCw4LjE1IC03LjUxLDE2Ljc2IGwgLTAuMzIsLTAuNDcgLTI3LjA0LC00MS4yMyBDIDY1LjIzLDIxNi41OSA3MC41MSwxOTEuMTcgODkuNDYsMTc4Ljc0IEwgMjYyLjQ3LDY1LjI4IGMgMTUuNDMsLTEwLjEzIDM1LjU5LC03LjMxIDQ3LjczLDUuNzkgbCAtNC44OCwxLjIgYyAwLDAgLTE5LjIxLDcuNzIgLTI0LjYxLDM2Ljg3IC01LjM5LDI5LjEzIC0zMC42LDQ4Ljc2IC01OC43OSw0MC40MyAtMjAuMjksMi45IC01LjQ0LDM0LjEyIDI1LjcxLDI1LjY2IDI1Ljc2LC03LjAxIDM4Ljc1LC0xMy4wMyA0OC44NSwtMTAuMTEgMy4zLDAuOTUgNS44LDMuNjYgNi41LDcuMDQgOC4zOSw0MC44NiAyNS4yMywxMjIuNDkgMjcuMjMsMTI5LjUxIDIuNDEsOC40MSA3Ljk1LDEwLjg2IDEzLjk2LDkuNjcgNy4wNSwtMC45IDcuNSwtOC4yMiA3LjY0LC0xMi45NyAwLC02LjA4IC00LjEsLTU0LjQgLTYuMjksLTc5LjggMS45Myw4LjAxIDYuNCwxNS45NSAxNi42NCwxNS4wOCAxMC41MSwtMC44OSAxMi4zOCwtMTMuODcgMTIuMTQsLTI0LjM2IDIuMTIsOC4yNSA3LjQ2LDE2LjU2IDIwLjcyLDEyLjU1IDMuNzEsLTEuMTIgNS44MiwtNS42NyA2LjksLTExLjg1IGwgMC40MSwwLjYzIDYuMjIsOS40OCB2IDAgYyAwLDAgMTguOTYsMjguOTEgMTguOTYsMjguOTEgMTIuNDMsMTguOTQgNy4xNCw0NC4zNyAtMTEuODEsNTYuOCBMIDI0NS4zMSw0MTcuNTUgYyAtMTUuODcsMTAuNDEgLTM2LjM2LDguMTkgLTQ5LjY1LC00LjE5IC0wLjY2LC0wLjYxIC0xLjMsLTEuMjUgLTEuOTIsLTEuOTEgeiIKICAgICAgICAgaWQ9InBhdGgxNSIgLz4KICAgICAgPGcKICAgICAgICAgaWQ9ImcxNyI+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBjbGFzcz0iY2xzLTEiCiAgICAgICAgICAgZD0ibSAyNTIuMzIsMjM4LjY5IC0zOS4wNCwtMzAuMzYgaCA4MS4wOCBsIC0zOS4wNCwzMC4zNiBjIC0wLjg4LDAuNjkgLTIuMTIsMC42OSAtMywwIHoiCiAgICAgICAgICAgaWQ9InBhdGgxNiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGNsYXNzPSJjbHMtMSIKICAgICAgICAgICBkPSJtIDI5OS45NywyMTMuNDYgdiA0OS45NCBjIDAsNi40IC01LjE5LDExLjU5IC0xMS41OSwxMS41OSBoIC02OS4xMyBjIC02LjQsMCAtMTEuNTksLTUuMTkgLTExLjU5LC0xMS41OSB2IC00OS45NCBjIDAsLTAuMjQgMC4wMiwtMC40OCAwLjA1LC0wLjcyIGwgNDIuMTMsMzIuNzcgYyAyLjMzLDEuODEgNS42LDEuODEgNy45MywwIGwgNDIuMTMsLTMyLjc3IGMgMC4wNCwwLjI0IDAuMDUsMC40OCAwLjA1LDAuNzMgeiIKICAgICAgICAgICBpZD0icGF0aDE3IiAvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K" # rubocop:disable Layout/LineLength 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/factories/user.rb b/spec/factories/user.rb index 5d5e843577..97fa806761 100644 --- a/spec/factories/user.rb +++ b/spec/factories/user.rb @@ -21,7 +21,6 @@ affiliation_number { "39012093812038" } family_situation { "divorced" } number_of_children { 12 } - notes { nil } logement { :locataire } responsible { nil } created_through { "user_sign_up" } 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_list_rdvs_spec.rb b/spec/features/agents/agent_can_list_rdvs_spec.rb index 363531404d..cf04d5b33d 100644 --- a/spec/features/agents/agent_can_list_rdvs_spec.rb +++ b/spec/features/agents/agent_can_list_rdvs_spec.rb @@ -11,6 +11,18 @@ def user_profile_path(user) login_as(current_agent, scope: :agent) end + it "displays user info for each RDV" do + organisation.territory.update!(enable_notes_field: true) + motif = create(:motif, service: current_agent.services.first, organisation:) + create(:rdv, organisation: organisation, agents: [current_agent], users: [user], motif: motif) + user.annotate!("Ma remarque", territory: organisation.territory) + + visit admin_organisation_rdvs_url(organisation, current_agent) + expect(page).to have_content(user.full_name) + expect(page).to have_content(motif.name) + expect(page.html).to include("Ma remarque") + end + describe "RDV visibility within organisation" do let!(:agent_from_same_service) { create(:agent, organisations: [organisation], service: current_agent.services.first) } let!(:agent_from_other_service) { create(:agent, organisations: [organisation]) } 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/features/agents/users/agent_can_create_user_spec.rb b/spec/features/agents/users/agent_can_create_user_spec.rb index 76f4db48bb..17a17cc1b8 100644 --- a/spec/features/agents/users/agent_can_create_user_spec.rb +++ b/spec/features/agents/users/agent_can_create_user_spec.rb @@ -1,5 +1,6 @@ RSpec.describe "Agent can create user" do - let!(:organisation) { create(:organisation, name: "MDS des Champs") } + let!(:organisation) { create(:organisation, name: "MDS des Champs", territory: territory) } + let!(:territory) { create(:territory, enable_notes_field: true) } let!(:agent) { create(:agent, basic_role_in_organisations: [organisation]) } let!(:user) do create(:user, first_name: "Jean", last_name: "LEGENDE", email: "jean@legende.com", organisations: [organisation]) @@ -18,8 +19,13 @@ it "works" do fill_in :user_first_name, with: "Marco" fill_in :user_last_name, with: "Lebreton" + fill_in "Remarques", with: "souhaite participer au prochain atelier collectif" click_button "Créer" expect_page_title("Marco LEBRETON") + + user = User.last + expect(user.annotation_for(organisation.territory)).to eq "souhaite participer au prochain atelier collectif" + expect(page).to have_no_content("Inviter") within("#spec-primary-user-card") { click_link "Modifier" } fill_in "Email", with: "marco@lebreton.bzh" diff --git a/spec/features/agents/users/agent_can_merge_users_spec.rb b/spec/features/agents/users/agent_can_merge_users_spec.rb index d1db8373dd..354c8fd4e6 100644 --- a/spec/features/agents/users/agent_can_merge_users_spec.rb +++ b/spec/features/agents/users/agent_can_merge_users_spec.rb @@ -1,6 +1,6 @@ RSpec.describe "Agent can delete user" do let!(:organisation) { create(:organisation, territory: territory) } - let!(:territory) { create(:territory, enable_logement_field: true) } + let!(:territory) { create(:territory, enable_logement_field: true, enable_notes_field: true) } let!(:agent) { create(:agent, basic_role_in_organisations: [organisation]) } let!(:user1) do create( @@ -61,4 +61,19 @@ expect(page).to have_content("Les usagers ont été fusionnés") expect(page).to have_content("aalyah@damn.com") end + + it "allows to merge annotations" do + Annotation.create!(user: user1, territory: territory, content: "Notes 1") + Annotation.create!(user: user2, territory: territory, content: "Notes 2") + + login_as(agent, scope: :agent) + visit new_admin_organisation_merge_users_path(organisation_id: organisation.id, user1_id: user1.id, user2_id: user2.id) + expect(page).to have_content("Notes 1") + expect(page).to have_content("Notes 2") + choose "Notes 2" + click_on "Fusionner" + expect(user1.reload.annotations.sole.content).to eq("Notes 2") + expect(user2.reload.deleted_at).to be_present + expect(user2.reload.annotations).to be_empty + end end diff --git a/spec/features/agents/users/agent_can_update_user_spec.rb b/spec/features/agents/users/agent_can_update_user_spec.rb index 6f3398a162..b079958813 100644 --- a/spec/features/agents/users/agent_can_update_user_spec.rb +++ b/spec/features/agents/users/agent_can_update_user_spec.rb @@ -11,10 +11,10 @@ login_as(agent, scope: :agent) visit authenticated_agent_root_path visit admin_organisation_user_path(organisation, user) - within("#spec-primary-user-card") { click_link "Modifier" } end it "update existing user's email" do + within("#spec-primary-user-card") { click_link "Modifier" } fill_in :user_first_name, with: "jeanne" fill_in :user_last_name, with: "reynolds" fill_in "Email", with: "jeanne@reynolds.com" @@ -33,6 +33,7 @@ let(:territory) { create(:territory, enable_notes_field: false, enable_caisse_affiliation_field: false) } it "doesn't show them them" do + within("#spec-primary-user-card") { click_link "Modifier" } expect(page).not_to have_content("Remarques") expect(page).not_to have_content("Caisse d'affiliation") end @@ -40,13 +41,30 @@ context "when they are enabled" do let(:territory) { create(:territory, enable_notes_field: true, enable_caisse_affiliation_field: true) } + let!(:annotation_in_current_territory) do + Annotation.create!(user: user, territory: territory, content: "Remarques du territoire courant") + end + let!(:annotation_in_other_territory) do + Annotation.create!(user: user, territory: other_territory, content: "Remarques de l'autre territoire") + end + let(:other_territory) { create(:territory) } + + before do + # On ajoute l'usager dans un autre territoire pour vérifier que ses annotations sont mises à jour dans un seul territoire + create(:user_profile, user: user, organisation: create(:organisation, territory: other_territory)) + end + + it "update user annotations" do + within("#spec-primary-user-card") { click_link "Modifier" } + expect(page).to have_field("Remarques", with: "Remarques du territoire courant") - it "update user notes" do fill_in "Remarques", with: "souhaite participer au prochain atelier collectif" select "MSA", from: "Caisse d'affiliation" click_button "Enregistrer" - expect(user.reload.notes).to eq "souhaite participer au prochain atelier collectif" + expect(user.reload.annotation_for(territory)).to eq "souhaite participer au prochain atelier collectif" expect(user.reload.caisse_affiliation).to eq "msa" + expect(user.reload.annotation_for(other_territory)).to eq "Remarques de l'autre territoire" + expect(page).to have_content("souhaite participer au prochain atelier collectif") end end end @@ -57,6 +75,7 @@ end it "add email to existing user" do + within("#spec-primary-user-card") { click_link "Modifier" } fill_in "Email", with: "jean@legende.com" click_button "Enregistrer" click_link "Inviter" @@ -70,6 +89,7 @@ let!(:user) { create(:user, :unconfirmed, organisations: [organisation]) } it "permet de désactiver et réactiver les préférences de notifications SMS et email" do + within("#spec-primary-user-card") { click_link "Modifier" } expect(page).to have_content("Modifier l'usager") expect(page).to have_checked_field("Accepte les notifications par email") expect(page).to have_checked_field("Accepte les notifications par SMS") diff --git a/spec/form_models/admin/user_form_spec.rb b/spec/form_models/admin/user_form_spec.rb index dfa44ce61e..e7ffcf5b78 100644 --- a/spec/form_models/admin/user_form_spec.rb +++ b/spec/form_models/admin/user_form_spec.rb @@ -17,7 +17,7 @@ it "saves the user" do expect(user).to receive(:save) - subject.save + subject.save(annotation_content: "", current_territory: organisation.territory) end end @@ -33,7 +33,7 @@ it "does not save the user" do expect(user).not_to receive(:save) - subject.save + subject.save(annotation_content: "", current_territory: organisation.territory) end end @@ -52,7 +52,7 @@ it "does not save the user" do expect(user).not_to receive(:save) - subject.save + subject.save(annotation_content: "", current_territory: organisation.territory) end end @@ -70,7 +70,7 @@ it "does not save the user" do expect(user).not_to receive(:save) - subject.save + subject.save(annotation_content: "", current_territory: organisation.territory) end end @@ -87,7 +87,7 @@ it "saves the user" do expect(user).to receive(:save) - subject.save + subject.save(annotation_content: "", current_territory: organisation.territory) end end @@ -110,7 +110,7 @@ it "does not save the user" do expect(user).not_to receive(:save) - subject.save + subject.save(annotation_content: "", current_territory: organisation.territory) end end @@ -130,7 +130,7 @@ it "saves the user" do expect(user).to receive(:save) - subject.save + subject.save(annotation_content: "", current_territory: organisation.territory) end end diff --git a/spec/models/annotation_spec.rb b/spec/models/annotation_spec.rb new file mode 100644 index 0000000000..ee604825c8 --- /dev/null +++ b/spec/models/annotation_spec.rb @@ -0,0 +1,32 @@ +RSpec.describe Annotation do + describe "upsert!" do + let!(:organisation) { create(:organisation) } + let!(:territory) { organisation.territory } + let!(:user) { create(:user, organisations: [organisation]) } + + context "when no annotation already exists for user and territory" do + it "creates one when upserting content" do + expect { described_class.upsert!("Lorem ipsum", user:, territory:) }.to change(described_class, :count).by(1) + end + + it "does nothing when upserting a blank content" do + expect { described_class.upsert!(" ", user:, territory:) }.not_to change { described_class.where(user:, territory:).count } + end + end + + context "when an annotation already exists for user and territory" do + before do + described_class.create!(content: "Lorem ipsum", user:, territory:) + end + + it "updates the existing annotation when upserting content" do + annotation = described_class.where(user:, territory:).sole + expect { described_class.upsert!("dolor sit amet", user:, territory:) }.to change { annotation.reload.content }.from("Lorem ipsum").to("dolor sit amet") + end + + it "deletes the existing annotation when upserting a blank content" do + expect { described_class.upsert!(" ", user:, territory:) }.to change { described_class.where(user:, territory:).count }.by(-1) + end + end + end +end diff --git a/spec/models/concerns/human_attribute_value_spec.rb b/spec/models/concerns/human_attribute_value_spec.rb index e934f460fd..953f11e492 100644 --- a/spec/models/concerns/human_attribute_value_spec.rb +++ b/spec/models/concerns/human_attribute_value_spec.rb @@ -64,8 +64,8 @@ def self.human_attribute_name(key, _options) context "string value ends with a dot (.)" do it "works" do - expect(User.new(notes: "C'est ainsi.").human_attribute_value(:notes)).to eq("C'est ainsi.") - expect(User.new(notes: "Hélas...").human_attribute_value(:notes)).to eq("Hélas...") + expect(User.new(last_name: "C'est ainsi.").human_attribute_value(:last_name)).to eq("C'est ainsi.") + expect(User.new(last_name: "Hélas...").human_attribute_value(:last_name)).to eq("Hélas...") end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a3835c507d..4235dc8fd1 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -366,68 +366,38 @@ end describe "annotations" do - describe "user creation" do - it "creates an annotation if needed" do - organisation = create(:organisation) - user_with_notes = create(:user, notes: "Lorem ipsum", organisations: [organisation]) - expect(user_with_notes.notes).to eq("Lorem ipsum") - expect(Annotation.where(user: user_with_notes).sole).to have_attributes(territory: organisation.territory, content: "Lorem ipsum") - - expect { create(:user, notes: "", organisations: [organisation]) }.not_to change(Annotation, :count) - end + it "provides the #annotate! helper method" do + user = create(:user) + territory = create(:territory) + expect(Annotation).to receive(:upsert!).with("Ma remarque", user:, territory:) + user.annotate!("Ma remarque", territory:) end - describe "user update" do - it "creates an annotation if notes appear" do - organisation = create(:organisation) - user = create(:user, notes: nil, organisations: [organisation]) - expect(Annotation.count).to eq(0) + it "deletes annotations when leaving the last org of a territory" do + territory_a = create(:territory) + territory_b = create(:territory) + organisation_a_1 = create(:organisation, territory: territory_a) + organisation_a_2 = create(:organisation, territory: territory_a) + organisation_b_1 = create(:organisation, territory: territory_b) + organisation_b_2 = create(:organisation, territory: territory_b) - expect { user.update!(notes: "Lorem ipsum") }.to change(Annotation, :count).by(1) - expect(Annotation.where(user: user).sole).to have_attributes(territory: organisation.territory, content: "Lorem ipsum") - end + user = create(:user, organisations: [organisation_a_1, organisation_a_2, organisation_b_1, organisation_b_2]) + user.annotate!("Infos du territoire A", territory: territory_a) + user.annotate!("Infos du territoire B", territory: territory_b) + expect(Annotation.where(user: user).count).to eq(2) - it "updates the annotation if the notes change" do - organisation = create(:organisation) - user = create(:user, notes: "Lorem ipsum", organisations: [organisation]) - expect { user.update!(notes: "dolor sit amet") }.to change { Annotation.where(user: user).sole.reload.updated_at } - expect(Annotation.where(user: user).sole.content).to eq("dolor sit amet") - end + expect { user.organisations.delete(organisation_a_1) }.not_to change(Annotation, :count) + expect { user.organisations.delete(organisation_a_2) }.to change(Annotation, :count).by(-1) - it "deletes the annotation if the notes become blank" do - organisation = create(:organisation) - user = create(:user, notes: "Lorem ipsum", organisations: [organisation]) - expect { user.update!(notes: "") }.to change { Annotation.where(user: user).first }.to(nil) - end + expect { user.organisations.delete(organisation_b_1) }.not_to change(Annotation, :count) + expect { user.organisations.delete(organisation_b_2) }.to change(Annotation, :count).by(-1) end - describe "user anonymization" do - it "deletes the annotations" do - organisation = create(:organisation) - user = create(:user, notes: "Lorem ipsum", organisations: [organisation]) - expect(Annotation.where(user: user).sole.content).to eq("Lorem ipsum") - - user.soft_delete - expect(Annotation.where(user: user)).to be_empty - end - end - - describe "removing user from organisation" do - it "removes annotations for the org's territory" do - territory_a = create(:territory) - territory_b = create(:territory) - organisation_a_1 = create(:organisation, territory: territory_a) - organisation_a_2 = create(:organisation, territory: territory_a) - organisation_b_1 = create(:organisation, territory: territory_b) - organisation_b_2 = create(:organisation, territory: territory_b) - - user = create(:user, notes: "Lorem ipsum", organisations: [organisation_a_1, organisation_a_2, organisation_b_1, organisation_b_2]) - expect(Annotation.where(user: user).count).to eq(2) - - expect { UserProfile.find_by!(user: user, organisation: organisation_a_1).destroy! }.not_to change(Annotation, :count) - expect { UserProfile.find_by!(user: user, organisation: organisation_a_2).destroy! }.to change(Annotation, :count).by(-1) - expect(Annotation.where(user: user).sole.territory).to eq(territory_b) - end + it "deletes annotations when soft deleting" do + organisation = create(:organisation) + user = create(:user, organisations: [organisation]) + user.annotate!("Ma remarque", territory: organisation.territory) + expect { user.soft_delete }.to change { user.annotations.count }.to(0) 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 af943ce24b..444fed6f72 100644 --- a/spec/services/merge_users_service_spec.rb +++ b/spec/services/merge_users_service_spec.rb @@ -189,23 +189,62 @@ end end - context "both users have notes" do - before do - user_target.update(notes: "Sympa") - user_to_merge.update(notes: "thiquement") - end - + context "both users have annotations" do it "preserves target by default" do + user_target.annotations.create!(territory: organisation.territory, content: "Sympa") + user_to_merge.annotations.create!(territory: organisation.territory, content: "thiquement") perform - expect(user_target.notes).to eq("Sympa") + expect(user_target.annotation_for(organisation.territory)).to eq("Sympa") + end + + context "when merging annotations" do + let(:attributes_to_merge) { [:annotation_content] } + + it "overrides annotation from merged user" do + user_target.annotations.create!(territory: organisation.territory, content: "Sympa") + user_to_merge.annotations.create!(territory: organisation.territory, content: "thiquement") + perform + expect(user_target.annotation_for(organisation.territory)).to eq("thiquement") + end end - context "when merging notes" do - let(:attributes_to_merge) { [:notes] } + context "when both users have annotations in two territories" do + let(:attributes_to_merge) { [:annotation_content] } + + let(:other_territory) { create(:territory) } + let(:organisation_in_other_territory) { create(:organisation, territory: other_territory) } + + let(:current_territory) { organisation.territory } + + before do + user_to_merge.organisations << organisation_in_other_territory + user_target.organisations << organisation_in_other_territory + + user_to_merge.annotations.create!(territory: current_territory, content: "User to merge, current territory") + user_to_merge.annotations.create!(territory: other_territory, content: "User to merge, other territory") + + user_target.annotations.create!(territory: current_territory, content: "User target, current territory") + user_target.annotations.create!(territory: other_territory, content: "User target, other territory") + end - it "overrides notes from merged user" do + it "preserves the 2 users with their 2 annotations in other territory, merges in current territory" do perform - expect(user_target.notes).to eq("thiquement") + user_target.reload + user_to_merge.reload + + # Dans l'autre territoire, les annotations restent intactes + expected_annotations_in_other_territory = [ + [user_to_merge, "User to merge, other territory"], + [user_target, "User target, other territory"], + ] + expect(Annotation.where(territory: other_territory).map { [_1.user, _1.content] }).to match_array(expected_annotations_in_other_territory) + + # Dans le territoire courant, on ne garde que l'annotation à fusionner, c'est-à-dire + # l'annotation sélectionnée, et on l'associe à l'usager qui est gardé (target). + expected_annotations_in_current_territory = [ + [user_target, "User to merge, current territory"], + ] + expect(Annotation.where(territory: current_territory).map { [_1.user, _1.content] }).to match_array(expected_annotations_in_current_territory) end end end