Skip to content

Commit

Permalink
[CPDLP-3937] Add mentor funding statement and output calculators
Browse files Browse the repository at this point in the history
  • Loading branch information
leandroalemao committed Jan 31, 2025
1 parent 470a230 commit 00afa3f
Show file tree
Hide file tree
Showing 16 changed files with 1,323 additions and 466 deletions.
2 changes: 1 addition & 1 deletion app/components/finance/statements/ecf_details_table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class ECFDetailsTable < BaseComponent

def initialize(statement:)
@statement = statement
@calculator = Finance::ECF::StatementCalculator.new(statement: @statement)
@calculator = Finance::ECF::ECT::StatementCalculator.new(statement: @statement)
end
end
end
Expand Down
6 changes: 5 additions & 1 deletion app/controllers/finance/ecf/statements_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ class StatementsController < BaseController
def show
@ecf_lead_provider = lead_provider_scope.find(params[:payment_breakdown_id])
@statement = @ecf_lead_provider.statements.find(params[:id])
@calculator = StatementCalculator.new(statement: @statement)
@calculator = ECT::StatementCalculator.new(statement: @statement)
set_important_message(title: t("finance.statements.payment_authorisations.banner.title"), content: t("finance.statements.payment_authorisations.banner.content", statement_marked_as_paid_at: @statement.marked_as_paid_at.strftime("%-I:%M%P on %-e %b %Y"))) if authorising_for_payment_banner_visible?(@statement)

if @statement.cohort.mentor_funding?
@mentor_calculator = Mentor::StatementCalculator.new(statement: @statement)
end
end

private
Expand Down
2 changes: 1 addition & 1 deletion app/models/finance/statement/ecf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def paid!
end

def calculator
@calculator ||= Finance::ECF::StatementCalculator.new(statement: self)
@calculator ||= Finance::ECF::ECT::StatementCalculator.new(statement: self)
end

def previous_statements
Expand Down
250 changes: 250 additions & 0 deletions app/services/finance/ecf/ect/output_calculator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# frozen_string_literal: true

module Finance
module ECF
module ECT
class OutputCalculator < Finance::ECF::OutputCalculator
COHORT_WITH_MENTOR_FUNDING_DECLARATION_CLASS_TYPES = ["ParticipantDeclaration::ECT"].freeze

def banding_breakdown
@banding_breakdown ||= begin
bandings = declaration_types.map do |declaration_type|
current_banding_for_declaration_type(declaration_type)
end

result = bandings[0]

band_letters.each do |letter|
bandings[1..].each do |banding|
new_chunk = banding.find { |e| e[:band] == letter }
result.find { |e| e[:band] == letter }.merge!(new_chunk)
end
end

result
end
end

def uplift_breakdown
@uplift_breakdown ||= {
previous_count: previous_fill_level_for_uplift,
count: current_billable_count_for_uplift - current_refundable_count_for_uplift,
additions: current_billable_count_for_uplift,
subtractions: current_refundable_count_for_uplift,
}
end

def fee_for_declaration(band_letter:, type:)
percentage = case type
when :started
started_event_percentage
when :completed
completed_event_percentage
when :retained_1, :retained_2, :retained_3, :retained_4
retained_event_percentage
when :extended_1, :extended_2, :extended_3
extended_event_percentage
end

percentage * band_for_letter(band_letter).output_payment_per_participant
end

private

def band_for_letter(letter)
bands.zip(:a..:z).find { |e| e[1] == letter }[0]
end

def started_event_percentage
0.2
end

def completed_event_percentage
0.2
end

def retained_event_percentage
0.15
end

def extended_event_percentage
0.15
end

def declaration_types
%w[
started
retained-1
retained-2
retained-3
retained-4
completed
extended-1
extended-2
extended-3
]
end

# this is a 3 pass algorithm
# first pass adds billable declarations
# second pass subtracts refunds
# third pass further subtracts refunds if statement is net negative
def current_banding_for_declaration_type(declaration_type)
pot_size = current_billable_count_for_declaration_type(declaration_type)

banding = previous_banding_for_declaration_type(declaration_type).map do |hash|
band_capacity = hash[:max] - (hash[:min] - 1) - hash[:"previous_#{declaration_type.underscore}_count"]

fill_level = [pot_size, band_capacity].min

pot_size -= fill_level

hash[:"#{declaration_type.underscore}_count"] = fill_level
hash[:"#{declaration_type.underscore}_additions"] = fill_level

hash
end

pot_size = current_refundable_count_declaration_type(declaration_type)

banding = banding.reverse.map do |hash|
fill_level = hash[:"#{declaration_type.underscore}_count"]

available = [fill_level, pot_size].min

hash[:"#{declaration_type.underscore}_count"] = fill_level - available
hash[:"#{declaration_type.underscore}_subtractions"] = available

pot_size -= available

hash
end

if pot_size.positive?
banding = banding.map do |hash|
available = hash[:"previous_#{declaration_type.underscore}_count"]

unless available.zero?
reduction = [pot_size, available].min

hash[:"#{declaration_type.underscore}_count"] = -reduction
hash[:"#{declaration_type.underscore}_subtractions"] += reduction

pot_size -= reduction
end

hash
end
end

banding.reverse
end

def previous_banding_for_declaration_type(declaration_type)
pot_size = previous_fill_level_for_declaration_type(declaration_type)

bands.zip(:a..:z).map do |band, letter|
# minimum band should always be 1 or more, otherwise band a will go over
# its max limit
band_min = band.min.to_i.zero? ? 1 : band.min

band_capacity = band.max - band_min + 1

fill_level = [pot_size, band_capacity].min

pot_size -= fill_level

key_name = "previous_#{declaration_type.underscore}_count".to_sym

{
band: letter,
min: band_min,
max: band.max,
key_name => fill_level,
}
end
end

def bands
statement.contract.bands.order(max: :asc)
end

def band_letters
bands.zip(:a..:z).map { |e| e[1] }
end

def previous_fill_level_for_declaration_type(declaration_type)
billable = Finance::StatementLineItem
.where(statement: statement.previous_statements)
.billable
.joins(:participant_declaration)
.where(participant_declarations: { declaration_type:, type: participant_declaration_class_types })
.count

refundable = Finance::StatementLineItem
.where(statement: statement.previous_statements)
.refundable
.joins(:participant_declaration)
.where(participant_declarations: { declaration_type:, type: participant_declaration_class_types })
.count

billable - refundable
end

def previous_fill_level_for_uplift
billable = Finance::StatementLineItem
.where(statement: statement.previous_statements)
.billable
.joins(:participant_declaration)
.where(participant_declarations: { declaration_type: "started", type: participant_declaration_class_types })
.where("participant_declarations.sparsity_uplift = true OR participant_declarations.pupil_premium_uplift = true")
.count

refundable = Finance::StatementLineItem
.where(statement: statement.previous_statements)
.refundable
.joins(:participant_declaration)
.where(participant_declarations: { declaration_type: "started", type: participant_declaration_class_types })
.where("participant_declarations.sparsity_uplift = true OR participant_declarations.pupil_premium_uplift = true")
.count

billable - refundable
end

def current_billable_count_for_declaration_type(declaration_type)
statement
.billable_statement_line_items
.joins(:participant_declaration)
.where(participant_declarations: { declaration_type:, type: participant_declaration_class_types })
.count
end

def current_refundable_count_declaration_type(declaration_type)
statement
.refundable_statement_line_items
.joins(:participant_declaration)
.where(participant_declarations: { declaration_type:, type: participant_declaration_class_types })
.count
end

def current_billable_count_for_uplift
statement
.billable_statement_line_items
.joins(:participant_declaration)
.where(participant_declarations: { declaration_type: "started", type: participant_declaration_class_types })
.where("participant_declarations.sparsity_uplift = true OR participant_declarations.pupil_premium_uplift = true")
.count
end

def current_refundable_count_for_uplift
statement
.refundable_statement_line_items
.joins(:participant_declaration)
.where(participant_declarations: { declaration_type: "started", type: participant_declaration_class_types })
.where("participant_declarations.sparsity_uplift = true OR participant_declarations.pupil_premium_uplift = true")
.count
end
end
end
end
end
Loading

0 comments on commit 00afa3f

Please sign in to comment.