From 03be5b52552017e12e010c403d7640ef633c7138 Mon Sep 17 00:00:00 2001 From: Toon Willems Date: Tue, 25 Feb 2025 10:50:41 +0100 Subject: [PATCH] Create dedicated service for updating thresholds --- app/models/subscription.rb | 2 +- app/services/plans/update_service.rb | 77 +-------------- .../plans/update_usage_thresholds_service.rb | 93 +++++++++++++++++++ 3 files changed, 96 insertions(+), 76 deletions(-) create mode 100644 app/services/plans/update_usage_thresholds_service.rb diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 0639324e647..b0d34344417 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -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 diff --git a/app/services/plans/update_service.rb b/app/services/plans/update_service.rb index d772a330713..9e6d44a9db1 100644 --- a/app/services/plans/update_service.rb +++ b/app/services/plans/update_service.rb @@ -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? @@ -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") @@ -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 = [] @@ -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 diff --git a/app/services/plans/update_usage_thresholds_service.rb b/app/services/plans/update_usage_thresholds_service.rb new file mode 100644 index 00000000000..32df639a9da --- /dev/null +++ b/app/services/plans/update_usage_thresholds_service.rb @@ -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