Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plan-charges-cascading): Support charge filters cascading #2761

Merged
merged 7 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/jobs/charges/update_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ module Charges
class UpdateJob < ApplicationJob
queue_as 'default'

def perform(charge:, params:, cascade:)
Charges::UpdateService.call(charge:, params:, cascade:).raise_if_error!
def perform(charge:, params:, cascade_options:)
Charges::UpdateService.call(charge:, params:, cascade_options:).raise_if_error!
end
end
end
6 changes: 6 additions & 0 deletions app/models/charge_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ def to_h
end
end

def to_h_with_discarded
@to_h_with_discarded ||= values.with_discarded.each_with_object({}) do |filter_value, result|
result[filter_value.billable_metric_filter.key] = filter_value.values
end
end

def to_h_with_all_values
@to_h_with_all_values ||= values.each_with_object({}) do |filter_value, result|
values = filter_value.values
Expand Down
63 changes: 59 additions & 4 deletions app/services/charge_filters/create_or_update_batch_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@

module ChargeFilters
class CreateOrUpdateBatchService < BaseService
def initialize(charge:, filters_params:)
def initialize(charge:, filters_params:, cascade_options: {})
@charge = charge
@filters_params = filters_params
@cascade_updates = cascade_options[:cascade]
@parent_filters_attributes = cascade_options[:parent_filters] || []
@parent_filters = if parent_filters_attributes.blank?
ChargeFilter.none
else
ChargeFilter.with_discarded.where(id: parent_filters_attributes.map { |f| f['id'] })
end

super
end
Expand All @@ -29,6 +36,20 @@ def call
f.to_h.sort == filter_param[:values].sort
end

# Skip cascade update if properties are already touched
if cascade_updates && filter && parent_filters
parent_filter = parent_filters.find do |pf|
pf.to_h.sort == filter.to_h.sort
end

if parent_filter.blank? || parent_filter_properties(parent_filter) != filter.properties
filter.touch # rubocop:disable Rails/SkipsModelValidations
result.filters << filter

next
end
end

filter ||= charge.filters.new

filter.invoice_display_name = filter_param[:invoice_display_name]
Expand Down Expand Up @@ -57,7 +78,9 @@ def call
end

# NOTE: remove old filters that were not created or updated
charge.filters.where.not(id: result.filters.map(&:id)).find_each do
remove_query = charge.filters
remove_query = remove_query.where(id: inherited_filter_ids) if cascade_updates && parent_filters
remove_query.where.not(id: result.filters.map(&:id)).find_each do
remove_filter(_1)
end
end
Expand All @@ -67,21 +90,53 @@ def call

private

attr_reader :charge, :filters_params
attr_reader :charge, :filters_params, :cascade_updates, :parent_filters, :parent_filters_attributes

def filters
@filters ||= charge.filters.includes(values: :billable_metric_filter)
end

def parent_filter_properties(parent_filter)
match = parent_filters_attributes.find do |f|
f['id'] == parent_filter.id
end

match['properties']
end

def remove_all
ActiveRecord::Base.transaction do
charge.filters.each { remove_filter(_1) }
if cascade_updates
charge.filters.where(id: inherited_filter_ids).find_each { remove_filter(_1) }
else
charge.filters.each { remove_filter(_1) }
end
end
end

def remove_filter(filter)
filter.values.each(&:discard!)
filter.discard!
end

def inherited_filter_ids
return @inherited_filter_ids if defined? @inherited_filter_ids

@inherited_filter_ids = []

return @inherited_filter_ids if parent_filters.blank? || !cascade_updates

parent_filters.find_each do |pf|
value = pf.to_h_with_discarded.sort

match = filters.find do |f|
value == f.to_h.sort
end

@inherited_filter_ids << match.id if match
end

@inherited_filter_ids
end
end
end
36 changes: 18 additions & 18 deletions app/services/charges/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

module Charges
class UpdateService < BaseService
def initialize(charge:, params:, cascade: false)
def initialize(charge:, params:, cascade_options: {})
@charge = charge
@params = params
@cascade = cascade
@cascade_options = cascade_options
@cascade = cascade_options[:cascade]

super
end
Expand All @@ -18,30 +19,29 @@ def call
charge.charge_model = params[:charge_model] unless plan.attached_to_subscriptions?
charge.invoice_display_name = params[:invoice_display_name] unless cascade

properties = params.delete(:properties).presence || Charges::BuildDefaultPropertiesService.call(
params[:charge_model]
)

charge.update!(
properties: Charges::FilterChargeModelPropertiesService.call(
charge:,
properties:
).properties
)

result.charge = charge
if !cascade || cascade_options[:equal_properties]
properties = params.delete(:properties).presence || Charges::BuildDefaultPropertiesService.call(
params[:charge_model]
)
charge.properties = Charges::FilterChargeModelPropertiesService.call(charge:, properties:).properties
end

# In cascade mode it is allowed only to change properties
return result if cascade
charge.save!

filters = params.delete(:filters)
unless filters.nil?
ChargeFilters::CreateOrUpdateBatchService.call(
charge:,
filters_params: filters.map(&:with_indifferent_access)
filters_params: filters.map(&:with_indifferent_access),
cascade_options:
).raise_if_error!
end

result.charge = charge

# In cascade mode it is allowed only to change properties
return result if cascade

tax_codes = params.delete(:tax_codes)
if tax_codes
taxes_result = Charges::ApplyTaxesService.call(charge:, tax_codes:)
Expand Down Expand Up @@ -69,7 +69,7 @@ def call

private

attr_reader :charge, :params, :cascade
attr_reader :charge, :params, :cascade_options, :cascade

delegate :plan, to: :charge
end
Expand Down
12 changes: 10 additions & 2 deletions app/services/plans/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,16 @@ def cascade_charge_update(charge, payload_charge)
plan.children.includes(:charges).find_each do |p|
child_charge = p.charges.find { |c| c.parent_id == charge.id }

if child_charge && charge.equal_properties?(child_charge)
Charges::UpdateJob.perform_later(charge: child_charge, params: payload_charge, cascade: true)
if child_charge
Charges::UpdateJob.perform_later(
charge: child_charge,
params: payload_charge,
cascade_options: {
cascade: true,
parent_filters: charge.filters.map(&:attributes),
equal_properties: charge.equal_properties?(child_charge)
}
)
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions spec/jobs/charges/update_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
let(:organization) { create(:organization) }
let(:plan) { create(:plan, organization:) }
let(:charge) { create(:standard_charge, plan:) }
let(:cascade) { true }
let(:cascade_options) { {} }
let(:params) do
{
properties: {}
}
end

before do
allow(Charges::UpdateService).to receive(:call).with(charge:, params:, cascade:).and_return(BaseService::Result.new)
allow(Charges::UpdateService).to receive(:call).with(charge:, params:, cascade_options:).and_return(BaseService::Result.new)
end

it 'calls the service' do
described_class.perform_now(charge:, params:, cascade:)
described_class.perform_now(charge:, params:, cascade_options:)

expect(Charges::UpdateService).to have_received(:call)
end
Expand Down
24 changes: 24 additions & 0 deletions spec/models/charge_filter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,30 @@
end
end

describe '#to_h_with_discarded' do
subject(:charge_filter) { create(:charge_filter) }

let(:card) { create(:billable_metric_filter, key: 'card', values: %w[credit debit]) }
let(:scheme) { create(:billable_metric_filter, key: 'scheme', values: %w[visa mastercard]) }
let(:values) do
[
create(:charge_filter_value, charge_filter:, values: ['credit'], billable_metric_filter: card).discard,
create(:charge_filter_value, charge_filter:, values: ['visa'], billable_metric_filter: scheme).discard
]
end

before { values }

it 'returns the values as a hash' do
expect(charge_filter.to_h_with_discarded).to eq(
{
'card' => ['credit'],
'scheme' => ['visa']
}
)
end
end

describe '#to_h_with_all_values' do
subject(:charge_filter) { build(:charge_filter, values:) }

Expand Down
Loading