spree_cm_commissioner 2.8.3.pre.pre4 → 2.8.3.pre.pre5

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.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +2 -1
  3. data/.gitignore +2 -0
  4. data/Gemfile.lock +1 -1
  5. data/app/controllers/spree/api/v2/tenant/episodes_controller.rb +38 -0
  6. data/app/controllers/spree/api/v2/tenant/shows_controller.rb +3 -1
  7. data/app/controllers/spree/api/v2/tenant/voting_contestants_controller.rb +4 -1
  8. data/app/models/spree_cm_commissioner/taxon_decorator.rb +3 -1
  9. data/app/models/spree_cm_commissioner/voting_contestant.rb +28 -0
  10. data/app/models/spree_cm_commissioner/voting_session.rb +1 -1
  11. data/app/serializers/spree/v2/tenant/campaign_serializer.rb +4 -0
  12. data/app/serializers/spree/v2/tenant/show_episode_serializer.rb +2 -0
  13. data/app/serializers/spree/v2/tenant/show_serializer.rb +2 -0
  14. data/app/serializers/spree/v2/tenant/voting_contestant_serializer.rb +4 -3
  15. data/app/serializers/spree/v2/tenant/voting_session_serializer.rb +3 -1
  16. data/app/services/spree_cm_commissioner/advertisements/sorted_advertisements.rb +61 -0
  17. data/app/services/spree_cm_commissioner/voting_contestants/advancer.rb +5 -4
  18. data/app/services/spree_cm_commissioner/voting_contestants/assigner.rb +11 -3
  19. data/app/services/spree_cm_commissioner/voting_sessions/finalize.rb +33 -3
  20. data/config/locales/en.yml +5 -0
  21. data/config/locales/km.yml +10 -0
  22. data/config/routes.rb +1 -0
  23. data/db/migrate/20260608000000_add_display_to_cm_voting_sessions.rb +5 -0
  24. data/lib/spree_cm_commissioner/version.rb +1 -1
  25. metadata +5 -3
  26. data/app/services/spree_cm_commissioner/advertisements/weight_validator.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8bd441927a9bb64c823a003b5a1f0ecbb7591a90de805f3e3edfdcaaa48180ba
4
- data.tar.gz: 3a5b5097a6b2daf1edf5d2b8a55d3d87cc6de0127022b3859817ffe3994ae75d
3
+ metadata.gz: 145ade12d325f15a3f1963b4fd75e56093cbb182e1f3ac115d0101c0c6cad285
4
+ data.tar.gz: e47239cbfa8a359ce7e0b0ed2f73e5270876f679d3047a2d88be89493683004a
5
5
  SHA512:
6
- metadata.gz: 37bcbc34200db68f4647ff333e5e923939b42d46f669ea04b721be9652f7280e12f047ad9541b538054723cd16a63350f62cc586d978fa8ad94a7e4625695ca9
7
- data.tar.gz: ab2ee51768d3b2d9c7b1145120b0d70defa14c6cbb6ea7c9a550a11f7ec2496b3674bdbba48f0a37f452c11f7fc5c7ba2d567eacd4f9de0380a18c06ce1fe68e
6
+ metadata.gz: '06134389566fe49ae2dfe58a65919130cc57dcf43694d33af480d2930d517cfbba1ba97c7714f0b011cdb063a9a7d1611dc205fdcce1cf7f77fd4618c6d9ffd9'
7
+ data.tar.gz: 60d6491f501f2baa87fb971bee6a906f3180425d9f72917462adc2d266c0f3302a44bf532c39fe9885b3b965d7c94fdae9b158d0c7bc9263879b2d2e43197e23
@@ -164,7 +164,8 @@ jobs:
164
164
  env:
165
165
  DATABASE_URL: postgres://myuser:mypassword@localhost:5432/test_db
166
166
  run: |
167
- bundle exec brakeman --no-exit-on-warn
167
+ # Using --no-threads to bypass a known Ruby 3.2.0 GC segmentation fault in CI.
168
+ bundle exec brakeman --no-threads --no-exit-on-warn
168
169
 
169
170
  test:
170
171
  needs: [setup]
data/.gitignore CHANGED
@@ -38,3 +38,5 @@ dump.rdb
38
38
  .code-review-graph/
39
39
  .cursorrules
40
40
  .claude
41
+ CLAUDE.md
42
+ AGENTS.md
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.pre4)
37
+ spree_cm_commissioner (2.8.3.pre.pre5)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -0,0 +1,38 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class EpisodesController < BaseController
6
+ private
7
+
8
+ def model_class
9
+ SpreeCmCommissioner::ShowEpisode
10
+ end
11
+
12
+ def resource
13
+ @resource ||= scope.find(params[:id])
14
+ end
15
+
16
+ def scope
17
+ current_season.episodes.includes(:voting_sessions)
18
+ end
19
+
20
+ def current_season
21
+ @current_season ||= SpreeCmCommissioner::Show
22
+ .where(vendor_id: @tenant.vendors.select(:id))
23
+ .where.not(parent_id: nil)
24
+ .find_by!(slug: params[:show_id])
25
+ end
26
+
27
+ def resource_serializer
28
+ Spree::V2::Tenant::ShowEpisodeSerializer
29
+ end
30
+
31
+ def collection_serializer
32
+ Spree::V2::Tenant::ShowEpisodeSerializer
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -20,7 +20,9 @@ module Spree
20
20
  return model_class.none if vendor_ids.blank?
21
21
 
22
22
  model_class.where(vendor_id: vendor_ids, preview: false)
23
- .includes(:parent, :app_banner, episodes: :voting_sessions, seasons: { episodes: :voting_sessions })
23
+ .includes(:parent, :app_banner, show_people: :show_person_images,
24
+ episodes: :voting_sessions, seasons: { episodes: :voting_sessions }
25
+ )
24
26
  end
25
27
 
26
28
  # override
@@ -9,7 +9,10 @@ module Spree
9
9
  def collection
10
10
  @collection ||= @voting_session
11
11
  .voting_contestants
12
- .includes(show_contestant: :show_contestant_images)
12
+ .includes(:voting_session, :show_contestant,
13
+ :show_contestant_images, :show_contestant_videos,
14
+ :advanced_to, :advanced_from
15
+ )
13
16
  .order(:id)
14
17
  end
15
18
 
@@ -86,7 +86,9 @@ module SpreeCmCommissioner
86
86
  base.scope :events, -> { where(taxonomy_id: Spree::Taxonomy.events.id, depth: 1) }
87
87
  base.scope :campaigns, -> { where(taxonomy_id: Spree::Taxonomy.ads.id, depth: 1) }
88
88
 
89
- base.has_many :advertisements, -> { advertisement }, through: :classifications, class_name: 'Spree::Product', source: :product
89
+ base.has_many :advertisements, lambda {
90
+ advertisement.where(deleted_at: nil)
91
+ }, through: :classifications, class_name: 'Spree::Product', source: :product
90
92
  base.has_many :agencies, class_name: 'SpreeCmCommissioner::Agency', foreign_key: :agency_category_id
91
93
  base.has_many :preview_roles, class_name: 'SpreeCmCommissioner::PreviewRole', as: :previewable
92
94
 
@@ -19,6 +19,8 @@ module SpreeCmCommissioner
19
19
  end
20
20
 
21
21
  before_destroy :prevent_if_last_contestant_in_parent_session
22
+ before_destroy :prevent_if_advanced_from_session
23
+ before_destroy :prevent_if_pre_vote_session
22
24
 
23
25
  validates :eliminated_via, presence: true, if: :eliminated?
24
26
 
@@ -45,6 +47,14 @@ module SpreeCmCommissioner
45
47
  (special_rules || {})['type']
46
48
  end
47
49
 
50
+ def advanced_from_name
51
+ advanced_from&.name
52
+ end
53
+
54
+ def advanced_to_name
55
+ advanced_to&.name
56
+ end
57
+
48
58
  private
49
59
 
50
60
  # Prevent removing the last contestant from a session that is referenced as a parent
@@ -57,5 +67,23 @@ module SpreeCmCommissioner
57
67
  errors.add(:base, :last_contestant_in_parent_session, session_name: voting_session.name)
58
68
  throw :abort
59
69
  end
70
+
71
+ def prevent_if_pre_vote_session
72
+ return unless voting_session.pre_vote?
73
+
74
+ errors.add(:base, :pre_vote_session)
75
+ throw :abort
76
+ end
77
+
78
+ # Prevent direct destroy when this record was advanced here from a previous session.
79
+ # Destroying it directly would leave the source session's advanced_to pointer dangling.
80
+ # The correct removal path is to clear advanced_to on the source VotingContestant,
81
+ # which triggers the Advancer service to clean up this record atomically.
82
+ def prevent_if_advanced_from_session
83
+ return if advanced_from_id.blank?
84
+
85
+ errors.add(:base, :advanced_from_session)
86
+ throw :abort
87
+ end
60
88
  end
61
89
  end
@@ -119,7 +119,7 @@ module SpreeCmCommissioner
119
119
  end
120
120
 
121
121
  def can_vote?
122
- enabled? && Time.zone.now.between?(opens_at, closes_at)
122
+ enabled? && Time.zone.now.between?(opens_at, closes_at) && !manual_advance?
123
123
  end
124
124
 
125
125
  # Returns the next advanceable session within the same episode (by position).
@@ -6,6 +6,10 @@ module Spree
6
6
 
7
7
  attributes :name, :from_date, :to_date
8
8
 
9
+ attribute :sorted_ads_ids do |campaign|
10
+ SpreeCmCommissioner::Advertisements::SortedAdvertisements.call(campaign: campaign).value
11
+ end
12
+
9
13
  has_many :advertisements, serializer: Spree::V2::Tenant::AdvertisementSerializer
10
14
  end
11
15
  end
@@ -11,6 +11,8 @@ module Spree
11
11
  'upcoming'
12
12
  end
13
13
  end
14
+
15
+ has_many :voting_sessions, serializer: Spree::V2::Tenant::VotingSessionSerializer
14
16
  end
15
17
  end
16
18
  end
@@ -9,6 +9,8 @@ module Spree
9
9
 
10
10
  attribute :name, &:display_name
11
11
 
12
+ has_many :show_people, serializer: Spree::V2::Tenant::ShowPersonSerializer
13
+ has_many :episodes, serializer: Spree::V2::Tenant::ShowEpisodeSerializer
12
14
  has_many :voting_sessions, serializer: Spree::V2::Tenant::VotingSessionSerializer
13
15
  has_one :current_episode, serializer: Spree::V2::Tenant::ShowEpisodeSerializer
14
16
  has_one :current_voting_session, serializer: Spree::V2::Tenant::VotingSessionSerializer
@@ -4,9 +4,9 @@ module Spree
4
4
  class VotingContestantSerializer < BaseSerializer
5
5
  set_type :voting_contestant
6
6
 
7
- attributes :show_id, :show_contestant_id, :name, :contestant_number, :vote_number, :category, :gender,
8
- :vote_count, :eliminated, :eliminated_via, :eliminated_at, :advanced_to_type, :advanced_to_id, :special_rules,
9
- :created_at, :updated_at, :advanced_from_type, :advanced_from_id, :unique_voter_count, :preferences
7
+ attributes :show_contestant_id, :name, :contestant_number, :vote_number, :category, :gender,
8
+ :eliminated, :eliminated_via, :vote_count, :unique_voter_count, :special_rules,
9
+ :created_at, :updated_at, :advanced_from_name, :advanced_to_name, :confirmed_rank
10
10
 
11
11
  attribute :voting_session_id, &:voting_session_id
12
12
 
@@ -16,6 +16,7 @@ module Spree
16
16
 
17
17
  has_many :show_contestant_images, serializer: ::Spree::V2::Tenant::AssetSerializer
18
18
  has_many :show_contestant_videos, serializer: ::Spree::V2::Tenant::VideoSerializer
19
+ belongs_to :voting_session, serializer: ::Spree::V2::Tenant::VotingSessionSerializer
19
20
  end
20
21
  end
21
22
  end
@@ -4,7 +4,7 @@ module Spree
4
4
  class VotingSessionSerializer < BaseSerializer
5
5
  attributes :opens_at, :name, :closes_at, :status, :session_type, :live_stream_enabled,
6
6
  :live_stream_thumbnail, :live_stream_title, :live_stream_description,
7
- :position, :fraud_config
7
+ :position, :fraud_config, :display, :public_metadata
8
8
 
9
9
  attribute :live_stream_url, &:embedded_live_stream_url
10
10
  attribute :can_vote, &:can_vote?
@@ -12,6 +12,8 @@ module Spree
12
12
  belongs_to :episode, serializer: Spree::V2::Tenant::ShowEpisodeSerializer
13
13
  belongs_to :show, serializer: Spree::V2::Tenant::ShowSerializer
14
14
 
15
+ has_many :voting_contestants, serializer: ::Spree::V2::Tenant::VotingContestantSerializer
16
+
15
17
  has_many :eliminated_voting_contestants, serializer: ::Spree::V2::Tenant::VotingContestantSerializer
16
18
  end
17
19
  end
@@ -0,0 +1,61 @@
1
+ # Builds a weighted round-robin slot sequence for one ad rotation cycle.
2
+ #
3
+ # Ads are interleaved so the same ad never repeats until all others have appeared.
4
+ # Each round emits one slot per ad in descending weight order. Higher weight =
5
+ # appears first in every round + appears in more rounds = displayed more often.
6
+ #
7
+ # Usage:
8
+ # result = SpreeCmCommissioner::Advertisements::SortedAdvertisements.call(campaign: campaign)
9
+ #
10
+ # result.success? # => true
11
+ # result.value # => [469, 470, 468, 469, 470, 468, 469, 470, ...]
12
+ #
13
+ # Example: Bachuss weight=5, Feranado weight=5, Ganzberg weight=4 (14 slots total)
14
+ # Round 1: Bachuss, Feranado, Ganzberg
15
+ # Round 2: Bachuss, Feranado, Ganzberg
16
+ # Round 3: Bachuss, Feranado, Ganzberg
17
+ # Round 4: Bachuss, Feranado, Ganzberg
18
+ # Round 5: Bachuss, Feranado ← Ganzberg exhausted
19
+ #
20
+ # Bachuss → 5/14 ≈ 36% of opens
21
+ # Feranado → 5/14 ≈ 36% of opens
22
+ # Ganzberg → 4/14 ≈ 28% of opens
23
+ module SpreeCmCommissioner
24
+ module Advertisements
25
+ class SortedAdvertisements
26
+ prepend ::Spree::ServiceModule::Base
27
+
28
+ def call(campaign:)
29
+ return success([]) unless campaign
30
+
31
+ ads = campaign.advertisements.to_a
32
+ return success([]) if ads.empty?
33
+
34
+ success(weighted_round_robin(ads))
35
+ end
36
+
37
+ private
38
+
39
+ # Interleaves ads so no ID repeats until all others have appeared.
40
+ # Each round emits one slot per ad (highest weight first); ads with
41
+ # remaining quota continue into the next round until all slots are filled.
42
+ def weighted_round_robin(ads)
43
+ pool = ads
44
+ .map { |ad| { id: ad.id, remaining: ad.advertise_weight.to_i } }
45
+ .sort_by { |entry| -entry[:remaining] }
46
+
47
+ slots = []
48
+ until pool.all? { |entry| entry[:remaining].zero? }
49
+ pool.each do |entry|
50
+ next if entry[:remaining].zero?
51
+
52
+ slots << entry[:id]
53
+ entry[:remaining] -= 1
54
+ end
55
+ end
56
+
57
+ slots
58
+ end
59
+ end
60
+ end
61
+ end
@@ -190,8 +190,9 @@ module SpreeCmCommissioner
190
190
  # e.g. vc_b.vote_count = 5 → return false
191
191
  return false if linked.vote_count.to_i.positive?
192
192
 
193
- # Safe to destroy (0 votes); signal that the pointer can be cleared.
194
- # e.g. vc_b.vote_count = 0 destroy vc_b, return true
193
+ # Clear back-reference before destroy so prevent_if_advanced_from_session doesn't abort.
194
+ # The Advancer manages both sides atomically, so the source pointer is cleaned up by the caller.
195
+ linked.update!(advanced_from_type: nil, advanced_from_id: nil)
195
196
  linked.destroy!
196
197
  true
197
198
  end
@@ -235,8 +236,8 @@ module SpreeCmCommissioner
235
236
  end
236
237
  end
237
238
 
238
- # Destroy this node after its subtree is cleaned up (post-order).
239
- # e.g. destroy vc_b after vc_c was already removed
239
+ # Clear back-reference before destroy so prevent_if_advanced_from_session doesn't abort.
240
+ linked_row.update!(advanced_from_type: nil, advanced_from_id: nil)
240
241
  linked_row.destroy!
241
242
  end
242
243
 
@@ -21,9 +21,17 @@ module SpreeCmCommissioner
21
21
  target_ids = @show_contestant_ids.uniq
22
22
  return if target_ids.empty?
23
23
 
24
- @episode.season.show_contestants.where(id: target_ids).find_each do |show_contestant|
25
- @voting_session.voting_contestants.find_or_create_by!(show_contestant: show_contestant) do |voting_contestant|
26
- voting_contestant.show_id = @voting_session.show_id
24
+ ApplicationRecord.transaction do
25
+ @episode.season.show_contestants.where(id: target_ids).find_each do |show_contestant|
26
+ if @voting_session.save_vote? && !show_contestant.eliminated?
27
+ raise I18n.t('spree_cm_commissioner.voting_contestants.assigner.not_eliminated',
28
+ name: show_contestant.name, status: show_contestant.status
29
+ )
30
+ end
31
+
32
+ @voting_session.voting_contestants.find_or_create_by!(show_contestant: show_contestant) do |voting_contestant|
33
+ voting_contestant.show_id = @voting_session.show_id
34
+ end
27
35
  end
28
36
  end
29
37
  end
@@ -3,10 +3,18 @@ module SpreeCmCommissioner
3
3
  class Finalize
4
4
  prepend ::Spree::ServiceModule::Base
5
5
 
6
- def call(voting_session:)
6
+ def call(voting_session:, save_advance_to_session_id: nil)
7
+ @save_advance_to_session_id = save_advance_to_session_id
8
+
7
9
  ApplicationRecord.transaction do
8
10
  validate_confirmed_ranks!(voting_session)
9
- advance_confirmed_winners(voting_session) if next_session?(voting_session)
11
+
12
+ if voting_session.save_vote?
13
+ restore_and_advance_saved_contestants(voting_session)
14
+ elsif next_session?(voting_session)
15
+ advance_confirmed_winners(voting_session)
16
+ end
17
+
10
18
  voting_session.update!(status: :finalized)
11
19
  success(voting_session: voting_session)
12
20
  end
@@ -17,7 +25,7 @@ module SpreeCmCommissioner
17
25
  private
18
26
 
19
27
  def validate_confirmed_ranks!(voting_session)
20
- if next_session?(voting_session)
28
+ if voting_session.save_vote? || next_session?(voting_session)
21
29
  raise I18n.t('voting.errors.no_winners_confirmed') if voting_session.voting_contestants.where.not(confirmed_rank: nil).none?
22
30
  else
23
31
  unranked = voting_session.voting_contestants.where(eliminated: false, confirmed_rank: nil)
@@ -25,6 +33,27 @@ module SpreeCmCommissioner
25
33
  end
26
34
  end
27
35
 
36
+ def restore_and_advance_saved_contestants(voting_session)
37
+ target_session = SpreeCmCommissioner::VotingSession.find_by(id: @save_advance_to_session_id)
38
+ raise I18n.t('voting.errors.save_advance_to_session_required') if target_session.nil?
39
+
40
+ voting_session.voting_contestants.each do |vc|
41
+ if vc.confirmed_rank.present?
42
+ vc.show_contestant.update!(status: :active)
43
+ SpreeCmCommissioner::VotingContestants::Advancer.call(
44
+ voting_contestant: vc,
45
+ attributes: { advanced_to: target_session }
46
+ )
47
+ else
48
+ SpreeCmCommissioner::VotingContestants::Advancer.call(
49
+ voting_contestant: vc,
50
+ attributes: { eliminated: true, eliminated_via: :instant_save_lost, advanced_to_type: nil, advanced_to_id: nil }
51
+ )
52
+ # ShowContestant#status already :eliminated — no change needed
53
+ end
54
+ end
55
+ end
56
+
28
57
  # Re-syncs advancement on every call — handles first finalize and re-finalize.
29
58
  # Contestants without a confirmed_rank get their advanced_to cleared.
30
59
  def advance_confirmed_winners(voting_session)
@@ -48,6 +77,7 @@ module SpreeCmCommissioner
48
77
  voting_contestant: voting_contestant,
49
78
  attributes: { eliminated: true, eliminated_via: :public_vote, advanced_to_type: nil, advanced_to_id: nil }
50
79
  )
80
+ voting_contestant.show_contestant.update!(status: :eliminated)
51
81
  end
52
82
  end
53
83
  end
@@ -557,6 +557,8 @@ en:
557
557
  attributes:
558
558
  base:
559
559
  last_contestant_in_parent_session: 'cannot remove the last contestant from "%{session_name}" because child sessions depend on it'
560
+ advanced_from_session: 'was advanced from a previous session and cannot be removed directly. Clear the advancement in the source session instead.'
561
+ pre_vote_session: 'cannot be removed from a pre-vote session.'
560
562
  spree_cm_commissioner/tenant:
561
563
  attributes:
562
564
  host:
@@ -777,6 +779,9 @@ en:
777
779
  verification_failed: "Human verification failed"
778
780
  service_unavailable: "Verification service is temporarily unavailable"
779
781
  spree_cm_commissioner:
782
+ voting_contestants:
783
+ assigner:
784
+ not_eliminated: "Only eliminated contestants can be added to a Save Contestants session. %{name} is %{status}."
780
785
  voting_leaderboards:
781
786
  combined_result:
782
787
  pre_vote_not_finalized: "Cannot combine results: Pre-Vote sessions not yet finalized: %{names}"
@@ -419,6 +419,12 @@ km:
419
419
  must_not_exceed_parent: "មិនអាចក្រោយ closes_at របស់ session មេ"
420
420
  parent_id:
421
421
  must_have_contestants: '"%{name}" ត្រូវតែមានអ្នកចូលប្រកួត'
422
+ spree_cm_commissioner/voting_contestant:
423
+ attributes:
424
+ base:
425
+ last_contestant_in_parent_session: 'មិនអាចដកអ្នកចូលប្រកួតចុងក្រោយពី "%{session_name}" ព្រោះ session កូនពឹងផ្អែកលើវា'
426
+ advanced_from_session: 'ត្រូវបានដំឡើងពី session មុន ហើយមិនអាចដកចេញដោយផ្ទាល់បានទេ។ សូមលុបការដំឡើងនៅក្នុង session ប្រភពជំនួសវិញ។'
427
+ pre_vote_session: 'មិនអាចដកចេញពី pre-vote session បានទេ។'
422
428
  spree_cm_commissioner/tenant:
423
429
  attributes:
424
430
  host:
@@ -612,6 +618,10 @@ km:
612
618
  token_missing: "សញ្ញាណផ្ទៀងផ្ទាត់គឺបាត់"
613
619
  verification_failed: "ការផ្ទៀងផ្ទាត់បានបរាជ័យ"
614
620
  service_unavailable: "សេវាកម្មពេលនេះមិនមានការផ្ទៀងផ្ទាត់"
621
+ spree_cm_commissioner:
622
+ voting_contestants:
623
+ assigner:
624
+ not_eliminated: "តែអ្នកចូលប្រកួតដែលត្រូវបានលុបចោលប៉ុណ្ណោះអាចបន្ថែមទៅ Save Contestants session បាន។ %{name} មានស្ថានភាព %{status}។"
615
625
  voting:
616
626
  errors:
617
627
  no_winners_confirmed: "មិនមានអ្នកឈ្នះត្រូវបានបញ្ជាក់"
data/config/routes.rb CHANGED
@@ -589,6 +589,7 @@ Spree::Core::Engine.add_routes do
589
589
  resources :waiting_room_sessions, only: :create
590
590
 
591
591
  resources :shows, only: [:show] do
592
+ resources :episodes, only: %i[index show]
592
593
  resources :people, controller: :show_people, only: %i[index] do
593
594
  resources :assignments, controller: :show_person_assignments, only: %i[index]
594
595
  end
@@ -0,0 +1,5 @@
1
+ class AddDisplayToCmVotingSessions < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :cm_voting_sessions, :display, :boolean, default: true, null: false, if_not_exists: true
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.8.3.pre.pre4'.freeze
2
+ VERSION = '2.8.3.pre.pre5'.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.pre4
4
+ version: 2.8.3.pre.pre5
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-08 00:00:00.000000000 Z
11
+ date: 2026-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -1045,6 +1045,7 @@ files:
1045
1045
  - app/controllers/spree/api/v2/tenant/cms_pages_controller.rb
1046
1046
  - app/controllers/spree/api/v2/tenant/customer_notifications_controller.rb
1047
1047
  - app/controllers/spree/api/v2/tenant/dynamic_field_options_controller.rb
1048
+ - app/controllers/spree/api/v2/tenant/episodes_controller.rb
1048
1049
  - app/controllers/spree/api/v2/tenant/free_vote_claims_controller.rb
1049
1050
  - app/controllers/spree/api/v2/tenant/guests_controller.rb
1050
1051
  - app/controllers/spree/api/v2/tenant/homepage_sections_controller.rb
@@ -2102,7 +2103,7 @@ files:
2102
2103
  - app/services/sms_adapter/base.rb
2103
2104
  - app/services/sms_adapter/plasgate.rb
2104
2105
  - app/services/sms_adapter/twillio.rb
2105
- - app/services/spree_cm_commissioner/advertisements/weight_validator.rb
2106
+ - app/services/spree_cm_commissioner/advertisements/sorted_advertisements.rb
2106
2107
  - app/services/spree_cm_commissioner/aes_encryption_service.rb
2107
2108
  - app/services/spree_cm_commissioner/agency_categories/create.rb
2108
2109
  - app/services/spree_cm_commissioner/agency_users/add.rb
@@ -3282,6 +3283,7 @@ files:
3282
3283
  - db/migrate/20260527062005_add_eliminated_at_to_cm_show_contestants.rb
3283
3284
  - db/migrate/20260603063652_add_parent_to_voting_sessions.rb
3284
3285
  - db/migrate/20260603090000_add_session_type_to_cm_voting_sessions.rb
3286
+ - db/migrate/20260608000000_add_display_to_cm_voting_sessions.rb
3285
3287
  - docker-compose.yml
3286
3288
  - docs/api/scoped-access-token-endpoints.md
3287
3289
  - docs/option_types/attr_types.md
@@ -1,43 +0,0 @@
1
- # Validates that adding or updating an advertisement keeps the campaign total within 100.
2
- #
3
- # Usage:
4
- # # on create
5
- # result = SpreeCmCommissioner::Advertisements::WeightValidator.call(
6
- # campaign: campaign,
7
- # new_weight: 40
8
- # )
9
- #
10
- # # on update (exclude current ad from total)
11
- # result = SpreeCmCommissioner::Advertisements::WeightValidator.call(
12
- # campaign: campaign,
13
- # new_weight: 40,
14
- # exclude_id: advertisement.id
15
- # )
16
- #
17
- # result.success? # => true
18
- # result.error.value # => "Campaign weights must not exceed 100, got 130"
19
- #
20
- # Rules:
21
- # - Skip validation if campaign is nil
22
- # - All other ads in the campaign + new_weight must not exceed 100
23
- module SpreeCmCommissioner
24
- module Advertisements
25
- class WeightValidator
26
- prepend ::Spree::ServiceModule::Base
27
-
28
- def call(campaign:, new_weight:, exclude_id: nil)
29
- return success(campaign) unless campaign
30
-
31
- scope = campaign.advertisements
32
- scope = scope.where.not(id: exclude_id) if exclude_id
33
-
34
- existing_total = scope.sum { |ad| ad.advertise_weight.to_i }
35
- total = existing_total + new_weight.to_i
36
-
37
- return success(campaign) if total <= 100
38
-
39
- failure(nil, "Campaign weights must not exceed 100, got #{total}")
40
- end
41
- end
42
- end
43
- end