spree_cm_commissioner 2.8.16 → 2.9.0

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 (178) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +2 -0
  5. data/app/controllers/spree/admin/homepage_section_controller.rb +4 -1
  6. data/app/controllers/spree/admin/taxons_controller_decorator.rb +19 -0
  7. data/app/controllers/spree/api/v2/storefront/homepage_sections_controller.rb +1 -0
  8. data/app/controllers/spree/api/v2/storefront/preview_products_controller.rb +48 -0
  9. data/app/controllers/spree/api/v2/storefront/preview_sections_controller.rb +27 -0
  10. data/app/controllers/spree/api/v2/storefront/preview_taxons_controller.rb +18 -0
  11. data/app/controllers/spree/api/v2/storefront/products_controller_decorator.rb +15 -0
  12. data/app/controllers/spree/api/v2/storefront/taxons_controller_decorator.rb +15 -0
  13. data/app/controllers/spree/api/v2/tenant/ad_campaigns_controller.rb +25 -0
  14. data/app/controllers/spree/api/v2/tenant/base_controller.rb +4 -0
  15. data/app/controllers/spree/api/v2/tenant/episodes_controller.rb +38 -0
  16. data/app/controllers/spree/api/v2/tenant/homepage_sections_controller.rb +1 -0
  17. data/app/controllers/spree/api/v2/tenant/preview_products_controller.rb +47 -0
  18. data/app/controllers/spree/api/v2/tenant/preview_sections_controller.rb +26 -0
  19. data/app/controllers/spree/api/v2/tenant/preview_shows_controller.rb +19 -0
  20. data/app/controllers/spree/api/v2/tenant/preview_taxons_controller.rb +19 -0
  21. data/app/controllers/spree/api/v2/tenant/products_controller.rb +1 -1
  22. data/app/controllers/spree/api/v2/tenant/show_contestants_controller.rb +52 -0
  23. data/app/controllers/spree/api/v2/tenant/show_elimination_sessions_controller.rb +57 -0
  24. data/app/controllers/spree/api/v2/tenant/show_people_controller.rb +49 -0
  25. data/app/controllers/spree/api/v2/tenant/show_person_assignments_controller.rb +36 -0
  26. data/app/controllers/spree/api/v2/tenant/shows_controller.rb +36 -0
  27. data/app/controllers/spree/api/v2/tenant/taxons_controller.rb +1 -1
  28. data/app/controllers/spree/api/v2/tenant/vote_packages_controller.rb +81 -0
  29. data/app/controllers/spree/api/v2/tenant/votes_controller.rb +94 -0
  30. data/app/controllers/spree/api/v2/tenant/voting_contestants_controller.rb +43 -0
  31. data/app/controllers/spree/api/v2/tenant/voting_credit_transactions_controller.rb +41 -0
  32. data/app/controllers/spree/api/v2/tenant/voting_credits_controller.rb +31 -0
  33. data/app/controllers/spree_cm_commissioner/admin/products_controller_decorator.rb +19 -0
  34. data/app/finders/spree_cm_commissioner/events/find_matches.rb +1 -0
  35. data/app/helpers/spree_cm_commissioner/admin/homepage_segment_helper.rb +2 -0
  36. data/app/jobs/spree_cm_commissioner/show_contestants/import_job.rb +9 -0
  37. data/app/jobs/spree_cm_commissioner/vote_fraud_event_job.rb +9 -0
  38. data/app/jobs/spree_cm_commissioner/voting_credit_allocation_job.rb +10 -0
  39. data/app/jobs/spree_cm_commissioner/voting_credit_de_allocation_job.rb +10 -0
  40. data/app/models/concerns/spree_cm_commissioner/homepage_section_bitwise.rb +2 -1
  41. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +2 -1
  42. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +11 -1
  43. data/app/models/concerns/spree_cm_commissioner/product_type.rb +1 -1
  44. data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +3 -0
  45. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +3 -1
  46. data/app/models/concerns/spree_cm_commissioner/voting_session_live_stream_concern.rb +41 -0
  47. data/app/models/spree_cm_commissioner/claim.rb +27 -0
  48. data/app/models/spree_cm_commissioner/import.rb +1 -1
  49. data/app/models/spree_cm_commissioner/imports/import_contestant.rb +7 -0
  50. data/app/models/spree_cm_commissioner/line_item_decorator.rb +26 -0
  51. data/app/models/spree_cm_commissioner/maintenance_tasks/voting_session.rb +36 -0
  52. data/app/models/spree_cm_commissioner/preview_role.rb +8 -0
  53. data/app/models/spree_cm_commissioner/product_decorator.rb +52 -0
  54. data/app/models/spree_cm_commissioner/role_decorator.rb +4 -1
  55. data/app/models/spree_cm_commissioner/role_user_decorator.rb +8 -0
  56. data/app/models/spree_cm_commissioner/show.rb +171 -0
  57. data/app/models/spree_cm_commissioner/show_contestant.rb +39 -0
  58. data/app/models/spree_cm_commissioner/show_contestant_image.rb +11 -0
  59. data/app/models/spree_cm_commissioner/show_contestant_video.rb +15 -0
  60. data/app/models/spree_cm_commissioner/show_episode.rb +122 -0
  61. data/app/models/spree_cm_commissioner/show_person.rb +15 -0
  62. data/app/models/spree_cm_commissioner/show_person_assignment.rb +21 -0
  63. data/app/models/spree_cm_commissioner/show_person_image.rb +11 -0
  64. data/app/models/spree_cm_commissioner/taxon_decorator.rb +23 -0
  65. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +19 -1
  66. data/app/models/spree_cm_commissioner/tenant.rb +9 -0
  67. data/app/models/spree_cm_commissioner/user_decorator.rb +5 -0
  68. data/app/models/spree_cm_commissioner/variant_decorator.rb +4 -0
  69. data/app/models/spree_cm_commissioner/variant_options.rb +4 -0
  70. data/app/models/spree_cm_commissioner/vendor_decorator.rb +5 -0
  71. data/app/models/spree_cm_commissioner/vote.rb +16 -0
  72. data/app/models/spree_cm_commissioner/vote_fraud_event.rb +20 -0
  73. data/app/models/spree_cm_commissioner/voting_contestant.rb +100 -0
  74. data/app/models/spree_cm_commissioner/voting_credit.rb +84 -0
  75. data/app/models/spree_cm_commissioner/voting_credit_transaction.rb +55 -0
  76. data/app/models/spree_cm_commissioner/voting_session.rb +217 -0
  77. data/app/models/spree_cm_commissioner/voting_session_stat.rb +8 -0
  78. data/app/overrides/spree/admin/products/_form/preview_checkbox.html.erb.deface +9 -0
  79. data/app/overrides/spree/admin/taxons/_form/preview_checkbox.html.erb.deface +7 -0
  80. data/app/serializers/spree/v2/storefront/homepage_section_serializer.rb +1 -1
  81. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +2 -1
  82. data/app/serializers/spree/v2/storefront/role_serializer.rb +1 -1
  83. data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +2 -1
  84. data/app/serializers/spree/v2/tenant/ad_campaign_serializer.rb +13 -0
  85. data/app/serializers/spree/v2/tenant/advertisement_serializer.rb +11 -0
  86. data/app/serializers/spree/v2/tenant/homepage_section_serializer.rb +1 -1
  87. data/app/serializers/spree/v2/tenant/role_serializer.rb +1 -1
  88. data/app/serializers/spree/v2/tenant/show_contestant_serializer.rb +21 -0
  89. data/app/serializers/spree/v2/tenant/show_episode_serializer.rb +20 -0
  90. data/app/serializers/spree/v2/tenant/show_parent_serializer.rb +13 -0
  91. data/app/serializers/spree/v2/tenant/show_person_assignment_serializer.rb +16 -0
  92. data/app/serializers/spree/v2/tenant/show_person_serializer.rb +13 -0
  93. data/app/serializers/spree/v2/tenant/show_serializer.rb +23 -0
  94. data/app/serializers/spree/v2/tenant/video_serializer.rb +9 -0
  95. data/app/serializers/spree/v2/tenant/vote_package_serializer.rb +15 -0
  96. data/app/serializers/spree/v2/tenant/vote_serializer.rb +14 -0
  97. data/app/serializers/spree/v2/tenant/voting_contestant_serializer.rb +23 -0
  98. data/app/serializers/spree/v2/tenant/voting_credit_serializer.rb +10 -0
  99. data/app/serializers/spree/v2/tenant/voting_credit_transaction_serializer.rb +14 -0
  100. data/app/serializers/spree/v2/tenant/voting_session_serializer.rb +21 -0
  101. data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +12 -0
  102. data/app/services/spree_cm_commissioner/fraud_check.rb +308 -0
  103. data/app/services/spree_cm_commissioner/imports/contestants/create.rb +153 -0
  104. data/app/services/spree_cm_commissioner/show_contestants/normalize_video_highlights.rb +57 -0
  105. data/app/services/spree_cm_commissioner/show_persons/contestant_assigner.rb +51 -0
  106. data/app/services/spree_cm_commissioner/url_embed/youtube_embed.rb +44 -0
  107. data/app/services/spree_cm_commissioner/vote_counters/audit_counters.rb +43 -0
  108. data/app/services/spree_cm_commissioner/vote_counters/base.rb +31 -0
  109. data/app/services/spree_cm_commissioner/vote_counters/increment.rb +44 -0
  110. data/app/services/spree_cm_commissioner/vote_counters/per_contestant_counter.rb +68 -0
  111. data/app/services/spree_cm_commissioner/vote_counters/rebuild_from_db.rb +70 -0
  112. data/app/services/spree_cm_commissioner/vote_counters/snapshot_to_db.rb +113 -0
  113. data/app/services/spree_cm_commissioner/vote_credit_deductor.rb +68 -0
  114. data/app/services/spree_cm_commissioner/vote_package/create.rb +150 -0
  115. data/app/services/spree_cm_commissioner/vote_package/update.rb +97 -0
  116. data/app/services/spree_cm_commissioner/vote_processor.rb +144 -0
  117. data/app/services/spree_cm_commissioner/voting_contestants/advancer.rb +335 -0
  118. data/app/services/spree_cm_commissioner/voting_contestants/assigner.rb +40 -0
  119. data/app/services/spree_cm_commissioner/voting_contestants/bulk_updater.rb +109 -0
  120. data/app/services/spree_cm_commissioner/voting_credits/admin_grant.rb +37 -0
  121. data/app/services/spree_cm_commissioner/voting_credits/allocate.rb +123 -0
  122. data/app/services/spree_cm_commissioner/voting_credits/claimable_scope_resolver.rb +64 -0
  123. data/app/services/spree_cm_commissioner/voting_credits/credit_calculator.rb +35 -0
  124. data/app/services/spree_cm_commissioner/voting_credits/de_allocate.rb +87 -0
  125. data/app/services/spree_cm_commissioner/voting_leaderboards/calculate_score.rb +67 -0
  126. data/app/services/spree_cm_commissioner/voting_leaderboards/combined_result.rb +190 -0
  127. data/app/services/spree_cm_commissioner/voting_sessions/finalize.rb +105 -0
  128. data/app/views/spree/admin/homepage_section/_form.html.erb +5 -0
  129. data/config/initializers/spree_permitted_attributes.rb +8 -0
  130. data/config/locales/en.yml +53 -5
  131. data/config/locales/km.yml +38 -1
  132. data/config/routes.rb +30 -0
  133. data/db/migrate/20260309230148_create_cm_show_people.rb +14 -0
  134. data/db/migrate/20260309230149_create_cm_show_people_assignments.rb +16 -0
  135. data/db/migrate/20260310082711_create_cm_show_contestants.rb +28 -0
  136. data/db/migrate/20260310082720_create_cm_voting_sessions.rb +21 -0
  137. data/db/migrate/20260310082721_create_cm_voting_contestants.rb +23 -0
  138. data/db/migrate/20260310082734_add_voting_fields_to_spree_taxons.rb +9 -0
  139. data/db/migrate/20260310082735_add_type_to_spree_products.rb +6 -0
  140. data/db/migrate/20260310082749_create_cm_voting_credits.rb +27 -0
  141. data/db/migrate/20260326080200_create_cm_voting_credit_transactions.rb +27 -0
  142. data/db/migrate/20260330160000_create_cm_votes.rb +25 -0
  143. data/db/migrate/20260401072500_add_advanced_from_to_cm_voting_contestants.rb +7 -0
  144. data/db/migrate/20260402000001_add_voting_credit_scope_to_spree_taxons.rb +6 -0
  145. data/db/migrate/20260402000002_rename_scopeable_to_votable_in_cm_voting_credits.rb +12 -0
  146. data/db/migrate/20260403070000_add_name_to_cm_voting_sessions.rb +5 -0
  147. data/db/migrate/20260406000001_add_vendor_id_to_voting_tables.rb +6 -0
  148. data/db/migrate/20260406000001_rename_votes_remaining_to_amount_in_cm_voting_credits.rb +11 -0
  149. data/db/migrate/20260408085255_add_show_id_and_vendor_id_to_cm_voting_sessions.rb +9 -0
  150. data/db/migrate/20260420000001_rename_type_to_credit_type_in_cm_voting_credits.rb +25 -0
  151. data/db/migrate/20260422000001_create_cm_vote_fraud_events.rb +23 -0
  152. data/db/migrate/20260423000001_add_preview_to_taxons_products_and_sections.rb +11 -0
  153. data/db/migrate/20260423000002_create_preview_roles.rb +24 -0
  154. data/db/migrate/20260515120000_add_public_metadata_to_cm_voting_sessions.rb +5 -0
  155. data/db/migrate/20260518090920_add_unique_voter_count_to_voting_contestants.rb +5 -0
  156. data/db/migrate/20260518094322_create_cm_voting_session_stats.rb +17 -0
  157. data/db/migrate/20260520000001_add_scoring_model_to_cm_voting_sessions.rb +5 -0
  158. data/db/migrate/20260520000001_optimize_cm_votes_indexes.rb +22 -0
  159. data/db/migrate/20260525042257_add_vote_number_to_cm_voting_contestants.rb +18 -0
  160. data/db/migrate/20260527035430_add_confirmed_rank_to_cm_voting_contestants.rb +5 -0
  161. data/db/migrate/20260527062005_add_eliminated_at_to_cm_show_contestants.rb +5 -0
  162. data/db/migrate/20260603063652_add_parent_to_voting_sessions.rb +9 -0
  163. data/db/migrate/20260603090000_add_session_type_to_cm_voting_sessions.rb +6 -0
  164. data/db/migrate/20260608000000_add_display_to_cm_voting_sessions.rb +5 -0
  165. data/db/migrate/20260623082233_create_cm_claims.rb +14 -0
  166. data/db/migrate/20260624175658_add_unique_index_to_cm_claims.rb +11 -0
  167. data/docs/sql/jsonb_query_guide.md +57 -0
  168. data/lib/spree_cm_commissioner/test_helper/factories/claim_factory.rb +14 -0
  169. data/lib/spree_cm_commissioner/test_helper/factories/show_episode_factory.rb +12 -0
  170. data/lib/spree_cm_commissioner/test_helper/factories/show_factory.rb +120 -0
  171. data/lib/spree_cm_commissioner/test_helper/factories/vote_credit_factory.rb +37 -0
  172. data/lib/spree_cm_commissioner/test_helper/factories/vote_factory.rb +28 -0
  173. data/lib/spree_cm_commissioner/test_helper/factories/voting_credit_transaction_factory.rb +11 -0
  174. data/lib/spree_cm_commissioner/test_helper/factories/voting_session_factory.rb +11 -0
  175. data/lib/spree_cm_commissioner/version.rb +1 -1
  176. data/lib/spree_cm_commissioner.rb +7 -1
  177. data/spree_cm_commissioner.gemspec +1 -1
  178. metadata +134 -2
@@ -0,0 +1,36 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class ShowsController < BaseController
6
+ private
7
+
8
+ # override
9
+ def model_class
10
+ SpreeCmCommissioner::Show
11
+ end
12
+
13
+ def resource
14
+ @resource ||= scope.find_by!(slug: params[:id])
15
+ end
16
+
17
+ # override
18
+ def scope
19
+ vendor_ids = @tenant.vendors.select(:id)
20
+ return model_class.none if vendor_ids.blank?
21
+
22
+ model_class.where(vendor_id: vendor_ids, preview: false)
23
+ .includes(:parent, :app_banner, show_people: :show_person_images,
24
+ episodes: :voting_sessions, seasons: { episodes: :voting_sessions }
25
+ )
26
+ end
27
+
28
+ # override
29
+ def resource_serializer
30
+ Spree::V2::Tenant::ShowSerializer
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -31,7 +31,7 @@ module Spree
31
31
 
32
32
  def scope
33
33
  MultiTenant.with(@tenant) do
34
- model_class
34
+ model_class.where(preview: false)
35
35
  end
36
36
  end
37
37
 
@@ -0,0 +1,81 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class VotePackagesController < BaseController
6
+ before_action :require_spree_current_user, only: :claim_status
7
+ before_action :require_votable_params, only: :index
8
+
9
+ # Overrided
10
+ def index
11
+ render_serialized_payload { serialize_collection(sorted_collection) }
12
+ end
13
+
14
+ def claim_status
15
+ product = current_season.vote_packages.find(params[:id])
16
+
17
+ result = SpreeCmCommissioner::VotingCredits::ClaimableScopeResolver.new(
18
+ product: product,
19
+ voting_session_id: params[:voting_session_id]
20
+ ).call
21
+
22
+ return render_error_payload(result.error, 422) if result.failure?
23
+
24
+ scope = result.value
25
+
26
+ claimed = SpreeCmCommissioner::Claim.free_claimed?(
27
+ user_id: spree_current_user.id,
28
+ product_id: product.id,
29
+ claimable_id: scope[:claimable_id],
30
+ claimable_type: scope[:claimable_type]
31
+ )
32
+
33
+ render_serialized_payload { { claimed: claimed }.merge(scope) }
34
+ end
35
+
36
+ private
37
+
38
+ # Overrided
39
+ def collection
40
+ current_vendor.vote_packages
41
+ .where(status: :active, event_id: current_season.id)
42
+ .by_votable(params[:votable_type], params[:votable_id])
43
+ end
44
+
45
+ # Overrided
46
+ def sorted_collection
47
+ packages = current_season.allow_free_vote? ? collection.to_a : collection.reject(&:free?)
48
+ packages.sort_by { |p| [p.free? ? 0 : 1, p.vote_credits.to_i] }
49
+ end
50
+
51
+ # Overrided
52
+ def serialize_collection(collection)
53
+ collection_serializer.new(
54
+ collection,
55
+ include: resource_includes
56
+ ).serializable_hash
57
+ end
58
+
59
+ # Overrided
60
+ def collection_serializer
61
+ Spree::V2::Tenant::VotePackageSerializer
62
+ end
63
+
64
+ def current_season
65
+ @current_season ||= current_vendor.shows
66
+ .where.not(parent_id: nil)
67
+ .find_by!(slug: params[:show_id])
68
+ end
69
+
70
+ # We need to require votable_type and votable_id params because we are filtering VotePackages by ShowEpisode.
71
+ # VotePackage Metadata(votable_type and votable_id) are important when allocating voting credits to users.
72
+ def require_votable_params
73
+ return if params[:votable_type].present? && params[:votable_id].present?
74
+
75
+ render_error_payload('votable_type and votable_id are required', 422)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,94 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class VotesController < BaseController
6
+ before_action :require_spree_current_user
7
+
8
+ def index
9
+ render_serialized_payload do
10
+ serialize_collection(collection)
11
+ end
12
+ end
13
+
14
+ def create
15
+ voting_session = current_vendor.voting_sessions.find(params[:voting_session_id])
16
+ contestant = voting_session.voting_contestants.find(params[:contestant_id])
17
+
18
+ processor_params = vote_params.merge(
19
+ device_fingerprint: device_params[:fingerprint],
20
+ voter_identifier: device_params[:identifier],
21
+ public_metadata: params[:public_metadata] || {},
22
+ tenant_id: MultiTenant.current_tenant_id,
23
+ vendor_id: current_vendor.id
24
+ )
25
+
26
+ result = SpreeCmCommissioner::VoteProcessor.call(
27
+ voting_session: voting_session,
28
+ contestant: contestant,
29
+ user: spree_current_user,
30
+ params: processor_params,
31
+ request: request
32
+ )
33
+
34
+ if result.success?
35
+ render_serialized_payload(201) { serialize_resource(result.value) }
36
+ else
37
+ render_error_payload(result.error)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def collection
44
+ @collection ||= scope.page(params[:page])
45
+ .per(params[:per_page])
46
+ end
47
+
48
+ def scope
49
+ MultiTenant.with(@tenant) do
50
+ scope = spree_current_user.votes
51
+ .includes(:contestant, :voting_session, :vote_credit)
52
+ .order(created_at: :desc)
53
+
54
+ scope = scope.for_session(params[:voting_session_id]) if params[:voting_session_id]
55
+ scope
56
+ end
57
+ end
58
+
59
+ def model_class
60
+ SpreeCmCommissioner::Vote
61
+ end
62
+
63
+ def resource_serializer
64
+ Spree::V2::Tenant::VoteSerializer
65
+ end
66
+
67
+ def collection_serializer
68
+ Spree::V2::Tenant::VoteSerializer
69
+ end
70
+
71
+ # {
72
+ # "contestant_id": 1,
73
+ # "vote": {
74
+ # "quantity": 1,
75
+ # "channel": "mobile"
76
+ # },
77
+ # "device": {
78
+ # "fingerprint": "fp-xyz",
79
+ # "identifier": "device-abc",
80
+ # "platform": "android"
81
+ # }
82
+ # }
83
+ def vote_params
84
+ params.require(:vote).permit(:quantity, :channel)
85
+ end
86
+
87
+ def device_params
88
+ params.require(:device).permit(:fingerprint, :identifier, :platform)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,43 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class VotingContestantsController < BaseController
6
+ before_action :load_voting_session, only: %i[index]
7
+
8
+ # override
9
+ def collection
10
+ @collection ||= @voting_session
11
+ .voting_contestants
12
+ .includes(:voting_session, :show_contestant,
13
+ :show_contestant_images, :show_contestant_videos,
14
+ :advanced_to, :advanced_from
15
+ )
16
+ .order(:id)
17
+ end
18
+
19
+ # override
20
+ def collection_serializer
21
+ Spree::V2::Tenant::VotingContestantSerializer
22
+ end
23
+
24
+ private
25
+
26
+ # override
27
+ def model_class
28
+ SpreeCmCommissioner::VotingSession
29
+ end
30
+
31
+ # override
32
+ def scope
33
+ current_vendor.voting_sessions.includes(:episode)
34
+ end
35
+
36
+ def load_voting_session
37
+ @voting_session = scope.find(params[:voting_session_id])
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,41 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class VotingCreditTransactionsController < BaseController
6
+ before_action :require_spree_current_user
7
+
8
+ def index
9
+ render_serialized_payload do
10
+ serialize_collection(collection)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def collection
17
+ @collection ||= scope.page(params[:page])
18
+ .per(params[:per_page])
19
+ end
20
+
21
+ def scope
22
+ scope = spree_current_user.voting_credit_transactions
23
+ .includes(:vote_credit, :originator)
24
+ .order(created_at: :desc)
25
+
26
+ scope = scope.by_action(params[:actions].split(',')) if params[:actions].present?
27
+ scope
28
+ end
29
+
30
+ def model_class
31
+ SpreeCmCommissioner::VotingCreditTransaction
32
+ end
33
+
34
+ def collection_serializer
35
+ Spree::V2::Tenant::VotingCreditTransactionSerializer
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class VotingCreditsController < BaseController
6
+ before_action :require_spree_current_user
7
+
8
+ def index
9
+ render_serialized_payload do
10
+ serialize_resource(voting_credit)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def voting_credit
17
+ spree_current_user.voting_credits.active.eligible(load_voting_session).first
18
+ end
19
+
20
+ def load_voting_session
21
+ @voting_session ||= SpreeCmCommissioner::VotingSession.find(params[:voting_session_id])
22
+ end
23
+
24
+ def resource_serializer
25
+ Spree::V2::Tenant::VotingCreditSerializer
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -6,6 +6,7 @@ module SpreeCmCommissioner
6
6
  base.around_action :set_writing_role, only: %i[index]
7
7
  base.after_action :set_tenant_after_update, only: %i[update]
8
8
  base.before_action :merge_industry_taxons_into_taxons, only: [:update]
9
+ base.before_action :normalize_sti_product_params, only: %i[create update]
9
10
  end
10
11
 
11
12
  # Override
@@ -51,6 +52,24 @@ module SpreeCmCommissioner
51
52
 
52
53
  protected
53
54
 
55
+ # Some admin forms submit STI records under their class key while
56
+ # Spree update reads :product. Merge both into :product so all edited
57
+ # fields are persisted for STI product subclasses.
58
+ def normalize_sti_product_params
59
+ return unless @product
60
+
61
+ sti_key = @product.class.model_name.param_key.to_sym
62
+ sti_params = params[sti_key]
63
+ return unless sti_params.is_a?(::ActionController::Parameters)
64
+
65
+ base_params = params[:product]
66
+ base_hash = base_params.is_a?(::ActionController::Parameters) ? base_params.to_unsafe_h : {}
67
+ sti_hash = sti_params.to_unsafe_h
68
+ merged_params = ::ActionController::Parameters.new(base_hash.merge(sti_hash))
69
+
70
+ params[:product] = merged_params
71
+ end
72
+
54
73
  def merge_industry_taxons_into_taxons
55
74
  return if params[:product].blank?
56
75
 
@@ -7,6 +7,7 @@ module SpreeCmCommissioner
7
7
  def execute
8
8
  scope.joins(:integration_mappings).where(
9
9
  kind: :event,
10
+ preview: false,
10
11
  integration_mappings: { status: :active }
11
12
  ).distinct
12
13
  end
@@ -15,6 +15,8 @@ module SpreeCmCommissioner
15
15
  'badge badge-success text-uppercase'
16
16
  when :football
17
17
  'badge badge-dark text-uppercase'
18
+ when :show
19
+ 'badge badge-danger text-uppercase'
18
20
  else
19
21
  'badge'
20
22
  end
@@ -0,0 +1,9 @@
1
+ module SpreeCmCommissioner
2
+ module ShowContestants
3
+ class ImportJob < ApplicationJob
4
+ def perform(import_id)
5
+ SpreeCmCommissioner::Imports::Contestants::Create.new(import_id: import_id).call
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module SpreeCmCommissioner
2
+ class VoteFraudEventJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ def perform(options = {})
6
+ SpreeCmCommissioner::VoteFraudEvent.create!(options)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ class VotingCreditAllocationJob < ApplicationUniqueJob
3
+ def perform(options = {})
4
+ order = Spree::Order.find_by(id: options[:order_id])
5
+ return if order.nil?
6
+
7
+ SpreeCmCommissioner::VotingCredits::Allocate.new(order).call
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ class VotingCreditDeAllocationJob < ApplicationUniqueJob
3
+ def perform(options = {})
4
+ order = Spree::Order.find_by(id: options[:order_id])
5
+ return if order.nil?
6
+
7
+ SpreeCmCommissioner::VotingCredits::DeAllocate.new(order).call
8
+ end
9
+ end
10
+ end
@@ -8,7 +8,8 @@ module SpreeCmCommissioner
8
8
  tour: 0b00100,
9
9
  accommodation: 0b01000,
10
10
  things_to_do: 0b10000,
11
- football: 0b100000
11
+ football: 0b100000,
12
+ show: 0b1000000
12
13
  }.freeze
13
14
 
14
15
  BIT_SEGMENT.each do |segment, bit_value|
@@ -45,7 +45,8 @@ module SpreeCmCommissioner
45
45
  'color' => 'color',
46
46
  'ticket-type' => 'string',
47
47
  'seat-type' => 'string',
48
- 'intercity-taxi' => 'string'
48
+ 'intercity-taxi' => 'string',
49
+ 'vote-package' => 'integer'
49
50
  }.freeze
50
51
 
51
52
  included do
@@ -1,7 +1,7 @@
1
1
  # Make sure to put this concern below other concern or methods that generating additional order info like guests, seat number, etc.
2
2
  # This will ensure that when each notification is sent with neccessary data.
3
3
  module SpreeCmCommissioner
4
- module OrderStateMachine
4
+ module OrderStateMachine # rubocop:disable Metrics/ModuleLength
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do # rubocop:disable Metrics/BlockLength
@@ -26,8 +26,10 @@ module SpreeCmCommissioner
26
26
 
27
27
  state_machine.after_transition to: :canceled, do: :precalculate_conversion
28
28
  state_machine.after_transition to: :canceled, do: :restock_inventory!
29
+ state_machine.after_transition to: :canceled, do: :de_allocate_resources
29
30
 
30
31
  state_machine.after_transition to: :complete, do: :increment_route_fulfilled_order_count
32
+ state_machine.after_transition to: :complete, do: :allocate_resources
31
33
  state_machine.after_transition to: :complete, do: :run_order_complete_alerts
32
34
 
33
35
  scope :accepted, -> { where(request_state: 'accepted') }
@@ -360,5 +362,13 @@ module SpreeCmCommissioner
360
362
  factory = OrderTelegramMessageFactory.new(title: title, order: self)
361
363
  TelegramNotificationSenderJob.perform_later(chat_id: chat_id, message: factory.message, parse_mode: factory.parse_mode)
362
364
  end
365
+
366
+ def allocate_resources
367
+ SpreeCmCommissioner::VotingCreditAllocationJob.perform_later(order_id: id)
368
+ end
369
+
370
+ def de_allocate_resources
371
+ SpreeCmCommissioner::VotingCreditDeAllocationJob.perform_later(order_id: id)
372
+ end
363
373
  end
364
374
  end
@@ -5,7 +5,7 @@ module SpreeCmCommissioner
5
5
  module ProductType
6
6
  extend ActiveSupport::Concern
7
7
 
8
- PRODUCT_TYPES = %i[accommodation service ecommerce transit].freeze
8
+ PRODUCT_TYPES = %i[accommodation service ecommerce transit show_episode vote_package advertisement].freeze
9
9
  PERMANENT_STOCK_PRODUCT_TYPES = %w[accommodation service transit].freeze
10
10
  PRE_INVENTORY_DAYS = { 'transit' => 90, 'accommodation' => 365, 'service' => 30 }.freeze
11
11
 
@@ -49,6 +49,9 @@ module SpreeCmCommissioner
49
49
  def self.load(json)
50
50
  return {} if json.blank?
51
51
 
52
+ # jsonb columns are already parsed to a Hash by ActiveRecord — return as-is.
53
+ return json if json.is_a?(Hash) || json.is_a?(Array)
54
+
52
55
  begin
53
56
  JSON.parse(json)
54
57
  rescue JSON::ParserError, TypeError
@@ -3,7 +3,9 @@ module SpreeCmCommissioner
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- enum kind: { category: 0, cms: 1, event: 2, occupation: 3, nationality: 4, organization: 5, transit: 6, industry: 7, agency_category: 8 }
6
+ enum kind: { category: 0, cms: 1, event: 2, occupation: 3, nationality: 4, organization: 5, transit: 6, industry: 7, agency_category: 8,
7
+ ads: 9
8
+ }
7
9
  end
8
10
  end
9
11
  end
@@ -0,0 +1,41 @@
1
+ module SpreeCmCommissioner
2
+ module VotingSessionLiveStreamConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ store_public_metadata :live_stream_url, :string
7
+ store_public_metadata :live_stream_enabled, :boolean
8
+ store_public_metadata :live_stream_thumbnail, :string
9
+ store_public_metadata :live_stream_title, :string
10
+ store_public_metadata :live_stream_description, :text
11
+
12
+ validate :validate_live_stream_url, if: :live_stream_url_changed?
13
+ before_save :update_live_stream_thumbnail
14
+ end
15
+
16
+ def embedded_live_stream_url
17
+ return nil if live_stream_url.blank?
18
+
19
+ result = SpreeCmCommissioner::UrlEmbed::YoutubeEmbed.call(url: live_stream_url)
20
+ result.success? ? result.value : nil
21
+ end
22
+
23
+ private
24
+
25
+ def validate_live_stream_url
26
+ return if live_stream_url.blank?
27
+
28
+ result = SpreeCmCommissioner::UrlEmbed::YoutubeEmbed.call(url: live_stream_url)
29
+ return if result.success?
30
+
31
+ errors.add(:live_stream_url, result.error)
32
+ end
33
+
34
+ def update_live_stream_thumbnail
35
+ return if live_stream_url.blank?
36
+
37
+ video_id = SpreeCmCommissioner::UrlEmbed::YoutubeEmbed.extract_video_id(live_stream_url)
38
+ self.live_stream_thumbnail = "https://img.youtube.com/vi/#{video_id}/hqdefault.jpg" if video_id
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ module SpreeCmCommissioner
2
+ class Claim < SpreeCmCommissioner::Base
3
+ belongs_to :user, class_name: 'Spree::User'
4
+ belongs_to :product, class_name: 'Spree::Product'
5
+ belongs_to :claimable, polymorphic: true
6
+
7
+ validates :tenant_id, presence: true
8
+ validates :user_id,
9
+ uniqueness: {
10
+ scope: %i[product_id claimable_id claimable_type],
11
+ conditions: -> { where(free: true) },
12
+ message: 'has already been claimed for this scope' # rubocop:disable Rails/I18nLocaleTexts
13
+ },
14
+ if: :free?
15
+
16
+ scope :free_claims, -> { where(free: true) }
17
+
18
+ def self.free_claimed?(user_id:, product_id:, claimable_id:, claimable_type:)
19
+ free_claims.exists?(
20
+ user_id: user_id,
21
+ product_id: product_id,
22
+ claimable_id: claimable_id,
23
+ claimable_type: claimable_type
24
+ )
25
+ end
26
+ end
27
+ end
@@ -3,7 +3,7 @@ module SpreeCmCommissioner
3
3
  extend FriendlyId
4
4
 
5
5
  enum :status, { :queue => 0, :progress => 1, :done => 2, :failed => 3, :canceling => 4, :canceled => 5 }
6
- enum :import_type, { :new_order => 0, :existing_order => 1, :aba_payment_reference => 2 }
6
+ enum :import_type, { :new_order => 0, :existing_order => 1, :aba_payment_reference => 2, :contestant_import => 3 }
7
7
  has_one_attached :imported_file
8
8
  friendly_id :name, use: :slugged
9
9
 
@@ -0,0 +1,7 @@
1
+ module SpreeCmCommissioner
2
+ module Imports
3
+ class ImportContestant < Import
4
+ preference :fail_rows, :string
5
+ end
6
+ end
7
+ end
@@ -121,6 +121,7 @@ module SpreeCmCommissioner
121
121
  def update_price
122
122
  # Skip price updates for ticket transfer orders to preserve custom transfer pricing
123
123
  return if order&.ticket_transfer?
124
+ return self.price = 0 if eligible_for_free_claim?
124
125
 
125
126
  base_price = variant.price_in(order.currency)
126
127
  self.price = if permanent_stock?
@@ -276,6 +277,17 @@ module SpreeCmCommissioner
276
277
  SpreeCmCommissioner::OrderJwtToken.encode(payload, order.token)
277
278
  end
278
279
 
280
+ # Returns { claimable_id:, claimable_type: } or nil if unresolvable.
281
+ # Scope is determined by the Show's free_vote_limit_type from voting_config.
282
+ def claim_scope
283
+ result = SpreeCmCommissioner::VotingCredits::ClaimableScopeResolver.new(
284
+ product: product,
285
+ voting_session_id: public_metadata&.dig('voting_session_id')
286
+ ).call
287
+
288
+ result.success? ? result.value : nil
289
+ end
290
+
279
291
  private
280
292
 
281
293
  def ensure_not_exceed_max_quantity_per_order
@@ -304,6 +316,20 @@ module SpreeCmCommissioner
304
316
  order.subscription.variant.post_paid?
305
317
  end
306
318
 
319
+ def eligible_for_free_claim?
320
+ return false if order&.user_id.blank?
321
+
322
+ scope = claim_scope
323
+ return false if scope.nil?
324
+
325
+ !SpreeCmCommissioner::Claim.free_claimed?(
326
+ user_id: order.user_id,
327
+ product_id: product_id,
328
+ claimable_id: scope[:claimable_id],
329
+ claimable_type: scope[:claimable_type]
330
+ )
331
+ end
332
+
307
333
  def due_days
308
334
  variant = order.subscription.variant
309
335
  day = variant.due_date