Skip to content

Commit

Permalink
Merge branch 'production' into fix/agent-rdv-policy-motif
Browse files Browse the repository at this point in the history
  • Loading branch information
adipasquale authored Jan 30, 2025
2 parents c85609b + 4d7c193 commit 24214ed
Show file tree
Hide file tree
Showing 77 changed files with 809 additions and 194 deletions.
19 changes: 13 additions & 6 deletions app/controllers/admin/rdvs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ def index

@form = Admin::RdvSearchForm.new(parsed_params.merge(pundit_user:))
@lieux = Lieu.joins(:organisation).where(organisations: { id: @scoped_organisations.select(:id) }).enabled.ordered_by_name
@motifs = Motif.joins(:organisation).where(organisations: { id: @scoped_organisations.select(:id) }).ordered_by_name
@motifs = Agent::MotifPolicy::ScopeForRdvsList.new(
current_agent,
Motif.joins(:organisation).where(organisations: { id: @scoped_organisations.select(:id) }).ordered_by_name
).resolve
end

def a_renseigner
Expand Down Expand Up @@ -72,7 +75,6 @@ def participations_export
end

def show
@uncollapsed_section = params[:uncollapsed_section]
authorize(@rdv, policy_class: Agent::RdvPolicy)
end

Expand All @@ -89,7 +91,7 @@ def update
authorize(@rdv, policy_class: Agent::RdvPolicy)

@rdv_form = Admin::EditRdvForm.new(@rdv, pundit_user)
@success = @rdv_form.submit(rdv_params)
@success = @rdv_form.submit(rdv_update_params)

respond_to do |format|
format.js do
Expand Down Expand Up @@ -161,9 +163,8 @@ def set_rdv
@rdv = policy_scope(Rdv, policy_scope_class: Agent::RdvPolicy::Scope).find(params[:id])
end

def rdv_params
def rdv_update_params
allowed_params = params.require(:rdv).permit(:motif_id, :status, :lieu_id, :duration_in_min, :starts_at, :context, :ignore_benign_errors, :max_participants_count, :name,
agent_ids: [],
participations_attributes: %i[user_id send_lifecycle_notifications send_reminder_notification id _destroy],
lieu_attributes: %i[name address latitude longitude id])

Expand All @@ -174,6 +175,12 @@ def rdv_params
allowed_params[:lieu_attributes][:availability] = :single_use
end

if params[:rdv][:agent_ids].present?
# La méthode Motif#authorized_agents est aussi utilisée pour lister les agents du select
# de l'edit, c'est donc cohérent de l'utiliser ici pour sanitizer les IDs d'agent.
allowed_params[:agent_ids] = @rdv.motif.authorized_agents.where(id: params[:rdv][:agent_ids]).pluck(:id).uniq
end

allowed_params
end

Expand All @@ -191,7 +198,7 @@ def parsed_params

def rdv_success_flash
{
notice: if rdv_params[:status].in?(Rdv::CANCELLED_STATUSES)
notice: if rdv_update_params[:status].in?(Rdv::CANCELLED_STATUSES)
I18n.t("admin.rdvs.message.success.cancel")
else
I18n.t("admin.rdvs.message.success.update")
Expand Down
73 changes: 71 additions & 2 deletions app/controllers/agents/rdv_plans_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class Agents::RdvPlansController < AgentAuthController
layout "application"
before_action :find_rdv_plan
before_action :redirect_to_rdv, if: -> { @rdv_plan.rdv.present? }, except: [:rdv]

def show
redirect_to edit_starts_at_agents_rdv_plan_path(@rdv_plan)
Expand All @@ -12,10 +13,73 @@ def edit_starts_at

def update_starts_at
@rdv_plan.update!(params.require(:rdv_plan).permit(:starts_at).merge(rdv_agent: current_agent))
redirect_to show_starts_at_agents_rdv_plan_path(@rdv_plan)
redirect_to edit_modalites_agents_rdv_plan_path(@rdv_plan)
end

def show_starts_at; end
def edit_modalites; end

def update_modalites
rdv_plan_params = params.require(:rdv_plan).permit(:starts_at, :modalite)

if @rdv_plan.update(rdv_plan_params)
redirect_to edit_motif_agents_rdv_plan_path(@rdv_plan)
else
render "edit_modalites"
end
end

def edit_motif
@motifs = current_agent.motifs.individuel.where(
organisation_id: current_agent.roles.select(:organisation_id),
location_type: @rdv_plan.location_type,
service: @rdv_plan.rdv_agent.services
)
if @motifs.count == 1
@rdv_plan.motif_id ||= @motifs.first.id
end
@rdv_plan.duration_in_minutes ||= @motifs.first.default_duration_in_min
end

def update_motif
rdv_plan_params = params.require(:rdv_plan).permit(:motif_id, :duration_in_minutes)

if @rdv_plan.update(rdv_plan_params)
redirect_to edit_user_agents_rdv_plan_path(@rdv_plan)
else
render "edit_motif_from_calendar"
end
end

def edit_user; end

def create_rdv
rdv_plan_params = params.require(:rdv_plan)

user_attributes = rdv_plan_params.require(:user).permit(:email, :phone_number)

# TODO: possible à mettre en commun ?
participation_attributes = if @rdv_plan.motif.visible_and_notified?
rdv_plan_params.require(:participation).permit(
:send_lifecycle_notifications, :send_reminder_notification
)
else
{ send_lifecycle_notifications: false, send_reminder_notification: false }
end

rdv = @rdv_plan.create_rdv(user_attributes:, participation_attributes:)

if rdv.valid?
flash[:success] = "Le rendez-vous a été créé."
redirect_to rdv_agents_rdv_plan_path(@rdv_plan)
else
flash[:error] = rdv.errors.full_messages.to_sentence
redirect_to edit_user_agents_rdv_plan_path(@rdv_plan)
end
end

def rdv
@rdv = @rdv_plan.rdv
end

private

Expand All @@ -24,6 +88,11 @@ def find_rdv_plan
authorize @rdv_plan, :edit?, policy_class: Agent::RdvPlanPolicy
end

def redirect_to_rdv
# TODO: ajouter un flash ici?
redirect_to rdv_agents_rdv_plan_path(@rdv_plan)
end

def pundit_user
current_agent
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ def user_for_paper_trail
def agent_not_authorized(exception)
policy_name = exception.policy.class.to_s.underscore
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
redirect_to authenticated_agent_root_path

if request.referer.present? &&
request.original_url != request.referer && # évite les boucles infinies
URI.parse(request.referer).host == current_domain.host_name # évite de rediriger vers l'extérieur
redirect_to request.referer
else
redirect_to authenticated_agent_root_url
end
end
end
16 changes: 9 additions & 7 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,17 @@ def alert_dsfr_class_for(alert)
end
end

def datetime_input(form, field, input_html: {})
def datetime_input(form, field, input_html: {}, options: {})
form.input(
field,
as: :string,
input_html: {
value: form.object.send(field)&.strftime("%d/%m/%Y %H:%M"),
data: { behaviour: "datetimepicker" },
autocomplete: "off",
}.deep_merge(input_html)
{
as: :string,
input_html: {
value: form.object.send(field)&.strftime("%d/%m/%Y %H:%M"),
data: { behaviour: "datetimepicker" },
autocomplete: "off",
}.deep_merge(input_html),
}.deep_merge(options)
)
end

Expand Down
2 changes: 2 additions & 0 deletions app/jobs/outlook/mass_create_event_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ class MassCreateEventJob < ApplicationJob
queue_as :outlook_sync

def perform(agent)
Sentry.set_user({ id: agent.id, role: "Agent", email: agent.email })

agent.agents_rdvs.joins(:rdv).where(rdv: { starts_at: 1.month.ago.. }).find_each do |agents_rdv|
Outlook::SyncEventJob.perform_later_for(agents_rdv)
end
Expand Down
2 changes: 2 additions & 0 deletions app/jobs/outlook/mass_destroy_event_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ class MassDestroyEventJob < ApplicationJob
queue_as :outlook_sync

def perform(agent)
Sentry.set_user({ id: agent.id, role: "Agent", email: agent.email })

client = Outlook::ApiClient.new(agent)

agent.agents_rdvs.where.not(outlook_id: nil).each do |agents_rdv|
Expand Down
2 changes: 2 additions & 0 deletions app/jobs/outlook/sync_event_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ def self.perform_later_for(agents_rdv)
end

def perform(agents_rdv_id, outlook_id, agent)
Sentry.set_user({ id: agent.id, role: "Agent", email: agent.email })

@agents_rdv_id = agents_rdv_id
@outlook_id = outlook_id
@agent = agent
Expand Down
9 changes: 9 additions & 0 deletions app/models/agent_territorial_access_right.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,13 @@ class AgentTerritorialAccessRight < ApplicationRecord
# Relations
belongs_to :agent
belongs_to :territory

scope :without_any_rights_allowed, lambda {
where(
allow_to_manage_teams: false,
allow_to_manage_access_rights: false,
allow_to_invite_agents: false
)
}
scope :with_some_rights_allowed, -> { without_any_rights_allowed.invert_where }
end
7 changes: 7 additions & 0 deletions app/models/rdv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def nested_lieu_attributes

validates :participations, presence: true, unless: :collectif?
validates :status, inclusion: { in: COLLECTIVE_RDV_STATUSES }, if: :collectif?
validate :validate_motif_organisation

# Hooks
after_save :associate_users_with_organisation
Expand Down Expand Up @@ -426,4 +427,10 @@ def update_agents_unknown_past_rdv_count
def set_created_by_for_participations
participations.each { |participation| participation.created_by = created_by }
end

def validate_motif_organisation
if organisation_id != motif.organisation_id
errors.add(:motif_id, "n’appartient pas à l’organisation du RDV")
end
end
end
41 changes: 41 additions & 0 deletions app/models/rdv_plan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,47 @@ class RdvPlan < ApplicationRecord

validate :return_url_is_authorized

# TODO: mettre en commun avec les motifs et ajouter une validation de synchro
enum :location_type, { public_office: "public_office", phone: "phone", home: "home", visio: "visio" }

def modalite
if location_type == "public_office"
"#{location_type}-#{lieu&.id}"
else
location_type
end
end

def modalite=(modalite)
self.location_type, self.lieu_id = modalite.split("-")
end

def create_rdv(user_attributes:, participation_attributes:)
if user.encrypted_password.blank? # Pour mettre à jour l'email sans renvoyer de mail de confirmation
user.skip_confirmation_notification!
user.skip_reconfirmation!
end
user.update!(user_attributes)

rdv = Rdv.create(
agents: [rdv_agent],
participations: [Participation.new(participation_attributes.merge(user_id: user.id))],
motif: motif,
organisation: organisation,
lieu: lieu,
starts_at: starts_at,
created_by: planning_agent,
ends_at: starts_at + (duration_in_minutes || motif.default_duration_in_min).minutes
)

if rdv.persisted?
update(rdv: rdv)
Notifiers::RdvCreated.perform_with(rdv, planning_agent)
end

rdv
end

private

def return_url_is_authorized
Expand Down
2 changes: 1 addition & 1 deletion app/models/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Service < ApplicationRecord
validates :name, :short_name, presence: true, uniqueness: { case_sensitive: false }

# Scopes
default_scope { order(Arel.sql("unaccent(LOWER(name))")) }
default_scope { order(Arel.sql("unaccent(LOWER(services.name))")) }

## -

Expand Down
22 changes: 22 additions & 0 deletions app/policies/agent/motif_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,26 @@ def resolve

alias current_agent pundit_user
end

class ScopeForRdvsList < Scope
# on veut permettre aux agents de filtrer sur les motifs d’autres services pour lesquels ils ont des RDV en binôme
def resolve
super.or(
scope.where(
organisation: current_agent.basic_orgs + current_agent.admin_orgs,
id: motif_ids_from_other_services
)
)
end

private

def motif_ids_from_other_services
# L’exclusion des services de l’agent ci-dessous n’est pas nécessaire mais évite de retourner de nombreux motif ids dans cette sous-requête
AgentsRdv.joins(rdv: [:motif])
.where(agent: current_agent)
.where.not(motifs: { service_id: current_agent.service_ids })
.select("rdvs.motif_id")
end
end
end
11 changes: 10 additions & 1 deletion app/policies/agent/rdv_plan_policy.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Agent::RdvPlanPolicy < ApplicationPolicy
def create?
pundit_user == record.planning_agent
authorized_lieu && pundit_user == record.planning_agent
end
alias edit? create?

Expand All @@ -9,4 +9,13 @@ def resolve
scope.where(planning_agent: pundit_user)
end
end

private

# TODO: ajouter une spec pour ce cas
def authorized_lieu
return true unless record.lieu_id

Agent::LieuPolicy::Scope.new(pundit_user, Lieu.enabled).resolve.find_by(id: record.lieu_id).present?
end
end
13 changes: 7 additions & 6 deletions app/policies/agent/territory_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ def initialize(current_agent, scope)
end

def resolve
@scope.joins(:roles).where(roles: { agent: @current_agent })
end
end
territories_with_roles = @scope.joins(:roles)
.where(agent_territorial_roles: { agent: @current_agent })

private
territories_with_rights = @scope.joins(:agent_territorial_access_rights)
.where(agent_territorial_access_rights: { agent: @current_agent })
.merge(AgentTerritorialAccessRight.with_some_rights_allowed)

def access_rights
@access_rights ||= @current_agent.agent_territorial_access_rights.find_by(territory: @territory)
@scope.where_id_in_subqueries([territories_with_roles, territories_with_rights])
end
end
end
3 changes: 2 additions & 1 deletion app/views/admin/organisations/new.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
= f.input :name, label: "Nom de votre organisation", placeholder: "Maison départementale des solidarités de Beaupréau-en-Mauges"
= f.association :territory, \
label: "Territoire", \
collection: Agent::TerritoryPolicy::Scope.new(current_agent, current_agent.territories).resolve, \
collection: [@organisation.territory], \
include_blank: false, \
readonly: true, \
label_method: -> { _1.to_s }
= f.submit value: "Enregistrer", class: "btn btn-primary"
Loading

0 comments on commit 24214ed

Please sign in to comment.