spree_cm_commissioner 2.8.3.pre.pre13 → 2.8.3.pre.pre14

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: eedbddb863eb9f58dad73a83892a225e971a89736c37af047df757d76bcfc058
4
- data.tar.gz: 59eaad5012be611640f1c27f6566bfb91cd7b5cea176d465be69c4230c8b9241
3
+ metadata.gz: e14575f7ac52d9cfa8c454de179107ec31f080a9b8bdc2ff1bfdebeeb5a42f46
4
+ data.tar.gz: fde706a204a681e6c94931be9dfc915ed5090f3abd81d2481120d598b1e168dd
5
5
  SHA512:
6
- metadata.gz: aa9db90a1c39aaa74f66135c5034cb8762e09123edaad154fb5fe6e013609b5344d23910bd8449380030c5b3e391b3a3792c92b569f5fbe2271d6e78420e55e6
7
- data.tar.gz: 80c51833f840ba8b4db0611309153c7a7723fd986f065113a06063ac7d5aee78cce2af29dd2eb591b64a90c57a75f1f4716b9d7daf2664903ff34ad031f663e4
6
+ metadata.gz: c69e06e635df7a5259a6fbcab7d1fba55b93eac89ed5c12592e0070baf1bfbaa3ad442754604fd7cffe435d069c340a0f6b427abf39049d5fa5855b5a68fc64f
7
+ data.tar.gz: 33bf29761d4ee458f559c067c5872db8e381c0f9de7805ce02efaa76bde07229e97a5c20f42ba81a77eb585820dc4360eadbcd035cb4be740364ff1d2d947440
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.8.3.pre.pre13)
37
+ spree_cm_commissioner (2.8.3.pre.pre14)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -0,0 +1,58 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class FreeVoteClaimController < BaseController
6
+ before_action :require_spree_current_user
7
+
8
+ def show
9
+ load_effective_config_onto_show
10
+
11
+ render_serialized_payload { serialize_resource(current_show) }
12
+ end
13
+
14
+ def create
15
+ result = SpreeCmCommissioner::VotingCredits::ClaimFreeVotes.new(
16
+ show: current_show,
17
+ user: spree_current_user,
18
+ tenant_id: MultiTenant.current_tenant_id,
19
+ votable_id: voting_session.id
20
+ ).call
21
+
22
+ if result.failure?
23
+ render_error_payload(result.error.to_s)
24
+ elsif result.value.nil?
25
+ head :no_content
26
+ else
27
+ render_serialized_payload { Spree::V2::Tenant::VotingCreditSerializer.new(result.value).serializable_hash }
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def resource_serializer
34
+ Spree::V2::Tenant::FreeVoteClaimSerializer
35
+ end
36
+
37
+ def load_effective_config_onto_show
38
+ config = voting_session.effective_voting_config(show_config: current_show.effective_voting_config)
39
+
40
+ current_show.claimed = !spree_current_user.free_vote_claimable?(show: current_show, votable_id: voting_session.id)
41
+ current_show.effective_free_vote_limit = config['free_vote_limit']
42
+ current_show.effective_free_vote_limit_type = config['free_vote_limit_type']
43
+ rescue ArgumentError
44
+ current_show.claimed = false
45
+ end
46
+
47
+ def voting_session
48
+ @voting_session ||= current_vendor.voting_sessions.find(params[:voting_session_id])
49
+ end
50
+
51
+ def current_show
52
+ @current_show ||= voting_session.show
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,33 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class VotePackagesController < BaseController
6
+ def index
7
+ render_serialized_payload { serialize_collection(scope) }
8
+ end
9
+
10
+ private
11
+
12
+ def scope
13
+ current_vendor.vote_packages.where(status: :active, event_id: current_season.id)
14
+ end
15
+
16
+ def current_season
17
+ @current_season ||= current_vendor.shows
18
+ .where.not(parent_id: nil)
19
+ .find_by!(slug: params[:show_id])
20
+ end
21
+
22
+ def serialize_collection(collection)
23
+ collection_serializer.new(collection, include: resource_includes).serializable_hash
24
+ end
25
+
26
+ def collection_serializer
27
+ Spree::V2::Tenant::VotePackageSerializer
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,14 +2,17 @@ module SpreeCmCommissioner
2
2
  class Show < Spree::Taxon
3
3
  include SpreeCmCommissioner::StoreMetadata
4
4
 
5
- has_many :voting_credits, class_name: 'SpreeCmCommissioner::VotingCredit', as: :votable, dependent: :destroy
6
5
  belongs_to :show, class_name: 'SpreeCmCommissioner::Show', foreign_key: :parent_id, optional: true
6
+
7
+ has_many :voting_credits, class_name: 'SpreeCmCommissioner::VotingCredit', as: :votable, dependent: :destroy
7
8
  has_many :seasons, class_name: 'SpreeCmCommissioner::Show', foreign_key: :parent_id, inverse_of: :parent
8
9
  has_many :show_contestants, class_name: 'SpreeCmCommissioner::ShowContestant', inverse_of: :show, dependent: :destroy
9
10
  has_many :show_people_assignments, class_name: 'SpreeCmCommissioner::ShowPersonAssignment', dependent: :destroy
10
11
  has_many :show_people, through: :show_people_assignments, source: :show_person
11
12
  has_many :episodes, class_name: 'SpreeCmCommissioner::ShowEpisode', foreign_key: :event_id
13
+ has_many :vote_packages, -> { where(product_type: :vote_package) }, class_name: 'SpreeCmCommissioner::ShowEpisode', foreign_key: :event_id
12
14
  has_many :voting_sessions, through: :episodes, class_name: 'SpreeCmCommissioner::VotingSession'
15
+
13
16
  has_one :current_episode, -> { current_or_next_upcoming }, class_name: 'SpreeCmCommissioner::ShowEpisode', foreign_key: :event_id
14
17
  has_one :current_voting_session, through: :current_episode
15
18
 
@@ -24,6 +27,8 @@ module SpreeCmCommissioner
24
27
  SHOW_TYPES = %w[show tournament boxing].freeze
25
28
 
26
29
  # Define standard voting configuration keys
30
+ attr_accessor :claimed, :effective_free_vote_limit, :effective_free_vote_limit_type
31
+
27
32
  store_accessor :voting_config,
28
33
  :free_vote_limit,
29
34
  :free_vote_limit_type,
@@ -64,12 +69,13 @@ module SpreeCmCommissioner
64
69
  "SpreeCmCommissioner::#{type}"
65
70
  end
66
71
 
67
- def free_vote_idempotency_key(user_id:, votable_id: nil)
68
- case free_vote_limit_type
72
+ def free_vote_idempotency_key(user_id:, votable_id: nil, limit_type: nil)
73
+ effective_limit_type = limit_type || free_vote_limit_type
74
+ case effective_limit_type
69
75
  when 'per_episode' then "free_claim::episode::#{votable_id}::user::#{user_id}"
70
76
  when 'per_voting_session' then "free_claim::voting_session::#{votable_id}::user::#{user_id}"
71
77
  when 'per_show' then "free_claim::show::#{id}::user::#{user_id}"
72
- else raise ArgumentError, "Invalid free_vote_limit_type: #{free_vote_limit_type}"
78
+ else raise ArgumentError, "Invalid free_vote_limit_type: #{effective_limit_type}"
73
79
  end
74
80
  end
75
81
 
@@ -196,6 +196,18 @@ module SpreeCmCommissioner
196
196
  notification
197
197
  end
198
198
 
199
+ def free_vote_claimable?(show:, votable_id: nil)
200
+ voting_session = votable_id.present? ? SpreeCmCommissioner::VotingSession.find_by(id: votable_id, show: show) : nil
201
+ effective_config = voting_session&.effective_voting_config || show.effective_voting_config
202
+ limit_type = effective_config['free_vote_limit_type']
203
+
204
+ return false unless limit_type
205
+
206
+ resolved_votable_id = limit_type == 'per_episode' ? voting_session&.episode_id : votable_id
207
+ idempotency_key = show.free_vote_idempotency_key(user_id: id, votable_id: resolved_votable_id, limit_type: limit_type)
208
+ !voting_credit_transactions.exists?(idempotency_key: idempotency_key)
209
+ end
210
+
199
211
  def push_notificable?
200
212
  return false if device_tokens_count.blank?
201
213
 
@@ -118,6 +118,7 @@ module SpreeCmCommissioner
118
118
  }, class_name: 'Spree::Taxonomy', dependent: :destroy
119
119
 
120
120
  base.has_many :merchandise_products, -> { where(product_type: :ecommerce, event_id: nil) }, class_name: 'Spree::Product'
121
+ base.has_many :vote_packages, -> { where(product_type: :vote_package) }, class_name: 'SpreeCmCommissioner::ShowEpisode', foreign_key: :vendor_id
121
122
 
122
123
  # Create maintaining task to purge vendor related caches
123
124
  base.after_save { SpreeCmCommissioner::MaintenanceTasks::CacheInvalidation.pending.create_or_find_by(maintainable: self) }
@@ -103,12 +103,12 @@ module SpreeCmCommissioner
103
103
  end
104
104
  end
105
105
 
106
- def effective_voting_config
106
+ def effective_voting_config(show_config: nil)
107
107
  return {} unless show
108
108
 
109
- config = parse_json_config(voting_config)
110
- show_config = show.effective_voting_config
111
- config.reverse_merge(show_config || {})
109
+ show_config ||= show.effective_voting_config
110
+ session_config = parse_json_config(voting_config).compact
111
+ show_config.merge(session_config)
112
112
  end
113
113
 
114
114
  def effective_fraud_config
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module V2
3
+ module Tenant
4
+ class FreeVoteClaimSerializer < BaseSerializer
5
+ attributes :claimed, :effective_free_vote_limit, :effective_free_vote_limit_type
6
+ end
7
+ end
8
+ end
9
+ end
@@ -2,7 +2,10 @@ module Spree
2
2
  module V2
3
3
  module Tenant
4
4
  class VotePackageSerializer < BaseSerializer
5
- attributes :name, :price, :compare_at_price, :available_on, :discontinue_on, :status, :vote_credits
5
+ attributes :name, :price, :compare_at_price, :currency, :display_price,
6
+ :available_on, :discontinue_on, :status, :vote_credits
7
+
8
+ has_many :variants, serializer: Spree::V2::Tenant::VariantSerializer
6
9
  end
7
10
  end
8
11
  end
@@ -1,82 +1,57 @@
1
- # Mobile can check that where ClaimFreeVotes Screen should Appear based on the show's voting_config.
2
- # free_vote_limit and voting_config.free_vote_limit_type, then call this service to claim free votes.
3
- #
4
- # Grants free votes to a user based on the show's free_vote_limit_type:
5
- # - per_show : once per show (no extra params needed)
6
- # - per_episode : once per episode (pass votable_type: 'SpreeCmCommissioner::ShowEpisode', votable_id: episode.id)
7
- # - per_voting_session: once per voting session (pass votable_type: 'SpreeCmCommissioner::VotingSession', votable_id: voting_session.id)
1
+ # Grants free votes based on the effective config (voting session overrides show):
2
+ # - per_show : once per show (votable_id optional)
3
+ # - per_episode : once per episode (votable_id: voting_session.id)
4
+ # - per_voting_session: once per voting session (votable_id: voting_session.id)
8
5
  #
6
+ # votable_id is always a VotingSession ID. The episode is derived from it when needed.
7
+ # The credit wallet target is determined by the show's voting_credit_scope.
9
8
  # Idempotent: a unique idempotency_key on the transaction prevents double-claiming.
10
- #
11
- # Usage:
12
- # Show with per_show limit:
13
- # SpreeCmCommissioner::VotingCredits::ClaimFreeVotes.new(
14
- # show: show,
15
- # user: user,
16
- # tenant_id: 1
17
- # ).call
18
- #
19
- # Show with per_episode limit:
20
- # SpreeCmCommissioner::VotingCredits::ClaimFreeVotes.new(
21
- # show: show,
22
- # user: user,
23
- # tenant_id: 1,
24
- # votable_type: 'SpreeCmCommissioner::ShowEpisode',
25
- # votable_id: episode.id
26
- # ).call
27
- #
28
- # Show with per_voting_session limit:
29
- # SpreeCmCommissioner::VotingCredits::ClaimFreeVotes.new(
30
- # show: show,
31
- # user: user,
32
- # tenant_id: 1,
33
- # votable_type: 'SpreeCmCommissioner::VotingSession',
34
- # votable_id: voting_session.id
35
- # ).call
36
9
  module SpreeCmCommissioner
37
10
  module VotingCredits
38
11
  class ClaimFreeVotes
39
12
  prepend ::Spree::ServiceModule::Base
40
13
 
41
- attr_reader :show, :user, :tenant_id, :votable_type, :votable_id
14
+ attr_reader :show, :user, :tenant_id, :votable_id
42
15
 
43
- def initialize(show:, user:, tenant_id:, votable_type: nil, votable_id: nil)
16
+ def initialize(show:, user:, tenant_id:, votable_id: nil)
44
17
  @show = show
45
18
  @user = user
46
19
  @tenant_id = tenant_id
47
- @votable_type = votable_type
48
20
  @votable_id = votable_id
49
21
  end
50
22
 
51
- # Returns the VotingCredit after granting free votes,
52
- # or returns msg if free votes cannot be granted (e.g. limit reached).
53
- def call # rubocop:disable Metrics/AbcSize
54
- return success(nil) unless show.free_vote_limit_type
23
+ def call
24
+ return success(nil) unless effective_limit_type
55
25
 
56
- idempotency_key = show.free_vote_idempotency_key(user_id: user.id, votable_id: votable_id)
26
+ return failure(nil, 'votable_id is required for per_episode/per_voting_session') if requires_votable_id? && votable_id.blank?
57
27
 
58
- # idempotency_key is uniquely indexed faster than scoping through user.voting_transactions association.
59
- if SpreeCmCommissioner::VotingCreditTransaction.exists?(idempotency_key: idempotency_key)
60
- return failure(nil, 'Free votes have already been claimed')
61
- end
28
+ key_votable_id = effective_limit_type == 'per_episode' ? resolved_voting_session.episode_id : votable_id
29
+ idempotency_key = show.free_vote_idempotency_key(user_id: user.id, votable_id: key_votable_id, limit_type: effective_limit_type)
62
30
 
63
- amount = show.free_vote_limit.to_i
31
+ return failure(nil, 'Free votes have already been claimed') if already_claimed?(idempotency_key)
32
+
33
+ amount = effective_limit.to_i
64
34
 
65
35
  return failure(nil, "Free votes are not available for #{show.voting_credit_scope}") if amount <= 0
66
36
 
67
- case show.free_vote_limit_type
68
- when 'per_episode', 'per_voting_session'
69
- if votable_type.blank? || votable_id.blank?
70
- return failure(nil, 'votable_type and votable_id are required for per_episode/per_voting_session')
71
- end
72
- end
37
+ credit = grant_votes(amount: amount, idempotency_key: idempotency_key)
73
38
 
39
+ success(credit)
40
+ rescue ActiveRecord::RecordNotUnique
41
+ failure(nil, 'Free votes have already been claimed')
42
+ rescue StandardError => e
43
+ failure(nil, e.message)
44
+ end
45
+
46
+ private
47
+
48
+ def grant_votes(amount:, idempotency_key:)
74
49
  credit = nil
75
50
 
76
51
  ActiveRecord::Base.transaction do
77
52
  credit = user.voting_credits.active.find_or_create_by!(
78
53
  tenant_id: tenant_id,
79
- votable: votable
54
+ votable: credit_votable
80
55
  ) { |c| c.category = :promo }
81
56
 
82
57
  credit.with_lock do
@@ -90,30 +65,52 @@ module SpreeCmCommissioner
90
65
  amount: amount,
91
66
  originator: show,
92
67
  free_claim_votable_id: votable_id,
68
+ idempotency_key: idempotency_key,
93
69
  user_total_amount: credit.available_votes,
94
70
  memo: "Free claim: #{amount} votes | credit_scope: #{show.voting_credit_scope} | key: #{idempotency_key}"
95
71
  )
96
72
  end
97
73
 
98
- success(credit)
99
- rescue ActiveRecord::RecordNotUnique
100
- # Two concurrent requests can both pass the existence check before either commits.
101
- # The one that loses the unique-index race lands here — treat it as already claimed.
102
- failure(nil, 'Free votes have already been claimed')
103
- rescue StandardError => e
104
- failure(nil, e.message)
74
+ credit
105
75
  end
106
76
 
107
- # Always find votable by the show's credit_votable_type, to ensure consistency with where the credits are allocated.
108
- # Resolves through the show association to prevent claiming against unrelated votable_ids.
109
- def votable
77
+ def credit_votable
110
78
  case show.credit_votable_type
111
- when 'SpreeCmCommissioner::Show' then show
112
- when 'SpreeCmCommissioner::ShowEpisode' then show.episodes.find(votable_id)
113
- when 'SpreeCmCommissioner::VotingSession' then show.voting_sessions.find(votable_id)
79
+ when 'SpreeCmCommissioner::Show' then show
80
+ when 'SpreeCmCommissioner::ShowEpisode' then SpreeCmCommissioner::ShowEpisode.find(resolved_voting_session.episode_id)
81
+ when 'SpreeCmCommissioner::VotingSession' then resolved_voting_session
114
82
  else raise ActiveRecord::RecordNotFound, "Unknown credit_votable_type: #{show.credit_votable_type}"
115
83
  end
116
84
  end
85
+
86
+ def resolved_voting_session
87
+ @resolved_voting_session ||= SpreeCmCommissioner::VotingSession.find_by!(id: votable_id, show: show)
88
+ end
89
+
90
+ def effective_voting_config
91
+ @effective_voting_config ||=
92
+ if votable_id.present?
93
+ resolved_voting_session.effective_voting_config(show_config: show.effective_voting_config)
94
+ else
95
+ show.effective_voting_config
96
+ end
97
+ end
98
+
99
+ def effective_limit_type
100
+ effective_voting_config['free_vote_limit_type']
101
+ end
102
+
103
+ def effective_limit
104
+ effective_voting_config['free_vote_limit']
105
+ end
106
+
107
+ def requires_votable_id?
108
+ effective_limit_type.in?(%w[per_episode per_voting_session])
109
+ end
110
+
111
+ def already_claimed?(idempotency_key)
112
+ SpreeCmCommissioner::VotingCreditTransaction.exists?(idempotency_key: idempotency_key)
113
+ end
117
114
  end
118
115
  end
119
116
  end
data/config/routes.rb CHANGED
@@ -605,10 +605,11 @@ Spree::Core::Engine.add_routes do
605
605
  end
606
606
  resources :contestants, controller: :show_contestants, only: %i[show]
607
607
  resources :elimination_sessions, controller: :show_elimination_sessions, only: %i[index]
608
- resources :free_vote_claims, only: :create
608
+ resources :vote_packages, only: :index
609
609
  end
610
610
 
611
611
  resources :voting_sessions, only: [] do
612
+ resource :free_vote_claim, controller: :free_vote_claim, only: %i[show create]
612
613
  resources :voting_contestants, only: :index
613
614
  resources :voting_credits, only: :index
614
615
  resources :votes, only: %i[create index]
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.8.3.pre.pre13'.freeze
2
+ VERSION = '2.8.3.pre.pre14'.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.3.pre.pre13
4
+ version: 2.8.3.pre.pre14
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-17 00:00:00.000000000 Z
11
+ date: 2026-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -1052,7 +1052,7 @@ files:
1052
1052
  - app/controllers/spree/api/v2/tenant/customer_notifications_controller.rb
1053
1053
  - app/controllers/spree/api/v2/tenant/dynamic_field_options_controller.rb
1054
1054
  - app/controllers/spree/api/v2/tenant/episodes_controller.rb
1055
- - app/controllers/spree/api/v2/tenant/free_vote_claims_controller.rb
1055
+ - app/controllers/spree/api/v2/tenant/free_vote_claim_controller.rb
1056
1056
  - app/controllers/spree/api/v2/tenant/guests_controller.rb
1057
1057
  - app/controllers/spree/api/v2/tenant/homepage_sections_controller.rb
1058
1058
  - app/controllers/spree/api/v2/tenant/id_cards_controller.rb
@@ -1091,6 +1091,7 @@ files:
1091
1091
  - app/controllers/spree/api/v2/tenant/user_device_tokens_controller.rb
1092
1092
  - app/controllers/spree/api/v2/tenant/user_registration_with_pin_codes_controller.rb
1093
1093
  - app/controllers/spree/api/v2/tenant/vendors_controller.rb
1094
+ - app/controllers/spree/api/v2/tenant/vote_packages_controller.rb
1094
1095
  - app/controllers/spree/api/v2/tenant/votes_controller.rb
1095
1096
  - app/controllers/spree/api/v2/tenant/voting_contestants_controller.rb
1096
1097
  - app/controllers/spree/api/v2/tenant/voting_credit_transactions_controller.rb
@@ -1988,6 +1989,7 @@ files:
1988
1989
  - app/serializers/spree/v2/tenant/digital_link_serializer.rb
1989
1990
  - app/serializers/spree/v2/tenant/dynamic_field_option_serializer.rb
1990
1991
  - app/serializers/spree/v2/tenant/dynamic_field_serializer.rb
1992
+ - app/serializers/spree/v2/tenant/free_vote_claim_serializer.rb
1991
1993
  - app/serializers/spree/v2/tenant/guest_card_class_serializer.rb
1992
1994
  - app/serializers/spree/v2/tenant/guest_dynamic_field_serializer.rb
1993
1995
  - app/serializers/spree/v2/tenant/guest_serializer.rb
@@ -1,37 +0,0 @@
1
- module Spree
2
- module Api
3
- module V2
4
- module Tenant
5
- class FreeVoteClaimsController < BaseController
6
- before_action :require_spree_current_user
7
-
8
- def create
9
- show = current_vendor.shows.find(params[:show_id])
10
-
11
- result = SpreeCmCommissioner::VotingCredits::ClaimFreeVotes.new(
12
- show: show,
13
- user: spree_current_user,
14
- tenant_id: MultiTenant.current_tenant_id,
15
- votable_type: params[:votable_type],
16
- votable_id: params[:votable_id]
17
- ).call
18
-
19
- if result.failure?
20
- render_error_payload(result.error.to_s)
21
- elsif result.value.nil?
22
- head :no_content
23
- else
24
- render_serialized_payload { serialize_resource(result.value) }
25
- end
26
- end
27
-
28
- private
29
-
30
- def resource_serializer
31
- Spree::V2::Tenant::VotingCreditSerializer
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end