Skip to content

Commit

Permalink
[CPDLP-3046] Add accept applications endpoint (#1368)
Browse files Browse the repository at this point in the history
* [CPDLP-3046] Add accept applications endpoint

* [CPDLP-3046] Address PR comments

* [CPDLP-3048] Add change funding application endpoint

* [CPDLP-3048] Address PR comments

* [CPDLP-3049] TS4 - Add swagger docs for all three new action endpoints in all versions

* [CPDLP-3049] Add more api docs specs

* [CPDLP-3049] Address PR comments

* [CPDLP-3046] Address PR comments

---------

Co-authored-by: Mooktakim Ahmed <[email protected]>
  • Loading branch information
leandroalemao and mooktakim authored May 30, 2024
1 parent 26cc532 commit bd4dcff
Show file tree
Hide file tree
Showing 38 changed files with 1,776 additions and 88 deletions.
36 changes: 35 additions & 1 deletion app/controllers/api/v1/applications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ def show
render json: to_json(application)
end

def accept = head(:method_not_allowed)
def accept
service = Applications::Accept.new(application:, funded_place:)

if service.accept
render json: to_json(service.application)
else
render json: API::Errors::Response.from(service), status: :unprocessable_entity
end
end

def reject
service = Applications::Reject.new(application:)
Expand All @@ -32,6 +40,16 @@ def reject
end
end

def change_funded_place
service = Applications::ChangeFundedPlace.new(application:, funded_place:)

if service.change
render json: to_json(service.application)
else
render json: API::Errors::Response.from(service), status: :unprocessable_entity
end
end

private

def applications_query
Expand Down Expand Up @@ -59,6 +77,22 @@ def to_json(obj)
def to_csv(obj)
ApplicationCsvSerializer.new(obj).call
end

def accept_permitted_params
parameters = params
.fetch(:data)
.permit(:type, attributes: %i[funded_place])

return parameters unless parameters["attributes"].empty?

raise ActionController::BadRequest, I18n.t(:invalid_data_structure)
rescue ActionController::ParameterMissing
{}
end

def funded_place
accept_permitted_params.dig("attributes", "funded_place")
end
end
end
end
36 changes: 35 additions & 1 deletion app/controllers/api/v2/applications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ def show
render json: to_json(application)
end

def accept = head(:method_not_allowed)
def accept
service = Applications::Accept.new(application:, funded_place:)

if service.accept
render json: to_json(service.application)
else
render json: API::Errors::Response.from(service), status: :unprocessable_entity
end
end

def reject
service = Applications::Reject.new(application:)
Expand All @@ -32,6 +40,16 @@ def reject
end
end

def change_funded_place
service = Applications::ChangeFundedPlace.new(application:, funded_place:)

if service.change
render json: to_json(service.application)
else
render json: API::Errors::Response.from(service), status: :unprocessable_entity
end
end

private

def applications_query
Expand Down Expand Up @@ -59,6 +77,22 @@ def to_json(obj)
def to_csv(obj)
ApplicationCsvSerializer.new(obj).call
end

def accept_permitted_params
parameters = params
.fetch(:data)
.permit(:type, attributes: %i[funded_place])

return parameters unless parameters["attributes"].empty?

raise ActionController::BadRequest, I18n.t(:invalid_data_structure)
rescue ActionController::ParameterMissing
{}
end

def funded_place
accept_permitted_params.dig("attributes", "funded_place")
end
end
end
end
36 changes: 35 additions & 1 deletion app/controllers/api/v3/applications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ def show
render json: to_json(application)
end

def accept = head(:method_not_allowed)
def accept
service = Applications::Accept.new(application:, funded_place:)

if service.accept
render json: to_json(service.application)
else
render json: API::Errors::Response.from(service), status: :unprocessable_entity
end
end

def reject
service = Applications::Reject.new(application:)
Expand All @@ -24,6 +32,16 @@ def reject
end
end

def change_funded_place
service = Applications::ChangeFundedPlace.new(application:, funded_place:)

if service.change
render json: to_json(service.application)
else
render json: API::Errors::Response.from(service), status: :unprocessable_entity
end
end

private

def applications_query
Expand Down Expand Up @@ -51,6 +69,22 @@ def application_params
def to_json(obj)
ApplicationSerializer.render(obj, view: :v3, root: "data")
end

def accept_permitted_params
parameters = params
.fetch(:data)
.permit(:type, attributes: %i[funded_place])

return parameters unless parameters["attributes"].empty?

raise ActionController::BadRequest, I18n.t(:invalid_data_structure)
rescue ActionController::ParameterMissing
{}
end

def funded_place
accept_permitted_params.dig("attributes", "funded_place")
end
end
end
end
1 change: 1 addition & 0 deletions app/serializers/api/application_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AttributesSerializer < Blueprinter::Base
field(:teacher_catchment_iso_country_code)
field(:itt_provider) { |a| a.itt_provider&.legal_name }
field(:lead_mentor)
field(:funded_place)
field(:created_at)
field(:updated_at) do |a|
[
Expand Down
2 changes: 1 addition & 1 deletion app/services/api/errors/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def self.from(service)
errors: service
.errors
.messages
.map { |title, detail| { title:, detail: detail.uniq.join(", ") } },
.map { |error, detail| new(error:, params: detail.uniq).call }.flatten,
}
end
end
Expand Down
112 changes: 112 additions & 0 deletions app/services/applications/accept.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# frozen_string_literal: true

module Applications
class Accept
include ActiveModel::Model
include ActiveModel::Attributes

attribute :application
attribute :funded_place, :boolean

validates :application, presence: { message: I18n.t("application.missing_application") }
validate :not_already_accepted
validate :cannot_change_from_rejected
validate :other_accepted_applications_with_same_course?
validate :eligible_for_funded_place
validate :validate_funded_place

def accept
return false unless valid?

ApplicationRecord.transaction do
accept_application!
reject_other_applications_in_same_cohort!
set_funded_place_on_npq_application!
end

true
end

private

delegate :cohort, :user, :course, to: :application

def not_already_accepted
return if application.blank?

errors.add(:application, I18n.t("application.has_already_been_accepted")) if application.accepted?
end

def cannot_change_from_rejected
return if application.blank?

errors.add(:application, I18n.t("application.cannot_change_from_rejected")) if application.rejected?
end

def other_accepted_applications_with_same_course?
errors.add(:application, I18n.t("application.has_another_accepted_application")) if other_accepted_applications_with_same_course.present?
end

def accept_application!
application.update!(lead_provider_approval_status: "accepted")
end

def reject_other_applications_in_same_cohort!
return if other_applications_in_same_cohort.blank?

other_applications_in_same_cohort.update!(lead_provider_approval_status: "rejected")
end

def other_accepted_applications_with_same_course
return if application.blank?

@other_accepted_applications_with_same_course ||= Application
.where(lead_provider_approval_status: "accepted", course: course.rebranded_alternative_courses, user: [user, same_trn_users].flatten.compact.uniq)
.where.not(id: application.id)
end

def other_applications_in_same_cohort
return if cohort.blank?

@other_applications_in_same_cohort ||= Application
.where(cohort:, course:, user:)
.where.not(id: application.id)
end

def trn
@trn ||= user.trn_verified? ? user.trn : nil
end

def same_trn_users
return if trn.blank?

@same_trn_users ||= User
.where(trn:)
.where.not(id: user.id)
end

def set_funded_place_on_npq_application!
return unless cohort&.funding_cap?

application.update!(funded_place:)
end

def eligible_for_funded_place
return if errors.any?
return unless cohort&.funding_cap?

if funded_place && !application.eligible_for_funding
errors.add(:application, I18n.t("application.not_eligible_for_funded_place"))
end
end

def validate_funded_place
return if errors.any?
return unless cohort&.funding_cap?

if funded_place.nil?
errors.add(:application, I18n.t("application.funded_place_required"))
end
end
end
end
51 changes: 51 additions & 0 deletions app/services/applications/change_funded_place.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

module Applications
class ChangeFundedPlace
include ActiveModel::Model
include ActiveModel::Attributes

attribute :application
attribute :funded_place, :boolean

validates :application, presence: { message: I18n.t("application.missing_application") }
validates :funded_place,
inclusion: {
in: [true, false],
message: I18n.t("application.missing_funded_place"),
}
validate :accepted_application
validate :eligible_for_funding
validate :cohort_has_funding_cap

def change
return false unless valid?

application.update!(funded_place:)
end

private

delegate :cohort, to: :application

def accepted_application
return if application.accepted?

errors.add(:application, I18n.t("application.cannot_change_funded_status_from_non_accepted"))
end

def eligible_for_funding
return unless funded_place
return if application.eligible_for_funding?

errors.add(:application, I18n.t("application.cannot_change_funded_status_non_eligible"))
end

def cohort_has_funding_cap
return if errors.any?
return if cohort&.funding_cap?

errors.add(:application, I18n.t("application.cohort_does_not_accept_capping"))
end
end
end
10 changes: 10 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ en:
not_found: "Not found"
invalid_updated_since_filter: "The filter '#/updated_since' must be a valid ISO 8601 date"
invalid_page_parameters: "The '#/page[page]' and '#/page[per_page]' parameter values must be a valid positive number"
invalid_data_structure: correct json data structure required. See API docs for reference

errors:
email:
Expand All @@ -15,6 +16,15 @@ en:
missing_application: "The entered '#/application' is missing from your request. Check details and try again."
has_already_been_rejected: This NPQ application has already been rejected
cannot_change_from_accepted: Once accepted an application cannot change state
has_another_accepted_application: The participant has already had an application accepted for this course.
has_already_been_accepted: This NPQ application has already been accepted
cannot_change_from_rejected: Once rejected an application cannot change state
not_eligible_for_funded_place: "The participant is not eligible for funding, so '#/funded_place' cannot be set to true."
funded_place_required: "Set '#/funded_place' to true or false."
cannot_change_funded_status_from_non_accepted: You must accept the application before attempting to change the '#/funded_place' setting.
cannot_change_funded_status_non_eligible: This participant is not eligible for funding. Contact us if you think this is wrong.
missing_funded_place: "The entered '#/funded_place' is missing from your request. Check details and try again."
cohort_does_not_accept_capping: "Leave the '#/funded_place' field blank. It's only needed for participants starting NPQs from autumn 2024 onwards."

time:
formats:
Expand Down
Loading

0 comments on commit bd4dcff

Please sign in to comment.