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: T-987 Edit / Delete plan permissions #69

Merged
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
8 changes: 8 additions & 0 deletions app/graphql/types/plans/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,21 @@ class Object < Types::BaseObject
field :charge_count, Integer, null: false, description: 'Number of charges attached to a plan'
field :customer_count, Integer, null: false, description: 'Number of customers attached to a plan'

field :can_be_deleted, Boolean, null: false do
description 'Check if plan is deletable'
end

def charge_count
object.charges.count
end

def customer_count
object.customers.count
end

def can_be_deleted
object.deletable?
end
end
end
end
8 changes: 8 additions & 0 deletions app/models/plan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@ class Plan < ApplicationRecord
def pay_in_arrear?
!pay_in_advance
end

def attached_to_subscriptions?
subscriptions.exists?
end

def deletable?
!attached_to_subscriptions?
end
end
51 changes: 33 additions & 18 deletions app/services/plans_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,21 @@ def update(**args)
return result.fail!('not_found') unless plan

plan.name = args[:name]
plan.code = args[:code]
plan.description = args[:description]
plan.interval = args[:interval].to_sym
plan.frequency = args[:frequency].to_sym
plan.pro_rata = args[:pro_rata]
plan.pay_in_advance = args[:pay_in_advance]
plan.amount_cents = args[:amount_cents]
plan.amount_currency = args[:amount_currency]
plan.vat_rate = args[:vat_rate]
plan.trial_period = args[:trial_period]

# NOTE: Only name and description are editable if plan
# is attached to subscriptions
unless plan.attached_to_subscriptions?
plan.code = args[:code]
plan.interval = args[:interval].to_sym
plan.frequency = args[:frequency].to_sym
plan.pro_rata = args[:pro_rata]
plan.pay_in_advance = args[:pay_in_advance]
plan.amount_cents = args[:amount_cents]
plan.amount_currency = args[:amount_currency]
plan.vat_rate = args[:vat_rate]
plan.trial_period = args[:trial_period]
end

metric_ids = args[:charges].map { |c| c[:billable_metric_id] }.uniq
if metric_ids.present? && plan.organization.billable_metrics.where(id: metric_ids).count != metric_ids.count
Expand All @@ -58,19 +63,22 @@ def update(**args)

ActiveRecord::Base.transaction do
plan.save!
created_charges_ids = []

args[:charges].each do |payload_charge|
charge = Charge.find_by(id: payload_charge[:id])
unless plan.attached_to_subscriptions?
created_charges_ids = []

next charge.update(payload_charge) if charge
args[:charges].each do |payload_charge|
charge = Charge.find_by(id: payload_charge[:id])

created_charge = create_charge(plan, payload_charge)
created_charges_ids.push(created_charge.id)
end
next charge.update(payload_charge) if charge

created_charge = create_charge(plan, payload_charge)
created_charges_ids.push(created_charge.id)
end

# NOTE: Delete charges that are no more linked to the plan
sanitize_charges(plan, args[:charges], created_charges_ids)
# NOTE: Delete charges that are no more linked to the plan
sanitize_charges(plan, args[:charges], created_charges_ids)
end
end

result.plan = plan
Expand All @@ -83,6 +91,13 @@ def destroy(id)
plan = result.user.plans.find_by(id: id)
return result.fail!('not_found') unless plan

unless plan.deletable?
return result.fail!(
'forbidden',
'Plan is attached to active subscriptions',
)
end

plan.destroy!

result.plan = plan
Expand Down
5 changes: 5 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,11 @@ type Plan {
amountCents: Int!
amountCurrency: CurrencyEnum!

"""
Check if plan is deletable
"""
canBeDeleted: Boolean!

"""
Number of charges attached to a plan
"""
Expand Down
18 changes: 18 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2625,6 +2625,24 @@

]
},
{
"name": "canBeDeleted",
"description": "Check if plan is deletable",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null,
"args": [

]
},
{
"name": "chargeCount",
"description": "Number of charges attached to a plan",
Expand Down
3 changes: 2 additions & 1 deletion spec/graphql/resolvers/plans_resolver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<<~GQL
query {
plans(limit: 5) {
collection { id chargeCount customerCount }
collection { id chargeCount customerCount canBeDeleted }
metadata { currentPage, totalCount }
}
}
Expand All @@ -31,6 +31,7 @@
aggregate_failures do
expect(plans_response['collection'].count).to eq(organization.plans.count)
expect(plans_response['collection'].first['id']).to eq(plan.id)
expect(plans_response['collection'].first['canBeDeleted']).to be_truthy

expect(plans_response['metadata']['currentPage']).to eq(1)
expect(plans_response['metadata']['totalCount']).to eq(1)
Expand Down
29 changes: 29 additions & 0 deletions spec/services/plans_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,22 @@
.to change { plan.charges.count }.by(-1)
end
end

context 'when attached to a subscription' do
before do
create(:subscription, plan: plan)
end

it 'updates only name and description' do
result = plans_service.update(**update_args)

updated_plan = result.plan
aggregate_failures do
expect(updated_plan.name).to eq('Updated plan name')
expect(plan.charges.count).to eq(0)
end
end
end
end

describe 'destroy' do
Expand All @@ -257,5 +273,18 @@
expect(result.error).to eq('not_found')
end
end

context 'when plan is attached to subscription' do
before do
create(:subscription, plan: plan)
end

it 'returns an error' do
result = plans_service.destroy(plan.id)

expect(result).not_to be_success
expect(result.error_code).to eq('forbidden')
end
end
end
end