mysociety/alaveteli

View on GitHub
app/models/info_request_batch.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# == Schema Information
# Schema version: 20210114161442
#
# Table name: info_request_batches
#
#  id               :integer          not null, primary key
#  title            :text             not null
#  user_id          :integer          not null
#  created_at       :datetime         not null
#  updated_at       :datetime         not null
#  body             :text
#  sent_at          :datetime
#  embargo_duration :string
#

class InfoRequestBatch < ApplicationRecord
  include AlaveteliPro::RequestSummaries
  include AlaveteliFeatures::Helpers
  include InfoRequest::TitleValidation

  has_many :info_requests,
           inverse_of: :info_request_batch
  belongs_to :user,
             inverse_of: :info_request_batches,
             counter_cache: true
  has_many :citations,
           -> (batch) { unscope(:where).for_batch(batch) },
           as: :citable,
           inverse_of: :citable,
           dependent: :destroy

  has_and_belongs_to_many :public_bodies, -> {
    AlaveteliLocalization.with_locale(AlaveteliLocalization.locale) do
      includes(:translations).
        reorder('public_body_translations.name asc')
    end
  }, inverse_of: :info_request_batches

  attr_accessor :ignore_existing_batch

  validates_presence_of :user
  validates_presence_of :body
  validates_absence_of :existing_batch,
                       unless: -> { ignore_existing_batch },
                       on: :create

  strip_attributes only: %i[embargo_duration]

  scope :not_embargoed, -> { where(embargo_duration: nil) }

  def self.send_batches
    where(sent_at: nil).find_each do |info_request_batch|
      AlaveteliLocalization.with_locale(info_request_batch.user.locale) do
        info_request_batch.create_batch!

        InfoRequestBatchMailer.batch_sent(
          info_request_batch,
          info_request_batch.unrequestable_public_bodies,
          info_request_batch.user
        ).deliver_now

        info_request_batch.sent_at = Time.zone.now
        info_request_batch.save!
      end
    end
  end

  def self.with_body(body)
    where("regexp_replace(info_request_batches.body, '[[:space:]]', '', 'g') =
           regexp_replace(?, '[[:space:]]', '', 'g')", body)
  end

  # When constructing a new batch, use this to check user hasn't double
  # submitted.
  def self.find_existing(user, title, body, public_body_ids, id: nil)
    conditions = {
      user_id: user,
      title: title,
      info_request_batches_public_bodies: {
        public_body_id: public_body_ids
      }
    }

    scope = includes(:public_bodies).
      where(conditions).
      with_body(body).
      references(:public_bodies)

    scope = scope.where.not(id: id) if id

    scope.first
  end

  # Create a new batch from the supplied draft version
  def self.from_draft(draft)
    new(user: draft.user,
             public_bodies: draft.public_bodies,
             title: draft.title,
             body: draft.body,
             embargo_duration: draft.embargo_duration)
  end

  def existing_batch
    self.class.find_existing(user, title, body, public_body_ids, id: id)
  end

  # Create a batch of information requests and sends them to public bodies
  def create_batch!
    requestable_public_bodies.each do |public_body|
      info_request = transaction do
        create_request!(public_body)
      end

      send_request(info_request)

      # Sleep between requests in production, in case we're sending a huge
      # batch which may result in a torrent of auto-replies coming back to
      # us and overloading the server.
      uses_poller = user.features.enabled?(:accept_mail_from_poller)
      sleep 60 if Rails.env.production? && !uses_poller
    end
    reload
  end

  # Create a FOI request for a public body
  def create_request!(public_body)
    filled_body = OutgoingMessage.fill_in_salutation(body, public_body)
    info_request = InfoRequest.build_from_attributes({ title: title },
                                                     { body: filled_body },
                                                     user)
    info_request.public_body = public_body
    info_request.info_request_batch = self

    unless embargo_duration.blank?
      info_request.embargo = AlaveteliPro::Embargo.create(
        info_request: info_request,
        embargo_duration: embargo_duration
      )
    end

    info_request.save!
    info_request
  end

  # Send a FOI request to a public body
  def send_request(info_request)
    outgoing_message = info_request.outgoing_messages.first
    outgoing_message.sendable?
    mail_message = OutgoingMailer.initial_request(
      outgoing_message.info_request,
      outgoing_message
    ).deliver_now
    outgoing_message.record_email_delivery(
      mail_message.to_addrs.join(', '),
      mail_message.message_id)
  end

  # Do we consider the InfoRequestBatch to be sent to all authorities?
  #
  # Returns a Boolean
  def sent?
    sent_at.present?
  end

  # Build an InfoRequest object which is an example of this batch.
  def example_request
    public_body = public_bodies.first
    body = OutgoingMessage.fill_in_salutation(self.body, public_body)
    info_request = InfoRequest.build_from_attributes(
      { title: title, public_body: public_body },
      { body: body },
      user
    )
    unless embargo_duration.blank?
      info_request.embargo = AlaveteliPro::Embargo.new(
        info_request: info_request,
        embargo_duration: embargo_duration
      )
    end
    info_request
  end

  # Is the batch currently embargoed?
  #
  # Returns Boolean
  def embargoed?
    embargo_duration.present?
  end

  # Do any of the requests in this batch have an embargo which is expiring
  # soon?
  #
  # Returns boolean
  def embargo_expiring?
    info_requests.embargo_expiring.any?
  end

  # Can the Embargo be safely changed?
  #
  # Returns a Boolean
  def can_change_embargo?
    sent?
  end

  # What phases are the requests in this batch in
  #
  # Returns unique array of symbols representing phases from InfoRequest::State
  def request_phases
    phases = info_requests.reload.map do |ir|
      if ir.last_event_forming_initial_request_id.nil?
        :awaiting_response
      else
        ir.state.phase
      end
    end
    phases.uniq
  end

  # Summarise the phases requests are in into three groups:
  # in progress, action needed, complete and provide a count of the number of
  # requests in each group.
  #
  # Returns hash of string group names mapped to an integer
  def request_phases_summary
    {
      in_progress: {
        label: _('In progress'),
        count: info_requests.in_progress.count
      },
      action_needed: {
        label: _('Action needed'),
        count: info_requests.action_needed.count
      },
      complete: {
        label: _('Complete'),
        count: info_requests.complete.count
      },
      other: {
        label: _('Other'),
        count: info_requests.other.count
      }
    }
  end

  # @see RequestSummaries#request_summary_body
  def request_summary_body
    body
  end

  # @see RequestSummaries#request_summary_public_body_names
  def request_summary_public_body_names
    public_bodies.pluck(:name).join(" ")
  end

  # @see RequestSummaries#request_summary_categories
  def request_summary_categories
    categories = []
    if embargo_expiring?
      categories << AlaveteliPro::RequestSummaryCategory.embargo_expiring
    end
    if sent_at
      phase_slugs = request_phases.map(&:to_s).uniq
      phases = AlaveteliPro::RequestSummaryCategory.where(slug: phase_slugs)
      categories.concat phases
    else
      # A batch info request which hasn't been sent yet won't show up in the
      # list unless we give it some kind of category, so we fake an awaiting
      # response one
      categories << AlaveteliPro::RequestSummaryCategory.awaiting_response
    end
    categories
  end

  # Return a list of public bodies we've already sent the request to
  #
  # Returns an array of PublicBody objects
  def sent_public_bodies
    PublicBody.where(id: info_requests.map(&:public_body_id))
  end

  # Return a list of public bodies which we can send the request to
  #
  # Returns an array of PublicBody objects
  def requestable_public_bodies
    public_bodies.is_requestable - sent_public_bodies
  end

  # Return a list of public bodies which we can't sent the request to
  #
  # Returns an array of PublicBody objects
  def unrequestable_public_bodies
    public_bodies - public_bodies.is_requestable - sent_public_bodies
  end

  # Have we persisted an InfoRequest for each PublicBody in this batch?
  #
  # Returns a Boolean
  def all_requests_created?
    requestable_public_bodies.empty?
  end

  # Should we summarise the batch request?
  #
  # Returns a Boolean
  def should_summarise?
    request_summary.nil? || all_requests_created?
  end

  # Log an event for all information requests within the batch
  #
  # Returns an array of InfoRequestEvent objects
  def log_event(*args)
    info_requests.map { |request| request.log_event(*args) }
  end

  def is_owning_user?(user)
    return false unless user

    user.id == user_id || user.owns_every_request?
  end

  def prominence
    'normal'
  end
end