mysociety/alaveteli

View on GitHub
app/models/info_request/state/calculator.rb

Summary

Maintainability
A
1 hr
Test Coverage
class InfoRequest
  module State
    class Calculator
      def initialize(info_request)
        @info_request = info_request
      end

      def phase(cached_value_ok=false)
        if @info_request.awaiting_description?
          :response_received
        else
          state = @info_request.calculate_status(cached_value_ok)
          case state
            when 'not_held',
                 'rejected',
                 'successful',
                 'partially_successful',
                 'user_withdrawn'
              :complete
            when 'waiting_clarification'
              :clarification_needed
            when 'waiting_response'
              :awaiting_response
            when 'waiting_response_overdue'
              :overdue
            when 'waiting_response_very_overdue'
              :very_overdue
            when 'gone_postal',
                 'internal_review',
                 'error_message',
                 'requires_admin',
                 'attention_requested',
                 'vexatious',
                 'not_foi'
              :other
          end
        end
      end

      # Calculates the available state transitions for the current request
      # and returns a hash of hashes, containing groups of states and their
      # accompanying labels, for presenting options to a user when updating
      # the request's status.
      #
      # @see InfoRequest::State::Transitions for the full list of labels
      # @see InfoRequest::State::Transitions.transition_label for options
      #   which can be passed through to control which labels are returned.
      #
      # @param [Hash] opts options to control the transition labels that are
      #   returned. Options will also be passed through to
      #    InfoRequest::State::Transitions#transition_label, so see
      #    documentation there for a full list.
      # @option opts [Boolean] :cached_value_ok is it ok to use a cached value
      #   for the request's current status? (optional, defaults to false)
      # @option opts [Boolean] :user_asked_to_update_status has the user
      #   explicitly asked to update the request status, or is the form just
      #   being shown incidentally? If true, more status options will be given
      #   (optional, defaults to false)
      #
      # @example transitions for a user
      #   calculator.transitions(is_owning_user: true,
      #                          user_asked_to_update_status: true)
      #   #=> {
      #     pending:  {
      #       "waiting_response"      => "I'm still <strong>waiting</strong> for my information <small>(maybe you got an acknowledgement)</small>",
      #       "waiting_clarification" => "I've been asked to <strong>clarify</strong> my request",
      #       "internal_review"       => "I'm waiting for an <strong>internal review</strong> response",
      #       "gone_postal"           => "They are going to reply <strong>by postal mail</strong>"
      #     },
      #     complete: {
      #       "not_held"              => "They do <strong>not have</strong> the information <small>(maybe they say who does)</small>",
      #       "partially_successful"  => "I've received <strong>some of the information</strong>",
      #       "successful"            => "I've received <strong>all the information</strong>",
      #       "rejected"              => "My request has been <strong>refused</strong>"
      #     },
      #     other: {
      #       "error_message"         => "I've received an <strong>error message</strong>",
      #       "requires_admin"        => "This request <strong>requires administrator attention</strong>",
      #       "user_withdrawn"        => "I would like to <strong>withdraw this request</strong>"
      #     }
      #   }
      #
      # @return [Hash] a hash with three keys, :pending, :complete, and :other
      #   which themselves contain hashes of the form state => label.
      def transitions(opts = {})
        cached_value_ok = opts.fetch(:cached_value_ok, false)
        state = @info_request.calculate_status(cached_value_ok)
        if admin_states.include?(state)
          return {
            pending: {},
            complete: {},
            other: {}
          }
        end
        opts[:in_internal_review] = state == 'internal_review'
        opts[:internal_review_requested] =
          @info_request.internal_review_requested?
        build_transitions_hash(opts)
      end

      # A summarised version of #phase, grouping the phases down into 3 groups
      def summarised_phase(cached_value_ok=false)
        phase = phase(cached_value_ok)
        case phase
          when :awaiting_response
            :in_progress
          when :complete
            :complete
          when :other
            :other
          when :response_received,
               :clarification_needed,
               :overdue,
               :very_overdue
            :action_needed
        end
      end

      private

      def build_transitions_hash(opts)
        hash = {}
        [:pending, :complete, :other].each do |group|
          method = "#{group}_states"
          states = send(method, opts)
          hash[group] = Transitions.labelled_hash(states, opts)
        end
        hash
      end

      def pending_states(opts)
        # Which pending states can we transition into
        if opts.fetch(:in_internal_review, false)
          states = %w[internal_review gone_postal]
        else
          states = []
          if opts.fetch(:internal_review_requested, false)
            states += ['internal_review']
          end
          states += %w[
            waiting_response
            waiting_clarification
            gone_postal
          ]
          if opts.fetch(:user_asked_to_update_status, false)
            states += ['internal_review']
          end
        end
        states.uniq
      end

      def complete_states(_opts = {})
        # States from which a request can go no further, because it's complete
        %w[
          not_held
          partially_successful
          successful
          rejected
        ]
      end

      def admin_states(_opts = {})
        # States which only an admin can put a request into, and from which
        # a normal user can't get the request out again
        %w[not_foi vexatious]
      end

      def other_states(opts = {})
        is_owning_user = opts.fetch(:is_owning_user, false)
        user_asked_to_update_status = opts.fetch(:user_asked_to_update_status, false)
        states = ['error_message']
        if user_asked_to_update_status && is_owning_user
          states += %w[requires_admin user_withdrawn]
        end
        states
      end
    end
  end
end