Skip to content

Commit

Permalink
Create dedicated service for updating thresholds
Browse files Browse the repository at this point in the history
  • Loading branch information
nudded committed Feb 25, 2025
1 parent 08f9119 commit 03be5b5
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 76 deletions.
2 changes: 1 addition & 1 deletion app/models/subscription.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def self.ending_at_in_timezone_sql
def mark_as_active!(timestamp = Time.current)
self.started_at ||= timestamp
self.lifetime_usage ||= previous_subscription&.lifetime_usage || build_lifetime_usage(organization:)
self.lifetime_usage.update(recalculate_invoiced_usage: true)
self.lifetime_usage.recalculate_invoiced_usage = true
active!
end

Expand Down
77 changes: 2 additions & 75 deletions app/services/plans/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,8 @@ def call

process_charges(plan, params[:charges]) if params[:charges]

if params.key?(:usage_thresholds) &&
License.premium? &&
plan.organization.progressive_billing_enabled?

if params[:usage_thresholds].empty?
plan.usage_thresholds.discard_all
else
process_usage_thresholds(plan, params[:usage_thresholds])
end
if params.key?(:usage_thresholds) && License.premium?
Plans::UpdateUsageThresholdsService.call(plan, params[:usage_thresholds])
end

process_minimum_commitment(plan, params[:minimum_commitment]) if params[:minimum_commitment] && License.premium?
Expand Down Expand Up @@ -144,25 +137,6 @@ def cascade?
ActiveModel::Type::Boolean.new.cast(params[:cascade_updates])
end

def create_usage_threshold(plan, params)
usage_threshold = plan.usage_thresholds.find_or_initialize_by(
recurring: params[:recurring] || false,
amount_cents: params[:amount_cents]
)

existing_recurring_threshold = plan.usage_thresholds.recurring.first

if params[:recurring] && existing_recurring_threshold
usage_threshold = existing_recurring_threshold
end

usage_threshold.threshold_display_name = params[:threshold_display_name]
usage_threshold.amount_cents = params[:amount_cents]

usage_threshold.save!
usage_threshold
end

def process_minimum_commitment(plan, params)
if params.present?
minimum_commitment = plan.minimum_commitment || Commitment.new(plan:, commitment_type: "minimum_commitment")
Expand All @@ -184,47 +158,6 @@ def process_minimum_commitment(plan, params)
minimum_commitment
end

def process_usage_thresholds(plan, params_thresholds)
created_thresholds_ids = []

hash_thresholds = params_thresholds.map { |c| c.to_h.deep_symbolize_keys }
hash_thresholds.each do |payload_threshold|
usage_threshold = plan.usage_thresholds.find_by(id: payload_threshold[:id])

if usage_threshold
if payload_threshold.key?(:threshold_display_name)
usage_threshold.threshold_display_name = payload_threshold[:threshold_display_name]
end

if payload_threshold.key?(:amount_cents)
usage_threshold.amount_cents = payload_threshold[:amount_cents]
end

if payload_threshold.key?(:recurring)
usage_threshold.recurring = payload_threshold[:recurring]
end

# This means that in the UI we just removed an existing threshold
# and then just re-added a threshold (which no longer has an id) with the same amount
# so we discard the existing one and we're inserting a new one instead
if !usage_threshold.valid? && usage_threshold.errors.where(:amount_cents, :taken).present?
usage_threshold.discard!
else
usage_threshold.save!
next
end
end

plan = plan.reload

created_threshold = create_usage_threshold(plan, payload_threshold)
created_thresholds_ids.push(created_threshold.id)
end

# NOTE: Delete thresholds that are no more linked to the plan
sanitize_thresholds(plan, hash_thresholds, created_thresholds_ids)
end

def process_charges(plan, params_charges)
created_charges_ids = []

Expand Down Expand Up @@ -259,12 +192,6 @@ def sanitize_charges(plan, args_charges, created_charges_ids)
end
end

def sanitize_thresholds(plan, args_thresholds, created_thresholds_ids)
args_thresholds_ids = args_thresholds.map { |c| c[:id] }.compact
thresholds_ids = plan.usage_thresholds.pluck(:id) - args_thresholds_ids - created_thresholds_ids
plan.usage_thresholds.where(id: thresholds_ids).discard_all
end

# NOTE: We should remove pending subscriptions
# if plan has been downgraded but amount cents became less than downgraded value. This pending subscription
# is not relevant in this case and downgrade should be ignored
Expand Down
93 changes: 93 additions & 0 deletions app/services/plans/update_usage_thresholds_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# frozen_string_literal: true

module Plans
class UpdateUsageThresholdsService < BaseService
Result = BaseResult[:plan]

def initialize(plan:, usage_threshold_params:)
@plan = plan
@usage_threshold_params = usage_threshold_params
super
end

def call
result.plan = plan
return result unless plan.organization.progressive_billing_enabled?

if usage_thresholds.empty?
plan.usage_thresholds.discard_all
else
process_usage_thresholds
end
end

private

def process_usage_thresholds
created_thresholds_ids = []

hash_thresholds = usage_threshold_params.map { |c| c.to_h.deep_symbolize_keys }
hash_thresholds.each do |payload_threshold|
usage_threshold = plan.usage_thresholds.find_by(id: payload_threshold[:id])

if usage_threshold
if payload_threshold.key?(:threshold_display_name)
usage_threshold.threshold_display_name = payload_threshold[:threshold_display_name]
end

if payload_threshold.key?(:amount_cents)
usage_threshold.amount_cents = payload_threshold[:amount_cents]
end

if payload_threshold.key?(:recurring)
usage_threshold.recurring = payload_threshold[:recurring]
end

# This means that in the UI we just removed an existing threshold
# and then just re-added a threshold (which no longer has an id) with the same amount
# so we discard the existing one and we're inserting a new one instead
if !usage_threshold.valid? && usage_threshold.errors.where(:amount_cents, :taken).present?
usage_threshold.discard!
else
usage_threshold.save!
next
end
end

plan = plan.reload

created_threshold = create_usage_threshold(plan, payload_threshold)
created_thresholds_ids.push(created_threshold.id)
end
# NOTE: Delete thresholds that are no more linked to the plan
sanitize_thresholds(plan, hash_thresholds, created_thresholds_ids)
end

def sanitize_thresholds(plan, args_thresholds, created_thresholds_ids)
args_thresholds_ids = args_thresholds.map { |c| c[:id] }.compact
thresholds_ids = plan.usage_thresholds.pluck(:id) - args_thresholds_ids - created_thresholds_ids
plan.usage_thresholds.where(id: thresholds_ids).discard_all
end

def create_usage_threshold(plan, params)
usage_threshold = plan.usage_thresholds.find_or_initialize_by(
recurring: params[:recurring] || false,
amount_cents: params[:amount_cents]
)

existing_recurring_threshold = plan.usage_thresholds.recurring.first

if params[:recurring] && existing_recurring_threshold
usage_threshold = existing_recurring_threshold
end

usage_threshold.threshold_display_name = params[:threshold_display_name]
usage_threshold.amount_cents = params[:amount_cents]

usage_threshold.save!
usage_threshold
end

attr_reader :plan, :usage_threshold_params
end
end

0 comments on commit 03be5b5

Please sign in to comment.