spree_cm_commissioner 2.8.6 → 2.8.7.pre.pre2

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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +8 -0
  3. data/.gitignore +6 -0
  4. data/Gemfile.lock +1 -1
  5. data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +2 -0
  6. data/app/controllers/spree/admin/homepage_section_controller.rb +4 -1
  7. data/app/controllers/spree/admin/import_new_orders_controller.rb +25 -0
  8. data/app/controllers/spree/admin/inventory_holds_controller.rb +20 -1
  9. data/app/controllers/spree/admin/taxons_controller_decorator.rb +19 -0
  10. data/app/controllers/spree/api/v2/storefront/account/orders_controller_decorator.rb +5 -0
  11. data/app/controllers/spree/api/v2/storefront/homepage_sections_controller.rb +1 -0
  12. data/app/controllers/spree/api/v2/storefront/order_histories_controller.rb +7 -2
  13. data/app/controllers/spree/api/v2/storefront/preview_products_controller.rb +48 -0
  14. data/app/controllers/spree/api/v2/storefront/preview_sections_controller.rb +27 -0
  15. data/app/controllers/spree/api/v2/storefront/preview_taxons_controller.rb +18 -0
  16. data/app/controllers/spree/api/v2/storefront/products_controller_decorator.rb +15 -0
  17. data/app/controllers/spree/api/v2/storefront/taxons_controller_decorator.rb +15 -0
  18. data/app/controllers/spree/api/v2/storefront/ticket_transfers_controller.rb +4 -3
  19. data/app/controllers/spree/api/v2/tenant/ad_campaigns_controller.rb +25 -0
  20. data/app/controllers/spree/api/v2/tenant/base_controller.rb +4 -0
  21. data/app/controllers/spree/api/v2/tenant/episodes_controller.rb +38 -0
  22. data/app/controllers/spree/api/v2/tenant/free_vote_claims_controller.rb +37 -0
  23. data/app/controllers/spree/api/v2/tenant/homepage_sections_controller.rb +1 -0
  24. data/app/controllers/spree/api/v2/tenant/order_histories_controller.rb +7 -2
  25. data/app/controllers/spree/api/v2/tenant/preview_products_controller.rb +47 -0
  26. data/app/controllers/spree/api/v2/tenant/preview_sections_controller.rb +26 -0
  27. data/app/controllers/spree/api/v2/tenant/preview_shows_controller.rb +19 -0
  28. data/app/controllers/spree/api/v2/tenant/preview_taxons_controller.rb +19 -0
  29. data/app/controllers/spree/api/v2/tenant/products_controller.rb +1 -1
  30. data/app/controllers/spree/api/v2/tenant/show_contestants_controller.rb +52 -0
  31. data/app/controllers/spree/api/v2/tenant/show_elimination_sessions_controller.rb +57 -0
  32. data/app/controllers/spree/api/v2/tenant/show_people_controller.rb +49 -0
  33. data/app/controllers/spree/api/v2/tenant/show_person_assignments_controller.rb +36 -0
  34. data/app/controllers/spree/api/v2/tenant/shows_controller.rb +36 -0
  35. data/app/controllers/spree/api/v2/tenant/taxons_controller.rb +1 -1
  36. data/app/controllers/spree/api/v2/tenant/votes_controller.rb +94 -0
  37. data/app/controllers/spree/api/v2/tenant/voting_contestants_controller.rb +43 -0
  38. data/app/controllers/spree/api/v2/tenant/voting_credit_transactions_controller.rb +41 -0
  39. data/app/controllers/spree/api/v2/tenant/voting_credits_controller.rb +31 -0
  40. data/app/controllers/spree_cm_commissioner/admin/products_controller_decorator.rb +19 -0
  41. data/app/finders/spree_cm_commissioner/events/find_matches.rb +1 -0
  42. data/app/finders/spree_cm_commissioner/orders/find.rb +33 -3
  43. data/app/finders/spree_cm_commissioner/orders/find_by_all_state.rb +11 -0
  44. data/app/helpers/spree_cm_commissioner/admin/homepage_segment_helper.rb +2 -0
  45. data/app/interactors/spree_cm_commissioner/waiting_guests_caller.rb +16 -4
  46. data/app/jobs/concerns/spree_cm_commissioner/idempotent_job.rb +12 -0
  47. data/app/jobs/spree_cm_commissioner/cancel_import_order_job.rb +11 -0
  48. data/app/jobs/spree_cm_commissioner/idempotency_keys/prune_job.rb +20 -0
  49. data/app/jobs/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_job.rb +21 -3
  50. data/app/jobs/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_on_hold_job.rb +34 -3
  51. data/app/jobs/spree_cm_commissioner/show_contestants/import_job.rb +9 -0
  52. data/app/jobs/spree_cm_commissioner/telegram_alerts/order_integrity_check_job.rb +17 -0
  53. data/app/jobs/spree_cm_commissioner/vote_fraud_event_job.rb +9 -0
  54. data/app/jobs/spree_cm_commissioner/voting_credit_allocation_job.rb +10 -0
  55. data/app/jobs/spree_cm_commissioner/voting_credit_de_allocation_job.rb +10 -0
  56. data/app/jobs/spree_cm_commissioner/waiting_guests_caller_job.rb +5 -0
  57. data/app/mailers/spree/order_mailer_decorator.rb +2 -0
  58. data/app/models/concerns/spree_cm_commissioner/homepage_section_bitwise.rb +2 -1
  59. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +2 -1
  60. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +43 -1
  61. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +2 -0
  62. data/app/models/concerns/spree_cm_commissioner/product_type.rb +1 -1
  63. data/app/models/concerns/spree_cm_commissioner/store_preference.rb +1 -0
  64. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +3 -1
  65. data/app/models/concerns/spree_cm_commissioner/voting_session_live_stream_concern.rb +41 -0
  66. data/app/models/spree_cm_commissioner/idempotency_key.rb +21 -0
  67. data/app/models/spree_cm_commissioner/import.rb +4 -2
  68. data/app/models/spree_cm_commissioner/imported_order.rb +6 -0
  69. data/app/models/spree_cm_commissioner/imports/import_contestant.rb +7 -0
  70. data/app/models/spree_cm_commissioner/imports/import_order.rb +21 -0
  71. data/app/models/spree_cm_commissioner/maintenance_tasks/voting_session.rb +36 -0
  72. data/app/models/spree_cm_commissioner/order_decorator.rb +14 -0
  73. data/app/models/spree_cm_commissioner/payment_decorator.rb +1 -0
  74. data/app/models/spree_cm_commissioner/preview_role.rb +8 -0
  75. data/app/models/spree_cm_commissioner/product_decorator.rb +53 -0
  76. data/app/models/spree_cm_commissioner/role_decorator.rb +4 -1
  77. data/app/models/spree_cm_commissioner/role_user_decorator.rb +8 -0
  78. data/app/models/spree_cm_commissioner/show.rb +163 -0
  79. data/app/models/spree_cm_commissioner/show_contestant.rb +39 -0
  80. data/app/models/spree_cm_commissioner/show_contestant_image.rb +11 -0
  81. data/app/models/spree_cm_commissioner/show_contestant_video.rb +15 -0
  82. data/app/models/spree_cm_commissioner/show_episode.rb +108 -0
  83. data/app/models/spree_cm_commissioner/show_person.rb +15 -0
  84. data/app/models/spree_cm_commissioner/show_person_assignment.rb +21 -0
  85. data/app/models/spree_cm_commissioner/show_person_image.rb +11 -0
  86. data/app/models/spree_cm_commissioner/taxon_decorator.rb +23 -0
  87. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +19 -1
  88. data/app/models/spree_cm_commissioner/tenant.rb +9 -0
  89. data/app/models/spree_cm_commissioner/user_decorator.rb +5 -0
  90. data/app/models/spree_cm_commissioner/variant_decorator.rb +4 -0
  91. data/app/models/spree_cm_commissioner/variant_options.rb +4 -0
  92. data/app/models/spree_cm_commissioner/vendor_decorator.rb +4 -0
  93. data/app/models/spree_cm_commissioner/vote.rb +16 -0
  94. data/app/models/spree_cm_commissioner/vote_fraud_event.rb +20 -0
  95. data/app/models/spree_cm_commissioner/voting_contestant.rb +100 -0
  96. data/app/models/spree_cm_commissioner/voting_credit.rb +72 -0
  97. data/app/models/spree_cm_commissioner/voting_credit_transaction.rb +55 -0
  98. data/app/models/spree_cm_commissioner/voting_session.rb +216 -0
  99. data/app/models/spree_cm_commissioner/voting_session_stat.rb +8 -0
  100. data/app/overrides/spree/admin/orders/_search/payment_number_search_field.html.erb.deface +8 -0
  101. data/app/overrides/spree/admin/products/_form/allow_gift_transfer.html.erb.deface +1 -1
  102. data/app/overrides/spree/admin/products/_form/allow_transfer.html.erb.deface +1 -1
  103. data/app/overrides/spree/admin/products/_form/enable_telegram_alert.html.erb.deface +14 -0
  104. data/app/overrides/spree/admin/products/_form/preview_checkbox.html.erb.deface +9 -0
  105. data/app/overrides/spree/admin/stores/_form/store_preferences.html.erb.deface +9 -0
  106. data/app/overrides/spree/admin/taxons/_form/preview_checkbox.html.erb.deface +7 -0
  107. data/app/serializers/spree/v2/storefront/homepage_section_serializer.rb +1 -1
  108. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +2 -1
  109. data/app/serializers/spree/v2/storefront/role_serializer.rb +1 -1
  110. data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +2 -1
  111. data/app/serializers/spree/v2/tenant/ad_campaign_serializer.rb +13 -0
  112. data/app/serializers/spree/v2/tenant/advertisement_serializer.rb +11 -0
  113. data/app/serializers/spree/v2/tenant/homepage_section_serializer.rb +1 -1
  114. data/app/serializers/spree/v2/tenant/role_serializer.rb +1 -1
  115. data/app/serializers/spree/v2/tenant/show_contestant_serializer.rb +21 -0
  116. data/app/serializers/spree/v2/tenant/show_episode_serializer.rb +20 -0
  117. data/app/serializers/spree/v2/tenant/show_parent_serializer.rb +13 -0
  118. data/app/serializers/spree/v2/tenant/show_person_assignment_serializer.rb +16 -0
  119. data/app/serializers/spree/v2/tenant/show_person_serializer.rb +13 -0
  120. data/app/serializers/spree/v2/tenant/show_serializer.rb +23 -0
  121. data/app/serializers/spree/v2/tenant/video_serializer.rb +9 -0
  122. data/app/serializers/spree/v2/tenant/vote_package_serializer.rb +9 -0
  123. data/app/serializers/spree/v2/tenant/vote_serializer.rb +14 -0
  124. data/app/serializers/spree/v2/tenant/voting_contestant_serializer.rb +23 -0
  125. data/app/serializers/spree/v2/tenant/voting_credit_serializer.rb +10 -0
  126. data/app/serializers/spree/v2/tenant/voting_credit_transaction_serializer.rb +14 -0
  127. data/app/serializers/spree/v2/tenant/voting_session_serializer.rb +21 -0
  128. data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +12 -0
  129. data/app/services/spree_cm_commissioner/cart/add_guest.rb +16 -2
  130. data/app/services/spree_cm_commissioner/fraud_check.rb +299 -0
  131. data/app/services/spree_cm_commissioner/imports/contestants/create.rb +153 -0
  132. data/app/services/spree_cm_commissioner/imports/orders/cancel.rb +63 -0
  133. data/app/services/spree_cm_commissioner/imports/orders/create.rb +1 -0
  134. data/app/services/spree_cm_commissioner/show_contestants/normalize_video_highlights.rb +57 -0
  135. data/app/services/spree_cm_commissioner/show_persons/contestant_assigner.rb +51 -0
  136. data/app/services/spree_cm_commissioner/telegram_alerts/checks/order_complete_payment_not_paid.rb +21 -0
  137. data/app/services/spree_cm_commissioner/telegram_alerts/checks/payment_paid_order_not_complete.rb +21 -0
  138. data/app/services/spree_cm_commissioner/telegram_alerts/order_integrity_alert.rb +112 -0
  139. data/app/services/spree_cm_commissioner/telegram_alerts/order_integrity_checks_runner.rb +32 -0
  140. data/app/services/spree_cm_commissioner/url_embed/youtube_embed.rb +44 -0
  141. data/app/services/spree_cm_commissioner/vote_counters/audit_counters.rb +43 -0
  142. data/app/services/spree_cm_commissioner/vote_counters/base.rb +31 -0
  143. data/app/services/spree_cm_commissioner/vote_counters/increment.rb +44 -0
  144. data/app/services/spree_cm_commissioner/vote_counters/per_contestant_counter.rb +68 -0
  145. data/app/services/spree_cm_commissioner/vote_counters/rebuild_from_db.rb +70 -0
  146. data/app/services/spree_cm_commissioner/vote_counters/snapshot_to_db.rb +113 -0
  147. data/app/services/spree_cm_commissioner/vote_credit_deductor.rb +68 -0
  148. data/app/services/spree_cm_commissioner/vote_package/create.rb +145 -0
  149. data/app/services/spree_cm_commissioner/vote_package/update.rb +91 -0
  150. data/app/services/spree_cm_commissioner/vote_processor.rb +144 -0
  151. data/app/services/spree_cm_commissioner/voting_contestants/advancer.rb +335 -0
  152. data/app/services/spree_cm_commissioner/voting_contestants/assigner.rb +40 -0
  153. data/app/services/spree_cm_commissioner/voting_contestants/bulk_updater.rb +106 -0
  154. data/app/services/spree_cm_commissioner/voting_credits/allocate.rb +77 -0
  155. data/app/services/spree_cm_commissioner/voting_credits/claim_free_votes.rb +119 -0
  156. data/app/services/spree_cm_commissioner/voting_credits/credit_calculator.rb +35 -0
  157. data/app/services/spree_cm_commissioner/voting_credits/de_allocate.rb +87 -0
  158. data/app/services/spree_cm_commissioner/voting_leaderboards/calculate_score.rb +67 -0
  159. data/app/services/spree_cm_commissioner/voting_leaderboards/combined_result.rb +190 -0
  160. data/app/services/spree_cm_commissioner/voting_sessions/finalize.rb +105 -0
  161. data/app/services/spree_cm_commissioner/waiting_room/stamp_queue_positions.rb +66 -24
  162. data/app/views/spree/admin/homepage_section/_form.html.erb +5 -0
  163. data/app/views/spree/admin/import_new_orders/_cancel_modal.html.erb +35 -0
  164. data/app/views/spree/admin/import_new_orders/index.html.erb +4 -0
  165. data/app/views/spree/admin/import_new_orders/show.html.erb +30 -0
  166. data/app/views/spree/admin/imports/index.html.erb +2 -0
  167. data/app/views/spree/admin/inventory_holds/index.html.erb +8 -3
  168. data/app/views/spree_cm_commissioner/order_mailer/purchased_items/_items.html.erb +12 -9
  169. data/config/initializers/spree_permitted_attributes.rb +12 -0
  170. data/config/locales/en.yml +54 -8
  171. data/config/locales/km.yml +38 -6
  172. data/config/routes.rb +29 -0
  173. data/db/migrate/20260309230148_create_cm_show_people.rb +14 -0
  174. data/db/migrate/20260309230149_create_cm_show_people_assignments.rb +16 -0
  175. data/db/migrate/20260310082711_create_cm_show_contestants.rb +28 -0
  176. data/db/migrate/20260310082720_create_cm_voting_sessions.rb +21 -0
  177. data/db/migrate/20260310082721_create_cm_voting_contestants.rb +23 -0
  178. data/db/migrate/20260310082734_add_voting_fields_to_spree_taxons.rb +9 -0
  179. data/db/migrate/20260310082735_add_type_to_spree_products.rb +6 -0
  180. data/db/migrate/20260310082749_create_cm_voting_credits.rb +27 -0
  181. data/db/migrate/20260326080200_create_cm_voting_credit_transactions.rb +27 -0
  182. data/db/migrate/20260330160000_create_cm_votes.rb +25 -0
  183. data/db/migrate/20260401072500_add_advanced_from_to_cm_voting_contestants.rb +7 -0
  184. data/db/migrate/20260402000001_add_voting_credit_scope_to_spree_taxons.rb +6 -0
  185. data/db/migrate/20260402000002_rename_scopeable_to_votable_in_cm_voting_credits.rb +12 -0
  186. data/db/migrate/20260403070000_add_name_to_cm_voting_sessions.rb +5 -0
  187. data/db/migrate/20260406000001_add_vendor_id_to_voting_tables.rb +6 -0
  188. data/db/migrate/20260406000001_rename_votes_remaining_to_amount_in_cm_voting_credits.rb +11 -0
  189. data/db/migrate/20260408085255_add_show_id_and_vendor_id_to_cm_voting_sessions.rb +9 -0
  190. data/db/migrate/20260420000001_rename_type_to_credit_type_in_cm_voting_credits.rb +25 -0
  191. data/db/migrate/20260422000001_create_cm_vote_fraud_events.rb +23 -0
  192. data/db/migrate/20260423000001_add_preview_to_taxons_products_and_sections.rb +11 -0
  193. data/db/migrate/20260423000002_create_preview_roles.rb +24 -0
  194. data/db/migrate/20260515120000_add_public_metadata_to_cm_voting_sessions.rb +5 -0
  195. data/db/migrate/20260518090920_add_unique_voter_count_to_voting_contestants.rb +5 -0
  196. data/db/migrate/20260518094322_create_cm_voting_session_stats.rb +17 -0
  197. data/db/migrate/20260520000001_add_scoring_model_to_cm_voting_sessions.rb +5 -0
  198. data/db/migrate/20260520000001_optimize_cm_votes_indexes.rb +22 -0
  199. data/db/migrate/20260525042257_add_vote_number_to_cm_voting_contestants.rb +18 -0
  200. data/db/migrate/20260527035430_add_confirmed_rank_to_cm_voting_contestants.rb +5 -0
  201. data/db/migrate/20260527062005_add_eliminated_at_to_cm_show_contestants.rb +5 -0
  202. data/db/migrate/20260603063652_add_parent_to_voting_sessions.rb +9 -0
  203. data/db/migrate/20260603090000_add_session_type_to_cm_voting_sessions.rb +6 -0
  204. data/db/migrate/20260608000000_add_display_to_cm_voting_sessions.rb +5 -0
  205. data/db/migrate/20260616000001_add_canceled_by_id_to_cm_imports.rb +5 -0
  206. data/db/migrate/20260617000002_create_cm_imported_orders.rb +12 -0
  207. data/db/migrate/20260619000001_create_cm_idempotency_keys.rb +11 -0
  208. data/docs/sql/jsonb_query_guide.md +57 -0
  209. data/lib/spree_cm_commissioner/test_helper/factories/show_episode_factory.rb +12 -0
  210. data/lib/spree_cm_commissioner/test_helper/factories/show_factory.rb +120 -0
  211. data/lib/spree_cm_commissioner/test_helper/factories/vote_credit_factory.rb +37 -0
  212. data/lib/spree_cm_commissioner/test_helper/factories/vote_factory.rb +28 -0
  213. data/lib/spree_cm_commissioner/test_helper/factories/voting_credit_transaction_factory.rb +11 -0
  214. data/lib/spree_cm_commissioner/test_helper/factories/voting_session_factory.rb +11 -0
  215. data/lib/spree_cm_commissioner/version.rb +1 -1
  216. data/lib/spree_cm_commissioner.rb +7 -1
  217. data/spree_cm_commissioner.gemspec +1 -1
  218. metadata +149 -4
@@ -0,0 +1,52 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class ShowContestantsController < BaseController
6
+ private
7
+
8
+ # override
9
+ def model_class
10
+ SpreeCmCommissioner::ShowContestant
11
+ end
12
+
13
+ # override
14
+ def scope
15
+ return model_class.none unless current_show
16
+
17
+ model_class
18
+ .where(show_id: current_show.id)
19
+ .includes(
20
+ :show_contestant_images,
21
+ :show_contestant_videos,
22
+ :current_voting_session,
23
+ :voting_contestants,
24
+ assigned_show_person: :show_person_images,
25
+ show: [
26
+ :parent,
27
+ { episodes: :voting_sessions },
28
+ { seasons: { episodes: :voting_sessions } }
29
+ ]
30
+ )
31
+ end
32
+
33
+ def current_show
34
+ @current_show ||= show_scope.find_by!(slug: params[:show_id])
35
+ end
36
+
37
+ def show_scope
38
+ vendor_ids = @tenant.vendors.select(:id)
39
+ return SpreeCmCommissioner::Show.none if vendor_ids.blank?
40
+
41
+ SpreeCmCommissioner::Show.where(vendor_id: vendor_ids)
42
+ end
43
+
44
+ # override
45
+ def resource_serializer
46
+ Spree::V2::Tenant::ShowContestantSerializer
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,57 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class ShowEliminationSessionsController < BaseController
6
+ # override
7
+ def collection
8
+ @collection ||= scope
9
+ .includes(
10
+ :episode,
11
+ :show,
12
+ eliminated_voting_contestants: [
13
+ :show_contestant,
14
+ { show_contestant: %i[show_contestant_images show_contestant_videos] }
15
+ ]
16
+ )
17
+ .order('cm_voting_sessions.closes_at DESC')
18
+ end
19
+
20
+ # override
21
+ def collection_serializer
22
+ Spree::V2::Tenant::VotingSessionSerializer
23
+ end
24
+
25
+ private
26
+
27
+ # override
28
+ def model_class
29
+ SpreeCmCommissioner::VotingSession
30
+ end
31
+
32
+ # override
33
+ def scope
34
+ return model_class.none unless current_show
35
+
36
+ current_show.voting_sessions
37
+ .joins(:episode)
38
+ .joins(:voting_contestants)
39
+ .where(voting_contestants: { eliminated: true })
40
+ .distinct
41
+ end
42
+
43
+ def current_show
44
+ @current_show ||= show_scope.find_by!(slug: params[:show_id])
45
+ end
46
+
47
+ def show_scope
48
+ vendor_ids = @tenant.vendors.select(:id)
49
+ return SpreeCmCommissioner::Show.none if vendor_ids.blank?
50
+
51
+ SpreeCmCommissioner::Show.where(vendor_id: vendor_ids)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,49 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class ShowPeopleController < BaseController
6
+ private
7
+
8
+ # override
9
+ def model_class
10
+ SpreeCmCommissioner::ShowPerson
11
+ end
12
+
13
+ # override
14
+ def scope
15
+ scope = current_show.show_people
16
+ .joins(:assignments)
17
+ .distinct
18
+ .includes(
19
+ :assignments,
20
+ assigned_contestants: %i[show_contestant_images current_voting_session]
21
+ )
22
+
23
+ role = params[:role].presence
24
+ return scope if role.blank?
25
+
26
+ role_value = SpreeCmCommissioner::ShowPersonAssignment.roles[role.to_s]
27
+ return scope.none if role_value.nil?
28
+
29
+ scope.where(cm_show_people_assignments: { role: role_value })
30
+ end
31
+
32
+ # override
33
+ def collection_serializer
34
+ Spree::V2::Tenant::ShowPersonSerializer
35
+ end
36
+
37
+ def current_show
38
+ @current_show ||= show_scope.find_by!(slug: params[:show_id])
39
+ end
40
+
41
+ def show_scope
42
+ vendor_ids = @tenant.vendors.select(:id)
43
+ SpreeCmCommissioner::Show.where(vendor_id: vendor_ids)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class ShowPersonAssignmentsController < BaseController
6
+ private
7
+
8
+ def model_class
9
+ SpreeCmCommissioner::ShowPersonAssignment
10
+ end
11
+
12
+ def scope
13
+ vendor_ids = @tenant.vendors.select(:id)
14
+ show_ids = SpreeCmCommissioner::Show.where(vendor_id: vendor_ids).select(:id)
15
+
16
+ model_class.where(show_id: show_ids)
17
+ .where(show_person_id: params[:person_id], show_id: current_show.id)
18
+ .includes(:show_person, :show)
19
+ end
20
+
21
+ def current_show
22
+ @current_show ||= SpreeCmCommissioner::Show.find_by!(slug: params[:show_id])
23
+ end
24
+
25
+ def resource_serializer
26
+ Spree::V2::Tenant::ShowPersonAssignmentSerializer
27
+ end
28
+
29
+ def collection_serializer
30
+ Spree::V2::Tenant::ShowPersonAssignmentSerializer
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -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,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
@@ -4,14 +4,30 @@
4
4
  module SpreeCmCommissioner
5
5
  module Orders
6
6
  class Find
7
+ CURRENT_CART_STATES = %w[cart address].freeze
8
+ CART_MAX_AGE_IN_MINUTES = ENV.fetch('CART_MAX_AGE_IN_MINUTES', '720').to_i # Default: 12 hours
9
+
7
10
  def execute(store:, user:, currency:, token: nil, state: nil)
11
+ state = Array(state).map(&:to_s)
12
+
8
13
  params = { store_id: store.id, currency: currency }
9
14
  params[:state] = state if state.present?
10
15
 
11
- return find_by_token(params, token) if token.present?
12
- return find_by_user(params, user) if user.present?
16
+ order = if token.present?
17
+ find_by_token(params, token)
18
+ elsif user.present?
19
+ find_by_user(params, user)
20
+ end
21
+
22
+ # Only enforce hold and age expiration when fetching current cart states ('cart' or 'address').
23
+ # For other states (like 'payment,complete'), we want to return the order even if it's old or expired
24
+ # because some pages on client still need some specific state of order to display.
25
+ if state.intersect?(CURRENT_CART_STATES)
26
+ return nil if hold_expired?(order)
27
+ return nil if cart_too_old?(order)
28
+ end
13
29
 
14
- nil
30
+ order
15
31
  end
16
32
 
17
33
  def find_by_token(params, token)
@@ -24,6 +40,20 @@ module SpreeCmCommissioner
24
40
  scope.order(created_at: :desc).find_by(params)
25
41
  end
26
42
 
43
+ def hold_expired?(order)
44
+ return false if order.nil?
45
+
46
+ # Check if the order has a hold_expires_at column and if it has already passed
47
+ order.hold_expires_at.present? && order.hold_expires_at < Time.current
48
+ end
49
+
50
+ def cart_too_old?(order)
51
+ return false if order.nil?
52
+
53
+ # Treat the cart as active based on its last update time, not its creation time.
54
+ order.updated_at < CART_MAX_AGE_IN_MINUTES.minutes.ago
55
+ end
56
+
27
57
  private
28
58
 
29
59
  def scope
@@ -0,0 +1,11 @@
1
+ module SpreeCmCommissioner
2
+ module Orders
3
+ class FindByAllState < Spree::Orders::FindComplete
4
+ private
5
+
6
+ def scope
7
+ user? ? user.orders.includes(scope_includes) : Spree::Order.includes(scope_includes)
8
+ end
9
+ end
10
+ end
11
+ 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
@@ -3,8 +3,11 @@ require 'google/cloud/firestore'
3
3
  # TODO: alert when available_slots is negative.
4
4
  module SpreeCmCommissioner
5
5
  class WaitingGuestsCaller < BaseInteractor
6
- MIN_WAIT_TO_ENTER_SECONDS = 120 # 2 min floor — Waiting Room step (full journey, more uncertainty)
7
- MIN_QUEUE_TO_ENTER_SECONDS = 60 # 1 min floor — Queue step (position assigned, one caller cycle minimum)
6
+ # 10 min floor — Waiting Room step (full journey, most uncertainty)
7
+ MIN_WAIT_TO_ENTER_SECONDS = (ENV['WAITING_ROOM_MIN_WAIT_TO_ENTER_SECONDS'] || 600).to_i
8
+
9
+ # 5 min floor — Queue step (position assigned, sub-segment of the journey)
10
+ MIN_QUEUE_TO_ENTER_SECONDS = (ENV['WAITING_ROOM_MIN_QUEUE_TO_ENTER_SECONDS'] || 300).to_i
8
11
 
9
12
  # Firestore bounds a batch update by payload size (10 MiB); 500 ops/commit leaves us far under that.
10
13
  FIRESTORE_BATCH_SIZE = (ENV['WAITING_ROOM_FIRESTORE_BATCH_SIZE'] || 500).to_i
@@ -20,7 +23,8 @@ module SpreeCmCommissioner
20
23
  full: long_waiting_guests.size >= available_slots,
21
24
  available_slots: available_slots - long_waiting_guests.size,
22
25
  avg_wait_to_enter_seconds: compute_avg_wait_to_enter_seconds(long_waiting_guests),
23
- avg_queue_to_enter_seconds: compute_avg_queue_to_enter_seconds(long_waiting_guests)
26
+ avg_queue_to_enter_seconds: compute_avg_queue_to_enter_seconds(long_waiting_guests),
27
+ slots_per_call: available_slots
24
28
  )
25
29
  end
26
30
 
@@ -96,10 +100,18 @@ module SpreeCmCommissioner
96
100
  end
97
101
 
98
102
  # merge: true so we preserve the published `waiting_guests_records_path` on the lobby doc.
99
- def mark_as(full:, available_slots:, avg_wait_to_enter_seconds: nil, avg_queue_to_enter_seconds: nil)
103
+ def mark_as(
104
+ full:,
105
+ available_slots:,
106
+ avg_wait_to_enter_seconds: nil,
107
+ avg_queue_to_enter_seconds: nil,
108
+ slots_per_call: nil
109
+ )
100
110
  data = { full: full, available_slots: available_slots }
101
111
  data[:avg_wait_to_enter_seconds] = avg_wait_to_enter_seconds if avg_wait_to_enter_seconds
102
112
  data[:avg_queue_to_enter_seconds] = avg_queue_to_enter_seconds if avg_queue_to_enter_seconds
113
+ data[:slots_per_call] = slots_per_call if slots_per_call
114
+
103
115
  lobby_document.set(data, merge: true)
104
116
  end
105
117
 
@@ -0,0 +1,12 @@
1
+ module SpreeCmCommissioner
2
+ module IdempotentJob
3
+ def with_idempotency(key = nil, &block)
4
+ SpreeCmCommissioner::IdempotencyKey.run_once(key.presence || default_idempotency_key, &block)
5
+ end
6
+
7
+ # Namespaced so the key is self-describing in the DB (no `source` column needed).
8
+ def default_idempotency_key
9
+ "#{self.class.name}:#{job_id}"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ module SpreeCmCommissioner
2
+ class CancelImportOrderJob < ApplicationUniqueJob
3
+ def perform(options = {})
4
+ SpreeCmCommissioner::Imports::Orders::Cancel.new(
5
+ import_order_id: options[:import_order_id],
6
+ canceled_by_user_id: options[:canceled_by_user_id],
7
+ cancellation_reason: options[:cancellation_reason]
8
+ ).call
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # Prunes old idempotency keys. Keys only matter within the retry window
2
+ # (minutes–hours), so anything older than the cutoff is safe to delete.
3
+ # The `created_at` index supports the delete.
4
+ module SpreeCmCommissioner
5
+ module IdempotencyKeys
6
+ class PruneJob < SpreeCmCommissioner::ApplicationJob
7
+ DEFAULT_CUTOFF_DAYS = 3
8
+
9
+ queue_as :default
10
+
11
+ def perform(cutoff_days = DEFAULT_CUTOFF_DAYS)
12
+ days = [cutoff_days.to_i, 1].max
13
+
14
+ SpreeCmCommissioner::IdempotencyKey
15
+ .where(created_at: ...days.days.ago)
16
+ .delete_all
17
+ end
18
+ end
19
+ end
20
+ end