Skip to content

Commit 61904f9

Browse files
committed
feat(products): gate structural catalog edits on attachment
## Context Once a catalog entity is part of a plan or subscription, its structural fields must stop changing: the code, an item's product attachment, and a filter's values. Cosmetic fields (name, description, invoice display name) stay editable. ## Description In the update services, assign code (products, items, filters), product attachment (items) and values (filters) only while the entity is not attached to a plan or subscription, silently skipping them otherwise. Expose those attributes on the update inputs and add an attached_to_plan_or_subscription field on the GraphQL types so the UI knows when to lock them.
1 parent d07840e commit 61904f9

14 files changed

Lines changed: 200 additions & 6 deletions

File tree

app/graphql/types/product_item_filters/object.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Object < Types::BaseObject
1111
field :id, ID, null: false
1212
field :organization, Types::Organizations::OrganizationType
1313

14+
field :attached_to_plan_or_subscription, Boolean, null: false, method: :attached_to_plan_or_subscription?
1415
field :code, String, null: false
1516
field :description, String, null: true
1617
field :invoice_display_name, String, null: true

app/graphql/types/product_item_filters/update_input.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class UpdateInput < BaseInputObject
77

88
argument :id, ID, required: true
99

10+
argument :code, String, required: false
1011
argument :description, String, required: false
1112
argument :invoice_display_name, String, required: false
1213
argument :name, String, required: false

app/graphql/types/product_items/object.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Object < Types::BaseObject
1111
field :id, ID, null: false
1212
field :organization, Types::Organizations::OrganizationType
1313

14+
field :attached_to_plan_or_subscription, Boolean, null: false, method: :attached_to_plan_or_subscription?
1415
field :code, String, null: false
1516
field :description, String, null: true
1617
field :invoice_display_name, String, null: true

app/graphql/types/product_items/update_input.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ class UpdateInput < BaseInputObject
77

88
argument :id, ID, required: true
99

10+
argument :code, String, required: false
1011
argument :description, String, required: false
1112
argument :invoice_display_name, String, required: false
1213
argument :name, String, required: false
14+
argument :product_id, ID, required: false
1315
end
1416
end
1517
end

app/graphql/types/products/object.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Object < Types::BaseObject
1414
field :invoice_display_name, String, null: true
1515
field :name, String, null: false
1616

17+
field :attached_to_plan_or_subscription, Boolean, null: false, method: :attached_to_plan_or_subscription?
1718
field :product_items_count, Integer, null: false
1819

1920
field :created_at, GraphQL::Types::ISO8601DateTime, null: false

app/graphql/types/products/update_input.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class UpdateInput < BaseInputObject
77

88
argument :id, ID, required: true
99

10+
argument :code, String, required: false
1011
argument :description, String, required: false
1112
argument :invoice_display_name, String, required: false
1213
argument :name, String, required: false

app/services/product_item_filters/update_service.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ def initialize(product_item_filter:, params:)
1818
def call
1919
return result.not_found_failure!(resource: "product_item_filter") unless product_item_filter
2020

21-
if params.key?(:values)
21+
# NOTE: code and values can only be edited while the filter's item is not
22+
# yet in a plan or subscription
23+
editable = !product_item_filter.attached_to_plan_or_subscription?
24+
25+
if editable && params.key?(:values)
2226
values_validation = ProductItemFilters::ValidateValuesService.call(
2327
product_item: product_item_filter.product_item,
2428
values_params: params[:values],
@@ -31,9 +35,10 @@ def call
3135
product_item_filter.name = params[:name] if params.key?(:name)
3236
product_item_filter.description = params[:description] if params.key?(:description)
3337
product_item_filter.invoice_display_name = params[:invoice_display_name] if params.key?(:invoice_display_name)
38+
product_item_filter.code = params[:code] if editable && params.key?(:code)
3439
product_item_filter.save!
3540

36-
replace_values if params.key?(:values)
41+
replace_values if editable && params.key?(:values)
3742

3843
result.product_item_filter = product_item_filter
3944
end

app/services/product_items/update_service.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ def call
2121
product_item.name = params[:name] if params.key?(:name)
2222
product_item.description = params[:description] if params.key?(:description)
2323
product_item.invoice_display_name = params[:invoice_display_name] if params.key?(:invoice_display_name)
24+
25+
# NOTE: code and product attachment can only be edited while the item is
26+
# not yet in a plan or subscription
27+
unless product_item.attached_to_plan_or_subscription?
28+
product_item.code = params[:code] if params.key?(:code)
29+
assign_product if params.key?(:product_id)
30+
end
31+
32+
return result if result.failure?
33+
2434
product_item.save!
2535

2636
result.product_item = product_item
@@ -32,5 +42,17 @@ def call
3242
private
3343

3444
attr_reader :product_item, :params
45+
46+
def assign_product
47+
if params[:product_id].blank?
48+
product_item.product = nil
49+
return
50+
end
51+
52+
product = product_item.organization.products.find_by(id: params[:product_id])
53+
return result.not_found_failure!(resource: "product") unless product
54+
55+
product_item.product = product
56+
end
3557
end
3658
end

app/services/products/update_service.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ def call
2121
product.name = params[:name] if params.key?(:name)
2222
product.description = params[:description] if params.key?(:description)
2323
product.invoice_display_name = params[:invoice_display_name] if params.key?(:invoice_display_name)
24+
25+
# NOTE: code can only be edited while the product is not yet in a plan or subscription
26+
product.code = params[:code] if params.key?(:code) && !product.attached_to_plan_or_subscription?
27+
2428
product.save!
2529

2630
result.product = product

schema.graphql

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)