ministryofjustice/Claim-for-Crown-Court-Defence

View on GitHub
app/services/claims/fee_calculator/unit_price.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
# Service to retrieve the unit price for a given fee.
# Unit price will require input from different attributes
# on the claim and may require the quantity from separate
# but related fees (i.e. for uplifts).
#
# Use this service for prices that can be determined
# with the supply of a value for only ONE unit type.
# This includes fixed fees and miscellaneous fees across
# LGFS and AGFS fee schemes.
#
module Claims
  module FeeCalculator
    UnitModifier = Struct.new(:name, :limit_from, keyword_init: true)

    class UnitPrice < CalculatePrice
      private

      # TODO: retrial basic (basic) fee excluded where retrial starts before trial ends
      # - validation in place to prevent this now
      #
      # TODO: cracked before retrial basic (basic) fee excluded until
      #  - we expose retrial_reduction on these case types
      #  - we find out how to determine retrial interval.
      #    - expose `trial_concluded_at`?
      #    - months between `trial_concluded_at` and `cracked_at`
      #  - we confirm how to handle situations where retrial started before tria concluded
      #
      def exclusions
        raise Exceptions::RetrialReductionExclusion if uncalculatable_retrial_reduction_required?
        raise Exceptions::CrackedBeforeRetrialExclusion if cracked_before_retrial_interval_required?
      end

      def amount
        unit_price
      end

      def price_options
        opts = {}
        opts[:scenario] = scenario.id
        opts[:offence_class] = offence_class_or_default
        opts[:advocate_type] = advocate_type
        opts[:fee_type_code] = fee_type_code_for(fee_type)
        opts[:limit_from] = limit_from
        opts[:limit_to] = limit_to
        opts[:unit] = unit
        opts.keep_if { |_k, v| v.present? }
      end

      def unit_price
        @prices = fee_scheme.prices(**price_options)
        filter_third_cracked_prices
        price
      end

      # NOTE: needed because scheme 9, guilty pleas have a DAY unit price and a PPE unit price
      def unit
        basic_fee_uplift? ? 'DAY' : nil
      end

      def price
        raise Exceptions::PriceNotFound if @prices.empty?
        raise Exceptions::TooManyPrices if @prices.size > 1
        Price.new(@prices.first, modifiers, quantity_from_parent_or_one)
      end

      def basic_fee_uplift?
        %w[BANDR BANOC].include?(fee_type.unique_code)
      end

      def uplift?
        fee_type.case_uplift? || fee_type.defendant_uplift?
      end

      def parent_fee_type
        return unless uplift?
        fee_type.case_uplift? ? case_uplift_parent : defendant_uplift_parent
      end

      def quantity_from_parent_or_one
        parent = parent_fee_type
        return 1 unless parent
        current_total_quantity_for_fee_type(parent)
      end

      # TODO: refactor share retrial methods with grad price
      def retrial_interval_required?
        [
          agfs?,
          case_type&.fee_type_code.eql?('GRRTR'),
          fee_type_code_for(fee_type).eql?('AGFS_FEE'),
          retrial_reduction
        ].all?
      end

      def third_cracked_required?
        [
          agfs?,
          %w[GRRAK GRCBR].include?(case_type&.fee_type_code),
          basic_fee_uplift?
        ].all?
      end

      def filter_third_cracked_prices
        return unless third_cracked_required?
        @prices.keep_if do |price|
          price.modifiers.any? do |m|
            m.modifier_type.name.eql?('THIRD_CRACKED') && third_cracked.in?(m.limit_from..m.limit_to)
          end
        end
      end

      def unit_modifier(name, limit_from)
        UnitModifier.new(name:, limit_from:)
      end

      def modifiers
        @modifiers ||= [].tap do |arr|
          arr.append(unit_modifier(:retrial_interval, retrial_interval)) if retrial_interval_required?
          arr.append(unit_modifier(:number_of_cases, 2)) if fee_type.case_uplift?
          arr.append(unit_modifier(:number_of_defendants, 2)) if fee_type.defendant_uplift?
        end
      end
    end
  end
end