summaryrefslogtreecommitdiff
path: root/lib/solidus_subscriptions/processor.rb
blob: ef3ec59e980fe2ff3588ec04f04c982409a913c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# This class is responsible for finding subscriptions and installments
# which need to be processed. It will group them together by user and attempts
# to process them together. Subscriptions will also be grouped by their
# shiping address id.
#
# This class passes the reponsibility of actually creating the order off onto
# the consolidated installment class.
#
# This class generates `ProcessInstallmentsJob`s
module SolidusSubscriptions
  class Processor
    class << self
      # Find all actionable subscriptions and intallments, group them together
      # by user, and schedule a processing job for the group as a batch
      def run
        batched_users_to_be_processed.each { |batch| new(batch).build_jobs }
      end

      private

      def batched_users_to_be_processed
        subscriptions = SolidusSubscriptions::Subscription.arel_table
        installments = SolidusSubscriptions::Installment.arel_table

        ::Spree::User.
          joins(:subscriptions).
          joins(
            subscriptions.
              join(installments, Arel::Nodes::OuterJoin).
              on(subscriptions[:id].eq(installments[:subscription_id])).
              join_sources
          ).
          where(
            SolidusSubscriptions::Subscription.actionable.arel.constraints.reduce(:and).
              or(SolidusSubscriptions::Installment.actionable.arel.constraints.reduce(:and))
          ).
          distinct.
          find_in_batches
      end
    end

    # @return [Array<Spree.user_class>]
    attr_reader :users

    # Get a new instance of the SolidusSubscriptions::Processor
    #
    # @param users [Array<Spree.user_class>] A list of users with actionable
    #   subscriptions or installments
    #
    # @return [SolidusSubscriptions::Processor]
    def initialize(users)
      @users = users
      @installments = {}
    end

    # Create `ProcessInstallmentsJob`s for the users used to initalize the
    # instance
    def build_jobs
      users.map do |user|
        installemts_by_address_and_user = installments(user).group_by do |i|
          i.subscription.shipping_address_id
        end

        installemts_by_address_and_user.values.each do |grouped_installments|
          ProcessInstallmentsJob.perform_later grouped_installments.map(&:id)
        end
      end
    end

    private

    def subscriptions_by_id
      @subscriptions_by_id ||= Subscription.
        actionable.
        includes(:line_items, :user).
        where(user_id: user_ids).
        group_by(&:user_id)
    end

    def retry_installments
      @failed_installments ||= Installment.
        actionable.
        includes(:subscription).
        where(solidus_subscriptions_subscriptions: { user_id: user_ids }).
        group_by { |i| i.subscription.user_id }
    end

    def installments(user)
      @installments[user.id] ||= retry_installments.fetch(user.id, []) + new_installments(user)
    end

    def new_installments(user)
      ActiveRecord::Base.transaction do
        subscriptions_by_id.fetch(user.id, []).map do |sub|
          sub.successive_skip_count = 0
          sub.advance_actionable_date
          sub.cancel! if sub.pending_cancellation?
          sub.deactivate! if sub.can_be_deactivated?
          sub.installments.create!
        end
      end
    end

    def user_ids
      @user_ids ||= users.map(&:id)
    end
  end
end