Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions app/controllers/api/v1/market_applications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,24 @@ def success_response(market_application)
end

def application_url_for(market_application)
if market_application.application_mode_choice_required?
return Rails.application.routes.url_helpers.application_mode_candidate_market_application_url(
market_application.identifier
)
end
target, step = market_application.next_required_wizard_step
return wizard_step_url(target, step) if target

Rails.application.routes.url_helpers.step_candidate_market_application_url(
market_application.identifier,
:company_identification
)
end

def wizard_step_url(market_application, step)
case step
when :application_mode
Rails.application.routes.url_helpers.application_mode_candidate_market_application_url(market_application.identifier)
when :grouping_legal_type
Rails.application.routes.url_helpers.grouping_legal_type_candidate_market_application_url(market_application.identifier)
end
end

def validate_application_completed
return if @market_application.completed?

Expand Down
29 changes: 9 additions & 20 deletions app/controllers/candidate/application_modes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ module Candidate
class ApplicationModesController < Candidate::ApplicationController
include Candidate::GroupementFeatureGuard
include Candidate::MarketApplicationGuard
include Candidate::WizardRoutable

prepend_before_action :find_market_application
before_action :redirect_if_mode_already_chosen

def show
@already_mandataire = already_mandataire_elsewhere?
@readonly = @market_application.application_mode.present?
@readonly_continue_path = next_step_path(@market_application) if @readonly
@already_mandataire = presenter.already_mandataire?
@readonly = presenter.readonly?
@readonly_continue_path = next_required_wizard_step_path(@market_application) if @readonly
end

def update
Expand All @@ -22,7 +23,7 @@ def update

return handle_success(result) if result.success?

@already_mandataire = already_mandataire_elsewhere?
@already_mandataire = presenter.already_mandataire?
@errors = result.errors
render :show, status: :unprocessable_content
end
Expand All @@ -39,27 +40,15 @@ def redirect_if_mode_already_chosen
return if @market_application.application_mode.nil?
return if action_name == 'show' && params[:readonly].present?

redirect_to next_step_path(@market_application)
redirect_to next_required_wizard_step_path(@market_application)
end

def next_step_path(market_application)
if market_application.grouping_legal_type_choice_required?
grouping_legal_type_candidate_market_application_path(market_application.identifier)
else
company_identification_candidate_market_application_path(market_application.identifier)
end
end

def already_mandataire_elsewhere?
Grouping
.joins(:mandataire_market_application)
.where(public_market: @market_application.public_market, market_applications: { siret: @market_application.siret })
.where.not(market_applications: { id: @market_application.id })
.exists?
def presenter
@presenter ||= Candidate::ApplicationModePresenter.new(@market_application)
end

def handle_success(result)
redirect_to next_step_path(result.market_application)
redirect_to next_required_wizard_step_path(result.market_application)
end
end
end
12 changes: 9 additions & 3 deletions app/controllers/candidate/grouping_legal_types_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ class GroupingLegalTypesController < Candidate::ApplicationController
prepend_before_action :find_market_application
before_action :redirect_unless_mandataire

def show; end
def show
@grouping = presenter.grouping
end

def update
result = Candidate::SetGroupingLegalType.call(
Expand All @@ -18,6 +20,7 @@ def update

return handle_success if result.success?

@grouping = presenter.grouping
@errors = result.errors
render :show, status: :unprocessable_content
end
Expand All @@ -31,12 +34,15 @@ def find_market_application
end

def redirect_unless_mandataire
@grouping = Grouping.joins(:mandataire_market_application).find_by(market_applications: { id: @market_application.id })
return if @grouping
return if presenter.grouping

redirect_to application_mode_candidate_market_application_path(@market_application.identifier)
end

def presenter
@presenter ||= Candidate::GroupingLegalTypePresenter.new(@market_application)
end

def handle_success
redirect_to company_identification_candidate_market_application_path(@market_application.identifier)
end
Expand Down
23 changes: 15 additions & 8 deletions app/controllers/candidate/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module Candidate
class SessionsController < Candidate::ApplicationController
include Candidate::WizardRoutable

skip_before_action :require_candidate_authentication

def new
Expand Down Expand Up @@ -38,23 +40,28 @@ def destroy

def sign_in_candidate(user, market_application)
reconnection = market_application.user_id.present?
market_application.update!(user:) unless reconnection
link_candidate_to_application(user, market_application) unless reconnection
session[:user_id] = user.id
session[:market_application_identifier] = market_application.identifier
redirect_to first_step_path(market_application)
end

def link_candidate_to_application(user, market_application)
counterpart = market_application.groupement_counterpart || market_application.solo_counterpart
market_application.update!(user:)
counterpart&.update!(user:)
end

def first_step_path(market_application)
if market_application.completed?
return deadline_passed_candidate_market_application_path(market_application.identifier) unless market_application.public_market.open?
return completed_application_path(market_application) if market_application.completed?

return candidate_sync_status_path(market_application.identifier)
end
next_required_wizard_step_path(market_application)
end

return application_mode_candidate_market_application_path(market_application.identifier) if market_application.application_mode_choice_required?
return grouping_legal_type_candidate_market_application_path(market_application.identifier) if market_application.grouping_legal_type_choice_required?
def completed_application_path(market_application)
return deadline_passed_candidate_market_application_path(market_application.identifier) unless market_application.public_market.open?

company_identification_candidate_market_application_path(market_application.identifier)
candidate_sync_status_path(market_application.identifier)
end

def handle_magic_link_sent(result)
Expand Down
5 changes: 3 additions & 2 deletions app/controllers/concerns/candidate/application_mode_guard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module Candidate
module ApplicationModeGuard
extend ActiveSupport::Concern
include Candidate::WizardRoutable

included do
before_action :redirect_to_application_mode_choice
Expand All @@ -11,9 +12,9 @@ module ApplicationModeGuard
private

def redirect_to_application_mode_choice
return unless @market_application&.application_mode_choice_required?
return unless @market_application

redirect_to application_mode_candidate_market_application_path(@market_application.identifier)
redirect_to next_required_wizard_step_path(@market_application) if @market_application.next_required_wizard_step
end
end
end
25 changes: 25 additions & 0 deletions app/controllers/concerns/candidate/wizard_routable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Candidate
module WizardRoutable
extend ActiveSupport::Concern

private

def wizard_step_path(market_application, step)
case step
when :application_mode
application_mode_candidate_market_application_path(market_application.identifier)
when :grouping_legal_type
grouping_legal_type_candidate_market_application_path(market_application.identifier)
end
end

def next_required_wizard_step_path(market_application)
target, step = market_application.next_required_wizard_step
return company_identification_candidate_market_application_path(market_application.identifier) unless target

wizard_step_path(target, step)
end
end
end
24 changes: 24 additions & 0 deletions app/models/market_application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,34 @@ def application_mode_choice_required?
end

def grouping_legal_type_choice_required?
return false if completed?

FeatureFlags::Groupement.enabled? && groupement? &&
Grouping.joins(:mandataire_market_application).exists?(legal_type: nil, market_applications: { id: })
end

def groupement_counterpart
return nil if groupement?

MarketApplication.where(public_market:, siret:, application_mode: :groupement, user_id:).where.not(id:).first
end

def solo_counterpart
return nil if solo?

MarketApplication.where(public_market:, siret:, application_mode: :solo, user_id:).where.not(id:).first
end

def next_required_wizard_step
return nil unless FeatureFlags::Groupement.enabled?
return [self, :application_mode] if application_mode_choice_required?

target = groupement_counterpart || self
return [target, :grouping_legal_type] if target.grouping_legal_type_choice_required?

nil
end

def update_api_status(api_name, status:, fields_filled: 0)
with_lock do
updated_status = (api_fetch_status || {}).dup
Expand Down
25 changes: 25 additions & 0 deletions app/presenters/candidate/application_mode_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Candidate
class ApplicationModePresenter
def initialize(market_application)
@market_application = market_application
end

def already_mandataire?
Grouping
.joins(:mandataire_market_application)
.where(public_market: market_application.public_market, market_applications: { siret: market_application.siret })
.where.not(market_applications: { id: market_application.id })
.exists?
end

def readonly?
market_application.application_mode.present?
end

private

attr_reader :market_application
end
end
20 changes: 20 additions & 0 deletions app/presenters/candidate/grouping_legal_type_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module Candidate
class GroupingLegalTypePresenter
def initialize(market_application)
@market_application = market_application
end

def grouping
return @grouping if defined?(@grouping)

@grouping = Grouping.joins(:mandataire_market_application)
.find_by(market_applications: { id: market_application.id })
end

private

attr_reader :market_application
end
end
10 changes: 4 additions & 6 deletions app/views/candidate/grouping_legal_types/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<div class="fr-container fr-py-6w">
<p class="fr-text--sm fr-mb-1w"><%= t('candidate.grouping_legal_types.step_current') %></p>
<h1 class="fr-h3 fr-mb-2w"><%= t('candidate.grouping_legal_types.title') %></h1>
<h1 class="fr-h4 fr-mb-2w"><%= t('candidate.grouping_legal_types.title') %></h1>

<div class="grouping-legal-type-stepper fr-mb-2w">
<div class="grouping-legal-type-stepper__step grouping-legal-type-stepper__step--done"></div>
Expand All @@ -15,7 +15,7 @@
<%= t('candidate.grouping_legal_types.step_next_label') %>
</p>

<h2 class="fr-h4 fr-mb-3w"><%= t('candidate.grouping_legal_types.question') %></h2>
<h2 class="fr-h1 fr-mb-1w"><%= t('candidate.grouping_legal_types.question') %></h2>

<% if @errors.present? %>
<div class="fr-alert fr-alert--error fr-mb-3w" role="alert">
Expand All @@ -34,9 +34,7 @@
<p><%= t('candidate.grouping_legal_types.alert') %></p>
</div>

<%= form_with url: grouping_legal_type_candidate_market_application_path(@market_application.identifier),
method: :patch,
data: { controller: "enable-on-choice" } do |form| %>
<%= form_with url: grouping_legal_type_candidate_market_application_path(@market_application.identifier), method: :patch, data: { controller: "enable-on-choice" } do |form| %>
<fieldset class="fr-fieldset">
<legend class="fr-fieldset__legend fr-sr-only"><%= t('candidate.grouping_legal_types.question') %></legend>

Expand All @@ -57,7 +55,7 @@
<span class="application-mode-choice__title">
<%= t("candidate.grouping_legal_types.#{legal_type}.label") %>
<% if legal_type == 'conjoint_mandataire_solidaire' %>
<span class="fr-badge fr-badge--info fr-badge--sm">
<span class="fr-badge fr-badge--info fr-badge--no-icon fr-badge--sm">
<%= t('candidate.grouping_legal_types.most_common_badge') %>
</span>
<% end %>
Expand Down
9 changes: 9 additions & 0 deletions features/candidacy_mode_choice.feature
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ Feature: Candidate chooses their candidacy mode
And my application mode should be "solo"
And a second market application should exist with mode "groupement"

@javascript
Scenario: Candidate reconnects from the groupement funnel after choosing mixte
When I visit the magic link
And I choose the candidacy mode "mixte"
Then I should land on the grouping legal type step of the groupement application
When I request a new magic link for the groupement application
And I click the link from the latest email
Then I should land on the grouping legal type step of the groupement application

Scenario: The choice screen is never shown again once a mode is chosen
Given the candidate application already has the mode "solo"
When I visit the magic link
Expand Down
13 changes: 13 additions & 0 deletions features/step_definitions/candidate_authentication_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@
)
end

When('I request a new magic link for the groupement application') do
visit new_candidate_sessions_path(market_application_id: @groupement_market_application.identifier)
fill_in I18n.t('candidate.sessions.new.email_label'), with: @user.email
find_button(I18n.t('candidate.sessions.new.submit'), disabled: false).click
expect(page).to have_current_path(sent_candidate_sessions_path, ignore_query: true)
end

When('I click the link from the latest email') do
mail = ActionMailer::Base.deliveries.last
url = mail.text_part.body.decoded[%r{https?://\S+}]
visit URI(url).request_uri
end

Then('I should see the authentication form') do
expect(page).to have_selector('form[action*="candidate/sessions"]')
end
Expand Down
10 changes: 10 additions & 0 deletions spec/interactors/candidate/create_applications_for_mode_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@
expect(Grouping.count).to eq(1)
expect(Grouping.last.mandataire_market_application).to eq(result.market_application)
end

context 'when the candidate is already authenticated on the solo application' do
let(:user) { create(:user) }
let!(:market_application) { create(:market_application, public_market:, siret:, user:) }

it 'links the groupement counterpart to the same user' do
result = subject
expect(result.market_application.user).to eq(user)
end
end
end

context 'when mixte mode finds an existing groupement application owned by another user' do
Expand Down
Loading