spree_cm_commissioner 2.8.11 → 2.8.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9db31429085eb3f3365a250a214b1025f0f0ab0252f2519581755240ff83cf1a
4
- data.tar.gz: a99971540c37e2cb085b29c3bae51087dbcbab5141cd6b08ae1611fa5c5bb1bb
3
+ metadata.gz: cd2507fdfcb489044af5b8a7f6fd3905d6b7820e06699491481499efe02fabe8
4
+ data.tar.gz: 20905a50da0e184669cc7383b35635898ab9a0134dc26d91516f3eec62985012
5
5
  SHA512:
6
- metadata.gz: 75b2c0ba0b20eac7fa49f22e6ead3253b06b8cbc61831b87d98c8e6010401c2c24f42a74b6e0441ed67da2aef02f4ae32700679651576a0ba167678056b45cf7
7
- data.tar.gz: 0d7e16f4f6bd9aa8124f958b9c8b3ab4ed93783bc9b1b3167bc7b95bc592590a5b0aad4852fe17abe4013b9f8563a677be70218c74102c604c4f10f8a551ee9b
6
+ metadata.gz: d0365e44dbb3c87a2bb62c446210b3396b5a6bebb59f27d703025afc738b87a1bb9dd79af8e2c6dfa14312d51ee60c031c3d7b17ccdc5e48a289bf47f6e351bc
7
+ data.tar.gz: 4a0b6cd516e730c37ad87c4cc0fab35d792d0cbab5532f4dd637b52a37cfcc0ea609d4673af80a94a2d6d2cceeba66c671a523d872406f8c409897a6b856d73b
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.8.11)
37
+ spree_cm_commissioner (2.8.13)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -26,6 +26,13 @@ module Spree
26
26
  redirect_back fallback_location: admin_system_waiting_room_path
27
27
  end
28
28
 
29
+ def modify_max_sessions_count_cap
30
+ modifier = params[:max_sessions_count_cap]&.to_i || 0
31
+ SpreeCmCommissioner::WaitingRoomSystemMetadataSetter.new.modify_max_sessions_count_cap(modifier)
32
+
33
+ redirect_back fallback_location: admin_system_waiting_room_path
34
+ end
35
+
29
36
  def force_pull
30
37
  SpreeCmCommissioner::WaitingRoomLatestSystemMetadataPullerJob.perform_now
31
38
  SpreeCmCommissioner::WaitingGuestsCallerJob.perform_now
@@ -62,6 +62,12 @@ module SpreeCmCommissioner
62
62
  SpreeCmCommissioner::OrderHolds::Release.call!(order: self, reason: reason)
63
63
  end
64
64
 
65
+ # Non-raising variant. OrderHolds::Release rescues internally and returns a failure
66
+ # result, so callers can guard on `.success?` instead of handling exceptions.
67
+ def release_order_holds(reason: :user_canceled)
68
+ SpreeCmCommissioner::OrderHolds::Release.call(order: self, reason: reason)
69
+ end
70
+
65
71
  # Converts all holds to reserved/complete state on order completion.
66
72
  # Raises on failure so the surrounding transaction rolls back.
67
73
  def reserve_order_holds!
@@ -9,7 +9,7 @@ module SpreeCmCommissioner
9
9
  belongs_to :user, class_name: 'Spree::User', optional: true
10
10
 
11
11
  enum status: { pending: 0, active: 1, payment_locked: 2, converted: 3, released: 4 }
12
- enum release_reason: { user_canceled: 0, hold_expired: 1, payment_timeout: 2 }
12
+ enum release_reason: { user_canceled: 0, hold_expired: 1, payment_timeout: 2, cart_changed: 3 }
13
13
 
14
14
  validates :expires_at, presence: true
15
15
  validates :held_at, presence: true
@@ -4,6 +4,14 @@ module SpreeCmCommissioner
4
4
  prepend Spree::ServiceModule::Base
5
5
 
6
6
  def call(order:, line_item:)
7
+ # Release holds BEFORE mutating, using the quantities the hold was placed against
8
+ # (Release reads live line items). Only release when this guest will actually bump
9
+ # the quantity. Guard on success and abort if it fails.
10
+ if should_increase_quantity?(line_item)
11
+ release = order.release_order_holds(reason: :cart_changed)
12
+ return release unless release.success?
13
+ end
14
+
7
15
  # Lock the line item (SELECT ... FOR UPDATE) so concurrent guest add/remove
8
16
  # on the same line item are serialized. with_lock reloads the record inside
9
17
  # the lock, so quantity/guests reflect the latest committed state before we
@@ -0,0 +1,22 @@
1
+ module SpreeCmCommissioner
2
+ module Cart
3
+ module AddItemDecorator
4
+ # Release the order's holds BEFORE mutating the cart, so the hold is reversed using
5
+ # the quantities it was actually placed against (Release reads live line items, it
6
+ # stores no snapshot). Releasing after the change would restock the wrong amounts.
7
+ # A fresh hold is re-acquired when the order transitions toward checkout.
8
+ # Guard on success: if the release fails, abort and surface the error — the cart
9
+ # stays untouched.
10
+ def call(order:, **)
11
+ release = order.release_order_holds(reason: :cart_changed)
12
+ return release unless release.success?
13
+
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ unless Spree::Cart::AddItem.included_modules.include?(SpreeCmCommissioner::Cart::AddItemDecorator)
21
+ Spree::Cart::AddItem.prepend(SpreeCmCommissioner::Cart::AddItemDecorator)
22
+ end
@@ -11,6 +11,14 @@ module SpreeCmCommissioner
11
11
  def call(line_item:, options: {})
12
12
  guest = line_item.guests.new(options)
13
13
 
14
+ # Release holds BEFORE mutating, using the quantities the hold was placed against
15
+ # (Release reads live line items). Only release when this guest will actually bump
16
+ # the quantity. Guard on success and abort if it fails.
17
+ if will_change_quantity?(line_item)
18
+ release = line_item.order.release_order_holds(reason: :cart_changed)
19
+ return release unless release.success?
20
+ end
21
+
14
22
  line_item.with_lock do
15
23
  next unless guest.save
16
24
 
@@ -45,6 +53,17 @@ module SpreeCmCommissioner
45
53
  true
46
54
  end
47
55
 
56
+ # True only when the guest about to be created will push the guest count past the
57
+ # current capacity (number_of_guests = variant.number_of_guests * quantity), i.e. the
58
+ # quantity will actually be bumped. One guest needs at most one extra unit, so a single
59
+ # `+1` comparison is enough. Used to gate the hold release so it fires only on a real
60
+ # quantity change, mirroring Cart::AddGuest#should_increase_quantity?.
61
+ def will_change_quantity?(line_item)
62
+ return false unless need_to_bump_quantity?(line_item)
63
+
64
+ (line_item.guests.count + 1) > line_item.number_of_guests
65
+ end
66
+
48
67
  def bump_quantity_to_cover_guests(line_item)
49
68
  guest_count = line_item.guests.count
50
69
  original_quantity = line_item.quantity
@@ -0,0 +1,23 @@
1
+ module SpreeCmCommissioner
2
+ module Cart
3
+ module EmptyDecorator
4
+ # Emptying the cart removes all line items, so any inventory/seat holds must be
5
+ # released first — reversed against the quantities the hold was placed against
6
+ # (Release reads live line items). Skip completed orders (Cart::Empty refuses them
7
+ # anyway, and their holds are already converted, not active). Guard on success and
8
+ # abort if the release fails.
9
+ def call(order:)
10
+ if order.present? && !order.completed?
11
+ release = order.release_order_holds(reason: :cart_changed)
12
+ return release unless release.success?
13
+ end
14
+
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ unless Spree::Cart::Empty.included_modules.include?(SpreeCmCommissioner::Cart::EmptyDecorator)
22
+ Spree::Cart::Empty.prepend(SpreeCmCommissioner::Cart::EmptyDecorator)
23
+ end
@@ -4,6 +4,16 @@ module SpreeCmCommissioner
4
4
  prepend ::Spree::ServiceModule::Base
5
5
 
6
6
  def call(order:, line_item:, guest_id: nil)
7
+ # Release holds BEFORE mutating, using the quantities the hold was placed against
8
+ # (Release reads live line items). Only release when the quantity will actually
9
+ # decrease. The gate projects the pending guest removal (we evaluate before it
10
+ # happens), unlike should_decrease_quantity? which assumes the guest is already
11
+ # gone. Guard on success and abort if it fails.
12
+ if will_decrease_quantity?(order, line_item, guest_id)
13
+ release = order.release_order_holds(reason: :cart_changed)
14
+ return release unless release.success?
15
+ end
16
+
7
17
  # Lock the line item (SELECT ... FOR UPDATE) so concurrent guest add/remove
8
18
  # on the same line item are serialized. with_lock reloads the record inside
9
19
  # the lock, so quantity/guests reflect the latest committed state before we
@@ -44,6 +54,16 @@ module SpreeCmCommissioner
44
54
  total_guests(line_item) <= required_guests_for_previous_unit(line_item)
45
55
  end
46
56
 
57
+ # Same decision as should_decrease_quantity? but evaluated BEFORE the guest is
58
+ # removed, so we subtract the guest about to be destroyed from the current count.
59
+ # Used only to gate the pre-mutation hold release.
60
+ def will_decrease_quantity?(order, line_item, guest_id)
61
+ return false if order.completed?
62
+
63
+ projected_guests = total_guests(line_item) - (guest_id.present? ? 1 : 0)
64
+ projected_guests <= required_guests_for_previous_unit(line_item)
65
+ end
66
+
47
67
  def total_guests(line_item)
48
68
  line_item.guests.size
49
69
  end
@@ -0,0 +1,17 @@
1
+ module SpreeCmCommissioner
2
+ module Cart
3
+ module RemoveItemDecorator
4
+ # See AddItemDecorator: release holds (guarded) before mutating the cart.
5
+ def call(order:, **)
6
+ release = order.release_order_holds(reason: :cart_changed)
7
+ return release unless release.success?
8
+
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ unless Spree::Cart::RemoveItem.included_modules.include?(SpreeCmCommissioner::Cart::RemoveItemDecorator)
16
+ Spree::Cart::RemoveItem.prepend(SpreeCmCommissioner::Cart::RemoveItemDecorator)
17
+ end
@@ -0,0 +1,17 @@
1
+ module SpreeCmCommissioner
2
+ module Cart
3
+ module RemoveLineItemDecorator
4
+ # See AddItemDecorator: release holds (guarded) before mutating the cart.
5
+ def call(order:, **)
6
+ release = order.release_order_holds(reason: :cart_changed)
7
+ return release unless release.success?
8
+
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ unless Spree::Cart::RemoveLineItem.included_modules.include?(SpreeCmCommissioner::Cart::RemoveLineItemDecorator)
16
+ Spree::Cart::RemoveLineItem.prepend(SpreeCmCommissioner::Cart::RemoveLineItemDecorator)
17
+ end
@@ -0,0 +1,17 @@
1
+ module SpreeCmCommissioner
2
+ module Cart
3
+ module SetQuantityDecorator
4
+ # See AddItemDecorator: release holds (guarded) before mutating the cart.
5
+ def call(order:, **)
6
+ release = order.release_order_holds(reason: :cart_changed)
7
+ return release unless release.success?
8
+
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ unless Spree::Cart::SetQuantity.included_modules.include?(SpreeCmCommissioner::Cart::SetQuantityDecorator)
16
+ Spree::Cart::SetQuantity.prepend(SpreeCmCommissioner::Cart::SetQuantityDecorator)
17
+ end
@@ -31,7 +31,9 @@ module SpreeCmCommissioner
31
31
  # Notify AFTER the transaction commits (deliver_later must not be enqueued inside an open
32
32
  # transaction), and only when THIS invocation performed the release — the `next` guards above
33
33
  # commit without releasing, so this keeps notifications idempotent across stale/concurrent jobs.
34
- notify_release(hold) if released
34
+ # Only scheduled (cron/job) releases may notify. Cart-driven releases never set
35
+ # scheduled_release, so customers editing their cart don't get a "released" push.
36
+ notify_release(hold) if released && scheduled_release
35
37
 
36
38
  success(hold)
37
39
  rescue ActiveRecord::StaleObjectError
@@ -12,17 +12,19 @@ module SpreeCmCommissioner
12
12
  @document_data = document.get.data
13
13
  end
14
14
 
15
- def self.compute_max_sessions_count_with_min(server_running_count:, max_thread_count:, multiplier:)
15
+ def self.compute_max_sessions_count_with_min(server_running_count:, max_thread_count:, multiplier:, cap:)
16
16
  min = ENV.fetch('WAITING_ROOM_MIN_SESSIONS_COUNT', '5').to_i
17
17
  max = server_running_count * max_thread_count * multiplier / 100
18
- [max, min].max
18
+ # Cap the auto-scaled value so the system does not scale beyond the configured ceiling.
19
+ max.clamp(min, cap)
19
20
  end
20
21
 
21
22
  def max_sessions_count_with_min
22
23
  @max_sessions_count_with_min ||= self.class.compute_max_sessions_count_with_min(
23
24
  server_running_count: server_running_count,
24
25
  max_thread_count: max_thread_count,
25
- multiplier: multiplier
26
+ multiplier: multiplier,
27
+ cap: max_sessions_count_cap
26
28
  )
27
29
  end
28
30
 
@@ -41,6 +43,11 @@ module SpreeCmCommissioner
41
43
  document_data[:multiplier]&.to_i || ENV.fetch('WAITING_ROOM_MULTIPLIER', '150').to_i
42
44
  end
43
45
 
46
+ # hard ceiling for max_sessions_count to avoid auto-scaling the system too much
47
+ def max_sessions_count_cap
48
+ document_data[:max_sessions_count_cap]&.to_i || ENV.fetch('WAITING_ROOM_MAX_SESSIONS_COUNT_CAP', '1000').to_i
49
+ end
50
+
44
51
  def document
45
52
  @document ||= firestore.col('metadata').doc('system')
46
53
  end
@@ -20,6 +20,16 @@ module SpreeCmCommissioner
20
20
  cache_max_sessions_count(new_data)
21
21
  end
22
22
 
23
+ def modify_max_sessions_count_cap(modify)
24
+ fetcher.load_document_data
25
+
26
+ new_data = fetcher.document_data.dup
27
+ new_data[:max_sessions_count_cap] = [fetcher.max_sessions_count_cap + modify, 1].max
28
+
29
+ fetcher.document.set(new_data)
30
+ cache_max_sessions_count(new_data)
31
+ end
32
+
23
33
  def set(server_running_count:)
24
34
  fetcher.load_document_data
25
35
 
@@ -40,7 +50,8 @@ module SpreeCmCommissioner
40
50
  max = WaitingRoomSystemMetadataFetcher.compute_max_sessions_count_with_min(
41
51
  server_running_count: data[:server_running_count]&.to_i || fetcher.server_running_count,
42
52
  max_thread_count: data[:max_thread_count]&.to_i || fetcher.max_thread_count,
43
- multiplier: data[:multiplier]&.to_i || fetcher.multiplier
53
+ multiplier: data[:multiplier]&.to_i || fetcher.multiplier,
54
+ cap: data[:max_sessions_count_cap]&.to_i || fetcher.max_sessions_count_cap
44
55
  )
45
56
  Rails.cache.write('waiting_room/max_sessions_count', max, expires_in: 1.hour)
46
57
  end
@@ -20,6 +20,11 @@
20
20
  field_name: :max_sessions,
21
21
  value: @fetcher.max_sessions_count_with_min,
22
22
  },
23
+ {
24
+ field_name: :max_sessions_count_cap,
25
+ value: @fetcher.max_sessions_count_cap,
26
+ modify_path: modify_max_sessions_count_cap_admin_system_waiting_room_path
27
+ },
23
28
  {
24
29
  field_name: :active_sesions,
25
30
  value: @active_sesions_count,
@@ -726,9 +726,9 @@ en:
726
726
  db_save_error: "We encountered an issue saving your reservation. Please try again."
727
727
  redis_error: "We encountered an issue processing your reservation. Please try again."
728
728
  validate_limits:
729
- exceeded_active_holds: "You have too many pending reservations. Please complete or cancel a reservation before trying another."
730
- ip_rate_limit: "Too many reservation attempts from your location. Please wait and try again."
731
- hold_cooldown: "Please wait before retrying. Your previous reservation may still be processing."
729
+ exceeded_active_holds: "You've reached the limit of reservations you can hold at once. Please complete or cancel one before reserving more."
730
+ ip_rate_limit: "Too many reservation attempts from your network in a short time. Please wait a while and try again."
731
+ hold_cooldown: "Your previous reservation just expired. Please wait a couple of minutes before reserving again."
732
732
 
733
733
  account_link:
734
734
  failure: "Failed to link your %{identity_type} account: %{reason}"
@@ -526,9 +526,9 @@ km:
526
526
  db_save_error: "មានបញ្ហាក្នុងការរក្សាទុកការកក់របស់អ្នក។ សូមព្យាយាមម្តងទៀត។"
527
527
  redis_error: "មានបញ្ហាក្នុងការដំណើរការការកក់របស់អ្នក។ សូមព្យាយាមម្តងទៀត។"
528
528
  validate_limits:
529
- exceeded_active_holds: "អ្នកមានការកក់ដែលរង់ចាំច្រើនពេក។ សូមបញ្ចប់ ឬលុបការកក់មួយមុនពេលព្យាយាមវិញ។"
530
- ip_rate_limit: "មានការព្យាយាមកក់ច្រើនពេកពីទីតាំងរបស់អ្នក។ សូមរង់ចាំ ហើយព្យាយាមម្តងទៀត។"
531
- hold_cooldown: "សូមរង់ចាំមុនពេលព្យាយាមម្តងទៀត។ ការកក់របស់អ្នកមុនប្រហែលជាកំពុងដំណើរការ។"
529
+ exceeded_active_holds: "អ្នកកក់ដល់ចំនួនកំណត់អតិបរមាក្នុងពេលតែមួយហើយ។ សូមបញ្ចប់ ឬលុបការកក់មួយ មុនពេលកក់បន្ថែម។"
530
+ ip_rate_limit: "មានការព្យាយាមកក់ច្រើនពេកពីបណ្តាញរបស់អ្នកក្នុងរយៈពេលខ្លី។ សូមរង់ចាំបន្តិច រួចព្យាយាមម្តងទៀត។"
531
+ hold_cooldown: "ការកក់មុនរបស់អ្នកទើបតែផុតកំណត់។ សូមរង់ចាំពីរបីនាទី មុនពេលកក់ម្តងទៀត។"
532
532
 
533
533
  account_link:
534
534
  failure: "បរាជ័យក្នុងការភ្ជាប់គណនី %{identity_type}: %{reason}"
data/config/routes.rb CHANGED
@@ -17,6 +17,7 @@ Spree::Core::Engine.add_routes do
17
17
  post :publish_lobby_path
18
18
  post :modify_multiplier
19
19
  post :modify_max_thread_count
20
+ post :modify_max_sessions_count_cap
20
21
  end
21
22
  end
22
23
 
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.8.11'.freeze
2
+ VERSION = '2.8.13'.freeze
3
3
 
4
4
  module_function
5
5
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_cm_commissioner
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.11
4
+ version: 2.8.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-21 00:00:00.000000000 Z
11
+ date: 2026-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -2100,10 +2100,15 @@ files:
2100
2100
  - app/services/spree_cm_commissioner/assign_product_to_section/update.rb
2101
2101
  - app/services/spree_cm_commissioner/calculate_distance.rb
2102
2102
  - app/services/spree_cm_commissioner/cart/add_guest.rb
2103
+ - app/services/spree_cm_commissioner/cart/add_item_decorator.rb
2103
2104
  - app/services/spree_cm_commissioner/cart/create_guest.rb
2104
2105
  - app/services/spree_cm_commissioner/cart/destroy_decorator.rb
2106
+ - app/services/spree_cm_commissioner/cart/empty_decorator.rb
2105
2107
  - app/services/spree_cm_commissioner/cart/recalculate_decorator.rb
2106
2108
  - app/services/spree_cm_commissioner/cart/remove_guest.rb
2109
+ - app/services/spree_cm_commissioner/cart/remove_item_decorator.rb
2110
+ - app/services/spree_cm_commissioner/cart/remove_line_item_decorator.rb
2111
+ - app/services/spree_cm_commissioner/cart/set_quantity_decorator.rb
2107
2112
  - app/services/spree_cm_commissioner/check_ins/create_bulk.rb
2108
2113
  - app/services/spree_cm_commissioner/check_ins/destroy_bulk.rb
2109
2114
  - app/services/spree_cm_commissioner/checkout/advance_decorator.rb