spree_cm_commissioner 2.8.3 → 2.8.4.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 (312) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +2 -1
  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/guests_controller.rb +1 -1
  7. data/app/controllers/spree/admin/homepage_section_controller.rb +4 -1
  8. data/app/controllers/spree/admin/order_ticket_transfers_controller.rb +25 -0
  9. data/app/controllers/spree/admin/stock_managements_controller.rb +1 -1
  10. data/app/controllers/spree/admin/system/waiting_room_controller.rb +15 -0
  11. data/app/controllers/spree/admin/taxons_controller_decorator.rb +24 -0
  12. data/app/controllers/spree/admin/ticket_transfer_rules_controller.rb +47 -0
  13. data/app/controllers/spree/admin/ticket_transfers_controller.rb +61 -0
  14. data/app/controllers/spree/api/v2/storefront/account/recipients_controller.rb +32 -0
  15. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +7 -5
  16. data/app/controllers/spree/api/v2/storefront/homepage_sections_controller.rb +1 -0
  17. data/app/controllers/spree/api/v2/storefront/preview_products_controller.rb +48 -0
  18. data/app/controllers/spree/api/v2/storefront/preview_sections_controller.rb +27 -0
  19. data/app/controllers/spree/api/v2/storefront/preview_taxons_controller.rb +18 -0
  20. data/app/controllers/spree/api/v2/storefront/products_controller_decorator.rb +15 -0
  21. data/app/controllers/spree/api/v2/storefront/taxons_controller_decorator.rb +15 -0
  22. data/app/controllers/spree/api/v2/storefront/ticket_transfer_acceptances_controller.rb +32 -0
  23. data/app/controllers/spree/api/v2/storefront/ticket_transfers_controller.rb +117 -0
  24. data/app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb +3 -1
  25. data/app/controllers/spree/api/v2/tenant/ad_campaigns_controller.rb +25 -0
  26. data/app/controllers/spree/api/v2/tenant/base_controller.rb +4 -0
  27. data/app/controllers/spree/api/v2/tenant/episodes_controller.rb +38 -0
  28. data/app/controllers/spree/api/v2/tenant/free_vote_claim_controller.rb +58 -0
  29. data/app/controllers/spree/api/v2/tenant/homepage_sections_controller.rb +1 -0
  30. data/app/controllers/spree/api/v2/tenant/preview_products_controller.rb +47 -0
  31. data/app/controllers/spree/api/v2/tenant/preview_sections_controller.rb +26 -0
  32. data/app/controllers/spree/api/v2/tenant/preview_shows_controller.rb +19 -0
  33. data/app/controllers/spree/api/v2/tenant/preview_taxons_controller.rb +19 -0
  34. data/app/controllers/spree/api/v2/tenant/products_controller.rb +1 -1
  35. data/app/controllers/spree/api/v2/tenant/show_contestants_controller.rb +52 -0
  36. data/app/controllers/spree/api/v2/tenant/show_elimination_sessions_controller.rb +57 -0
  37. data/app/controllers/spree/api/v2/tenant/show_people_controller.rb +49 -0
  38. data/app/controllers/spree/api/v2/tenant/show_person_assignments_controller.rb +36 -0
  39. data/app/controllers/spree/api/v2/tenant/shows_controller.rb +36 -0
  40. data/app/controllers/spree/api/v2/tenant/taxons_controller.rb +1 -1
  41. data/app/controllers/spree/api/v2/tenant/transit/draft_orders_controller.rb +3 -1
  42. data/app/controllers/spree/api/v2/tenant/trips_controller.rb +11 -0
  43. data/app/controllers/spree/api/v2/tenant/vote_packages_controller.rb +33 -0
  44. data/app/controllers/spree/api/v2/tenant/votes_controller.rb +94 -0
  45. data/app/controllers/spree/api/v2/tenant/voting_contestants_controller.rb +43 -0
  46. data/app/controllers/spree/api/v2/tenant/voting_credit_transactions_controller.rb +41 -0
  47. data/app/controllers/spree/api/v2/tenant/voting_credits_controller.rb +31 -0
  48. data/app/controllers/spree_cm_commissioner/admin/orders_controller_decorator.rb +7 -3
  49. data/app/controllers/spree_cm_commissioner/admin/products_controller_decorator.rb +19 -0
  50. data/app/finders/spree_cm_commissioner/events/find_matches.rb +1 -0
  51. data/app/helpers/spree_cm_commissioner/admin/guest_helper.rb +10 -0
  52. data/app/helpers/spree_cm_commissioner/admin/homepage_segment_helper.rb +2 -0
  53. data/app/helpers/spree_cm_commissioner/admin/ticket_transfers_helper.rb +32 -0
  54. data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +62 -5
  55. data/app/interactors/spree_cm_commissioner/waiting_guests_caller.rb +86 -10
  56. data/app/jobs/spree_cm_commissioner/expire_ticket_transfer_job.rb +10 -0
  57. data/app/jobs/spree_cm_commissioner/show_contestants/import_job.rb +9 -0
  58. data/app/jobs/spree_cm_commissioner/telegram_gateway/pin_code_sender_job.rb +18 -0
  59. data/app/jobs/spree_cm_commissioner/vendor_route_metrics/decrease_trip_count_job.rb +17 -0
  60. data/app/jobs/spree_cm_commissioner/vendor_route_metrics/increase_trip_count_job.rb +14 -0
  61. data/app/jobs/spree_cm_commissioner/vote_fraud_event_job.rb +9 -0
  62. data/app/jobs/spree_cm_commissioner/voting_credit_allocation_job.rb +10 -0
  63. data/app/jobs/spree_cm_commissioner/voting_credit_de_allocation_job.rb +10 -0
  64. data/app/jobs/spree_cm_commissioner/waiting_room/publish_lobby_path_job.rb +17 -0
  65. data/app/jobs/spree_cm_commissioner/waiting_room/stamp_queue_positions_job.rb +84 -0
  66. data/app/models/concerns/spree_cm_commissioner/homepage_section_bitwise.rb +2 -1
  67. data/app/models/concerns/spree_cm_commissioner/line_item_guests_concern.rb +16 -2
  68. data/app/models/concerns/spree_cm_commissioner/line_item_open_dated_trippable.rb +50 -7
  69. data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +0 -1
  70. data/app/models/concerns/spree_cm_commissioner/line_items_filter_scope.rb +5 -0
  71. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +2 -1
  72. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +10 -0
  73. data/app/models/concerns/spree_cm_commissioner/product_type.rb +1 -1
  74. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +3 -1
  75. data/app/models/concerns/spree_cm_commissioner/ticket_transferable.rb +15 -0
  76. data/app/models/concerns/spree_cm_commissioner/voting_session_live_stream_concern.rb +41 -0
  77. data/app/models/spree_cm_commissioner/guest.rb +20 -0
  78. data/app/models/spree_cm_commissioner/import.rb +1 -1
  79. data/app/models/spree_cm_commissioner/imports/import_contestant.rb +7 -0
  80. data/app/models/spree_cm_commissioner/line_item_decorator.rb +7 -0
  81. data/app/models/spree_cm_commissioner/maintenance_tasks/voting_session.rb +36 -0
  82. data/app/models/spree_cm_commissioner/notification.rb +10 -1
  83. data/app/models/spree_cm_commissioner/order_decorator.rb +13 -3
  84. data/app/models/spree_cm_commissioner/payment_decorator.rb +19 -0
  85. data/app/models/spree_cm_commissioner/payment_method_decorator.rb +8 -0
  86. data/app/models/spree_cm_commissioner/pin_code.rb +62 -9
  87. data/app/models/spree_cm_commissioner/preview_role.rb +8 -0
  88. data/app/models/spree_cm_commissioner/product_decorator.rb +56 -0
  89. data/app/models/spree_cm_commissioner/reserved_block.rb +2 -0
  90. data/app/models/spree_cm_commissioner/role_decorator.rb +4 -1
  91. data/app/models/spree_cm_commissioner/role_user_decorator.rb +8 -0
  92. data/app/models/spree_cm_commissioner/show.rb +169 -0
  93. data/app/models/spree_cm_commissioner/show_contestant.rb +39 -0
  94. data/app/models/spree_cm_commissioner/show_contestant_image.rb +11 -0
  95. data/app/models/spree_cm_commissioner/show_contestant_video.rb +15 -0
  96. data/app/models/spree_cm_commissioner/show_episode.rb +108 -0
  97. data/app/models/spree_cm_commissioner/show_person.rb +15 -0
  98. data/app/models/spree_cm_commissioner/show_person_assignment.rb +21 -0
  99. data/app/models/spree_cm_commissioner/show_person_image.rb +11 -0
  100. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +4 -0
  101. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +3 -0
  102. data/app/models/spree_cm_commissioner/taxon_ad_banner.rb +12 -0
  103. data/app/models/spree_cm_commissioner/taxon_decorator.rb +24 -0
  104. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +19 -1
  105. data/app/models/spree_cm_commissioner/tenant.rb +9 -0
  106. data/app/models/spree_cm_commissioner/ticket_transfer.rb +191 -0
  107. data/app/models/spree_cm_commissioner/ticket_transfer_rule.rb +88 -0
  108. data/app/models/spree_cm_commissioner/trip.rb +33 -12
  109. data/app/models/spree_cm_commissioner/user_decorator.rb +18 -0
  110. data/app/models/spree_cm_commissioner/variant_decorator.rb +4 -0
  111. data/app/models/spree_cm_commissioner/variant_options.rb +4 -0
  112. data/app/models/spree_cm_commissioner/vendor_decorator.rb +5 -0
  113. data/app/models/spree_cm_commissioner/vendor_route_metric.rb +25 -0
  114. data/app/models/spree_cm_commissioner/vote.rb +16 -0
  115. data/app/models/spree_cm_commissioner/vote_fraud_event.rb +20 -0
  116. data/app/models/spree_cm_commissioner/voting_contestant.rb +100 -0
  117. data/app/models/spree_cm_commissioner/voting_credit.rb +72 -0
  118. data/app/models/spree_cm_commissioner/voting_credit_transaction.rb +55 -0
  119. data/app/models/spree_cm_commissioner/voting_session.rb +216 -0
  120. data/app/models/spree_cm_commissioner/voting_session_stat.rb +8 -0
  121. data/app/notifications/spree_cm_commissioner/ticket_transfer_accepted_notification.rb +48 -0
  122. data/app/notifications/spree_cm_commissioner/ticket_transfer_completed_notification.rb +48 -0
  123. data/app/notifications/spree_cm_commissioner/ticket_transfer_expired_notification.rb +46 -0
  124. data/app/notifications/spree_cm_commissioner/ticket_transfer_received_notification.rb +48 -0
  125. data/app/notifications/spree_cm_commissioner/ticket_transfer_rejected_notification.rb +48 -0
  126. data/app/overrides/spree/admin/products/_form/allow_gift_transfer.html.erb.deface +9 -0
  127. data/app/overrides/spree/admin/products/_form/allow_transfer.html.erb.deface +9 -0
  128. data/app/overrides/spree/admin/products/_form/preview_checkbox.html.erb.deface +9 -0
  129. data/app/overrides/spree/admin/shared/_order_tabs/ticket_transfers.html.erb.deface +10 -0
  130. data/app/overrides/spree/admin/shared/sub_menu/_product/ticket_transfers_tab.html.erb.deface +3 -0
  131. data/app/overrides/spree/admin/taxons/_form/assets_form.html.erb.deface +10 -1
  132. data/app/overrides/spree/admin/taxons/_form/preview_checkbox.html.erb.deface +7 -0
  133. data/app/queries/spree_cm_commissioner/multi_leg_trips_query.rb +31 -8
  134. data/app/queries/spree_cm_commissioner/recipient_searcher_query.rb +14 -0
  135. data/app/queries/spree_cm_commissioner/single_leg_trips_query.rb +57 -5
  136. data/app/serializers/spree/v2/storefront/homepage_section_serializer.rb +1 -1
  137. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +4 -2
  138. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +5 -2
  139. data/app/serializers/spree/v2/storefront/role_serializer.rb +1 -1
  140. data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +4 -1
  141. data/app/serializers/spree/v2/tenant/ad_campaign_serializer.rb +13 -0
  142. data/app/serializers/spree/v2/tenant/advertisement_serializer.rb +11 -0
  143. data/app/serializers/spree/v2/tenant/dynamic_field_serializer.rb +1 -1
  144. data/app/serializers/spree/v2/tenant/free_vote_claim_serializer.rb +9 -0
  145. data/app/serializers/spree/v2/tenant/homepage_section_serializer.rb +1 -1
  146. data/app/serializers/spree/v2/tenant/role_serializer.rb +1 -1
  147. data/app/serializers/spree/v2/tenant/show_contestant_serializer.rb +21 -0
  148. data/app/serializers/spree/v2/tenant/show_episode_serializer.rb +20 -0
  149. data/app/serializers/spree/v2/tenant/show_person_assignment_serializer.rb +16 -0
  150. data/app/serializers/spree/v2/tenant/show_person_serializer.rb +13 -0
  151. data/app/serializers/spree/v2/tenant/show_serializer.rb +21 -0
  152. data/app/serializers/spree/v2/tenant/video_serializer.rb +9 -0
  153. data/app/serializers/spree/v2/tenant/vote_package_serializer.rb +12 -0
  154. data/app/serializers/spree/v2/tenant/vote_serializer.rb +14 -0
  155. data/app/serializers/spree/v2/tenant/voting_contestant_serializer.rb +23 -0
  156. data/app/serializers/spree/v2/tenant/voting_credit_serializer.rb +10 -0
  157. data/app/serializers/spree/v2/tenant/voting_credit_transaction_serializer.rb +14 -0
  158. data/app/serializers/spree/v2/tenant/voting_session_serializer.rb +21 -0
  159. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +1 -1
  160. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_minimal_serializer.rb +30 -0
  161. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +3 -1
  162. data/app/serializers/spree_cm_commissioner/v2/storefront/line_item_minimal_serializer.rb +57 -0
  163. data/app/serializers/spree_cm_commissioner/v2/storefront/pin_code_serializer.rb +1 -1
  164. data/app/serializers/spree_cm_commissioner/v2/storefront/recipient_serializer.rb +13 -0
  165. data/app/serializers/spree_cm_commissioner/v2/storefront/reserved_block_serializer.rb +1 -1
  166. data/app/serializers/spree_cm_commissioner/v2/storefront/ticket_transfer_minimal_serializer.rb +17 -0
  167. data/app/serializers/spree_cm_commissioner/v2/storefront/ticket_transfer_serializer.rb +20 -0
  168. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_query_result_serializer.rb +4 -1
  169. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_result_serializer.rb +2 -1
  170. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +1 -0
  171. data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +12 -0
  172. data/app/services/spree_cm_commissioner/cart/add_guest.rb +6 -1
  173. data/app/services/spree_cm_commissioner/cart/create_guest.rb +61 -0
  174. data/app/services/spree_cm_commissioner/cart/remove_guest.rb +6 -1
  175. data/app/services/spree_cm_commissioner/check_ins/create_bulk.rb +11 -5
  176. data/app/services/spree_cm_commissioner/checkout/complete_decorator.rb +22 -0
  177. data/app/services/spree_cm_commissioner/fraud_check.rb +299 -0
  178. data/app/services/spree_cm_commissioner/imports/contestants/create.rb +153 -0
  179. data/app/services/spree_cm_commissioner/inventory_items/reset.rb +1 -1
  180. data/app/services/spree_cm_commissioner/open_dated_trips/redeem.rb +80 -36
  181. data/app/services/spree_cm_commissioner/reserved_blocks/reserve.rb +2 -1
  182. data/app/services/spree_cm_commissioner/show_contestants/normalize_video_highlights.rb +57 -0
  183. data/app/services/spree_cm_commissioner/show_persons/contestant_assigner.rb +51 -0
  184. data/app/services/spree_cm_commissioner/telegram_gateway/pin_code_sender.rb +99 -0
  185. data/app/services/spree_cm_commissioner/ticket_transfers/accept.rb +132 -0
  186. data/app/services/spree_cm_commissioner/ticket_transfers/cancel.rb +54 -0
  187. data/app/services/spree_cm_commissioner/ticket_transfers/complete.rb +68 -0
  188. data/app/services/spree_cm_commissioner/ticket_transfers/eligibility_checker.rb +99 -0
  189. data/app/services/spree_cm_commissioner/ticket_transfers/expire.rb +47 -0
  190. data/app/services/spree_cm_commissioner/ticket_transfers/fee_calculator.rb +57 -0
  191. data/app/services/spree_cm_commissioner/ticket_transfers/initiate.rb +98 -0
  192. data/app/services/spree_cm_commissioner/ticket_transfers/preview_rule.rb +38 -0
  193. data/app/services/spree_cm_commissioner/ticket_transfers/reject.rb +55 -0
  194. data/app/services/spree_cm_commissioner/ticket_transfers/transfer_guest.rb +29 -0
  195. data/app/services/spree_cm_commissioner/transit_order/create.rb +48 -14
  196. data/app/services/spree_cm_commissioner/trips/add_ons/create.rb +124 -0
  197. data/app/services/spree_cm_commissioner/trips/add_ons/update_price.rb +36 -0
  198. data/app/services/spree_cm_commissioner/trips/create_multi_leg.rb +1 -2
  199. data/app/services/spree_cm_commissioner/trips/variants/create.rb +1 -2
  200. data/app/services/spree_cm_commissioner/url_embed/youtube_embed.rb +44 -0
  201. data/app/services/spree_cm_commissioner/vendor_route_metrics/decrease_trip_count.rb +19 -0
  202. data/app/services/spree_cm_commissioner/vendor_route_metrics/increase_trip_count.rb +24 -0
  203. data/app/services/spree_cm_commissioner/vote_counters/audit_counters.rb +43 -0
  204. data/app/services/spree_cm_commissioner/vote_counters/base.rb +31 -0
  205. data/app/services/spree_cm_commissioner/vote_counters/increment.rb +44 -0
  206. data/app/services/spree_cm_commissioner/vote_counters/per_contestant_counter.rb +68 -0
  207. data/app/services/spree_cm_commissioner/vote_counters/rebuild_from_db.rb +70 -0
  208. data/app/services/spree_cm_commissioner/vote_counters/snapshot_to_db.rb +113 -0
  209. data/app/services/spree_cm_commissioner/vote_credit_deductor.rb +68 -0
  210. data/app/services/spree_cm_commissioner/vote_package/create.rb +145 -0
  211. data/app/services/spree_cm_commissioner/vote_package/update.rb +91 -0
  212. data/app/services/spree_cm_commissioner/vote_processor.rb +144 -0
  213. data/app/services/spree_cm_commissioner/voting_contestants/advancer.rb +335 -0
  214. data/app/services/spree_cm_commissioner/voting_contestants/assigner.rb +40 -0
  215. data/app/services/spree_cm_commissioner/voting_contestants/bulk_updater.rb +106 -0
  216. data/app/services/spree_cm_commissioner/voting_credits/allocate.rb +77 -0
  217. data/app/services/spree_cm_commissioner/voting_credits/claim_free_votes.rb +116 -0
  218. data/app/services/spree_cm_commissioner/voting_credits/credit_calculator.rb +35 -0
  219. data/app/services/spree_cm_commissioner/voting_credits/de_allocate.rb +87 -0
  220. data/app/services/spree_cm_commissioner/voting_leaderboards/calculate_score.rb +67 -0
  221. data/app/services/spree_cm_commissioner/voting_leaderboards/combined_result.rb +190 -0
  222. data/app/services/spree_cm_commissioner/voting_sessions/finalize.rb +105 -0
  223. data/app/services/spree_cm_commissioner/waiting_room/publish_lobby_path.rb +48 -0
  224. data/app/services/spree_cm_commissioner/waiting_room_lobby_metadata_fetcher.rb +47 -0
  225. data/app/services/telegram_gateway_adapter/client.rb +124 -0
  226. data/app/views/spree/admin/guests/_table.html.erb +82 -87
  227. data/app/views/spree/admin/homepage_section/_form.html.erb +5 -0
  228. data/app/views/spree/admin/order_ticket_transfers/show.html.erb +7 -0
  229. data/app/views/spree/admin/shared/_ticket_transfer_details.html.erb +226 -0
  230. data/app/views/spree/admin/shared/_ticket_transfer_tabs.html.erb +14 -0
  231. data/app/views/spree/admin/system/waiting_room/show.html.erb +53 -0
  232. data/app/views/spree/admin/ticket_transfer_rules/_form.html.erb +193 -0
  233. data/app/views/spree/admin/ticket_transfer_rules/edit.html.erb +11 -0
  234. data/app/views/spree/admin/ticket_transfer_rules/index.html.erb +79 -0
  235. data/app/views/spree/admin/ticket_transfer_rules/new.html.erb +11 -0
  236. data/app/views/spree/admin/ticket_transfers/_search_form.html.erb +70 -0
  237. data/app/views/spree/admin/ticket_transfers/_tabs.html.erb +14 -0
  238. data/app/views/spree/admin/ticket_transfers/index.html.erb +74 -0
  239. data/app/views/spree/admin/ticket_transfers/show.html.erb +13 -0
  240. data/app/views/spree/admin/ticket_transfers/state_changes.html.erb +47 -0
  241. data/config/initializers/spree_permitted_attributes.rb +8 -0
  242. data/config/locales/en.yml +111 -7
  243. data/config/locales/km.yml +105 -4
  244. data/config/routes.rb +51 -0
  245. data/db/migrate/20260303101440_add_allow_transfer_to_spree_products.rb +5 -0
  246. data/db/migrate/20260303101907_create_cm_ticket_transfers.rb +32 -0
  247. data/db/migrate/20260303102316_add_state_to_cm_guests.rb +7 -0
  248. data/db/migrate/20260309230148_create_cm_show_people.rb +14 -0
  249. data/db/migrate/20260309230149_create_cm_show_people_assignments.rb +16 -0
  250. data/db/migrate/20260310082711_create_cm_show_contestants.rb +28 -0
  251. data/db/migrate/20260310082720_create_cm_voting_sessions.rb +21 -0
  252. data/db/migrate/20260310082721_create_cm_voting_contestants.rb +23 -0
  253. data/db/migrate/20260310082734_add_voting_fields_to_spree_taxons.rb +9 -0
  254. data/db/migrate/20260310082735_add_type_to_spree_products.rb +6 -0
  255. data/db/migrate/20260310082749_create_cm_voting_credits.rb +27 -0
  256. data/db/migrate/20260326080200_create_cm_voting_credit_transactions.rb +27 -0
  257. data/db/migrate/20260330160000_create_cm_votes.rb +25 -0
  258. data/db/migrate/20260401072500_add_advanced_from_to_cm_voting_contestants.rb +7 -0
  259. data/db/migrate/20260402000001_add_voting_credit_scope_to_spree_taxons.rb +6 -0
  260. data/db/migrate/20260402000002_rename_scopeable_to_votable_in_cm_voting_credits.rb +12 -0
  261. data/db/migrate/20260403070000_add_name_to_cm_voting_sessions.rb +5 -0
  262. data/db/migrate/20260406000001_add_vendor_id_to_voting_tables.rb +6 -0
  263. data/db/migrate/20260406000001_rename_votes_remaining_to_amount_in_cm_voting_credits.rb +11 -0
  264. data/db/migrate/20260408085255_add_show_id_and_vendor_id_to_cm_voting_sessions.rb +9 -0
  265. data/db/migrate/20260408100000_create_cm_ticket_transfer_rules.rb +28 -0
  266. data/db/migrate/20260410120000_add_transfer_type_to_cm_ticket_transfers.rb +5 -0
  267. data/db/migrate/20260420000001_rename_type_to_credit_type_in_cm_voting_credits.rb +25 -0
  268. data/db/migrate/20260422000001_create_cm_vote_fraud_events.rb +23 -0
  269. data/db/migrate/20260422120000_add_settlement_status_to_cm_ticket_transfers.rb +6 -0
  270. data/db/migrate/20260423000001_add_preview_to_taxons_products_and_sections.rb +11 -0
  271. data/db/migrate/20260423000002_create_preview_roles.rb +24 -0
  272. data/db/migrate/20260507000000_add_index_to_cm_ticket_transfers_completed_at.rb +5 -0
  273. data/db/migrate/20260515120000_add_public_metadata_to_cm_voting_sessions.rb +5 -0
  274. data/db/migrate/20260518090920_add_unique_voter_count_to_voting_contestants.rb +5 -0
  275. data/db/migrate/20260518094322_create_cm_voting_session_stats.rb +17 -0
  276. data/db/migrate/20260520000000_add_number_to_cm_ticket_transfers.rb +6 -0
  277. data/db/migrate/20260520000001_add_scoring_model_to_cm_voting_sessions.rb +5 -0
  278. data/db/migrate/20260520000001_optimize_cm_votes_indexes.rb +22 -0
  279. data/db/migrate/20260525042257_add_vote_number_to_cm_voting_contestants.rb +18 -0
  280. data/db/migrate/20260527035430_add_confirmed_rank_to_cm_voting_contestants.rb +5 -0
  281. data/db/migrate/20260527062005_add_eliminated_at_to_cm_show_contestants.rb +5 -0
  282. data/db/migrate/20260603063652_add_parent_to_voting_sessions.rb +9 -0
  283. data/db/migrate/20260603090000_add_session_type_to_cm_voting_sessions.rb +6 -0
  284. data/db/migrate/20260605120908_add_description_to_cm_dynamic_fields.rb +5 -0
  285. data/db/migrate/20260608000000_add_allow_gift_transfer_to_spree_products.rb +5 -0
  286. data/db/migrate/20260608000000_add_display_to_cm_voting_sessions.rb +5 -0
  287. data/db/migrate/20260608000000_create_cm_vendor_route_metrics.rb +25 -0
  288. data/db/migrate/20260608152251_add_gender_to_cm_reserved_blocks.rb +5 -0
  289. data/db/migrate/20260610000001_add_delivery_channel_to_cm_pin_codes.rb +6 -0
  290. data/db/migrate/20260610000001_drop_is_open_dated_from_cm_trips.rb +6 -0
  291. data/db/migrate/20260614000001_change_code_limit_in_cm_pin_codes.rb +5 -0
  292. data/db/migrate/20260615000002_add_index_to_code_on_cm_pin_codes.rb +5 -0
  293. data/docs/sql/jsonb_query_guide.md +57 -0
  294. data/lib/spree_cm_commissioner/test_helper/factories/show_episode_factory.rb +12 -0
  295. data/lib/spree_cm_commissioner/test_helper/factories/show_factory.rb +120 -0
  296. data/lib/spree_cm_commissioner/test_helper/factories/ticket_transfer_factory.rb +18 -0
  297. data/lib/spree_cm_commissioner/test_helper/factories/vendor_route_metric_factory.rb +10 -0
  298. data/lib/spree_cm_commissioner/test_helper/factories/vote_credit_factory.rb +37 -0
  299. data/lib/spree_cm_commissioner/test_helper/factories/vote_factory.rb +28 -0
  300. data/lib/spree_cm_commissioner/test_helper/factories/voting_credit_transaction_factory.rb +11 -0
  301. data/lib/spree_cm_commissioner/test_helper/factories/voting_session_factory.rb +11 -0
  302. data/lib/spree_cm_commissioner/transit/trip_form.rb +0 -13
  303. data/lib/spree_cm_commissioner/trip_query_result.rb +46 -6
  304. data/lib/spree_cm_commissioner/trip_result.rb +9 -7
  305. data/lib/spree_cm_commissioner/version.rb +1 -1
  306. data/lib/spree_cm_commissioner.rb +7 -1
  307. data/lib/tasks/backfill_route_metrics.rake +21 -0
  308. data/lib/tasks/backfill_vendor_route_metrics.rake +23 -0
  309. data/spree_cm_commissioner.gemspec +1 -1
  310. metadata +217 -6
  311. data/app/services/spree_cm_commissioner/trips/create_open_dated_trip.rb +0 -98
  312. data/app/services/spree_cm_commissioner/trips/update_open_dated_trip.rb +0 -67
@@ -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
@@ -40,9 +40,13 @@ module SpreeCmCommissioner
40
40
 
41
41
  # override
42
42
  def cancel
43
- internal_note = params.dig(:order, :internal_note)
44
- @order.canceled_by(try_spree_current_user, cancellation_reason: internal_note)
45
- flash[:success] = Spree.t(:order_canceled)
43
+ if @order.guests_in_transfer?
44
+ flash[:error] = Spree.t('admin.orders.cannot_cancel_guest_transfer')
45
+ else
46
+ internal_note = params.dig(:order, :internal_note)
47
+ @order.canceled_by(try_spree_current_user, cancellation_reason: internal_note)
48
+ flash[:success] = Spree.t(:order_canceled)
49
+ end
46
50
  redirect_back fallback_location: spree.edit_admin_order_url(@order)
47
51
  end
48
52
 
@@ -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
@@ -61,6 +61,16 @@ module SpreeCmCommissioner
61
61
  'badge-info'
62
62
  end
63
63
  end
64
+
65
+ def guest_state_badge(guest)
66
+ badge_class = case guest.state
67
+ when 'active' then 'badge-success'
68
+ when 'locked' then 'badge-warning'
69
+ when 'transferred' then 'badge-secondary'
70
+ else 'badge-info'
71
+ end
72
+ content_tag(:span, guest.state.humanize, class: "badge #{badge_class}")
73
+ end
64
74
  end
65
75
  end
66
76
  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,32 @@
1
+ module SpreeCmCommissioner
2
+ module Admin
3
+ module TicketTransfersHelper
4
+ def ticket_transfer_state_badge(state)
5
+ badge_class = case state
6
+ when 'pending' then 'badge-warning'
7
+ when 'accepted' then 'badge-info'
8
+ when 'completed' then 'badge-success'
9
+ when 'rejected' then 'badge-danger'
10
+ when 'expired' then 'badge-danger' # rubocop:disable Lint/DuplicateBranch
11
+ when 'cancelled' then 'badge-secondary'
12
+ else 'badge-secondary' # rubocop:disable Lint/DuplicateBranch
13
+ end
14
+
15
+ content_tag(:span, state.humanize, class: "badge #{badge_class}")
16
+ end
17
+
18
+ def ticket_transfer_settlement_status_badge(ticket_transfer)
19
+ badge_class = case ticket_transfer.settlement_status
20
+ when 'pending' then 'badge-warning'
21
+ when 'ready' then 'badge-info'
22
+ when 'disputed' then 'badge-danger'
23
+ when 'resolved' then 'badge-primary'
24
+ when 'completed' then 'badge-success'
25
+ else 'badge-secondary'
26
+ end
27
+
28
+ content_tag(:span, ticket_transfer.settlement_status.humanize, class: "badge #{badge_class}")
29
+ end
30
+ end
31
+ end
32
+ end
@@ -6,7 +6,7 @@ module SpreeCmCommissioner
6
6
  context.fail!(message: I18n.t('pincode_sender.pincode.blank')) if context.pin_code.nil?
7
7
 
8
8
  if context.pin_code.phone_number?
9
- send_sms
9
+ send_phone_otp
10
10
  else
11
11
  send_email
12
12
  end
@@ -16,18 +16,73 @@ module SpreeCmCommissioner
16
16
 
17
17
  private
18
18
 
19
- def send_sms
19
+ # Channel selection for phone-number OTPs lives here so the routing is
20
+ # easy to find and reason about. Telegram Gateway is primary; SMS is the fallback.
21
+ def send_phone_otp
22
+ if telegram_gateway_available?
23
+ send_via_telegram_gateway
24
+ else
25
+ send_via_sms
26
+ end
27
+ end
28
+
29
+ def telegram_gateway_available?
30
+ return false unless telegram_gateway_enabled?
31
+ return false if telegram_gateway_api_key.blank?
32
+
33
+ ability = telegram_gateway_adapter.check_send_ability(context.pin_code.contact)
34
+ context.telegram_gateway_request_id = ability.request_id if ability.ok?
35
+ ability.ok?
36
+ rescue TelegramGatewayAdapter::Error, Faraday::Error
37
+ false
38
+ end
39
+
40
+ def send_via_telegram_gateway
20
41
  from_number = sms_from_number
21
42
  return if from_number.blank?
22
43
 
23
- options = {
44
+ # Save the chosen channel + the Telegram request_id synchronously so
45
+ # (a) the API response already reflects the channel for the mobile UI,
46
+ # (b) verification can reach Telegram even if the user submits the OTP
47
+ # before the async send job has run. For Telegram-delivered codes
48
+ # the `code` column stores Telegram's request_id (Telegram generates
49
+ # and holds the actual OTP).
50
+ context.pin_code.update!(
51
+ delivery_channel: :telegram_gateway,
52
+ code: context.telegram_gateway_request_id
53
+ )
54
+
55
+ SpreeCmCommissioner::TelegramGateway::PinCodeSenderJob.perform_later(
56
+ pin_code_id: context.pin_code.id,
57
+ from: from_number,
58
+ request_id: context.telegram_gateway_request_id,
59
+ tenant_id: context.pin_code&.application&.tenant_id
60
+ )
61
+ end
62
+
63
+ def send_via_sms
64
+ from_number = sms_from_number
65
+ return if from_number.blank?
66
+
67
+ SpreeCmCommissioner::SmsPinCodeJob.perform_later(
68
+ pin_code_id: context.pin_code.id,
24
69
  from: from_number,
25
70
  to: context.pin_code.contact,
26
71
  body: I18n.t('pincode_sender.sms.body', code: context.pin_code.code, readable_type: context.pin_code.readable_type),
27
72
  tenant_id: context.pin_code&.application&.tenant_id
28
- }
73
+ )
74
+ end
75
+
76
+ def telegram_gateway_enabled?
77
+ ENV['TELEGRAM_GATEWAY_ENABLED'] == 'yes'
78
+ end
29
79
 
30
- SpreeCmCommissioner::SmsPinCodeJob.perform_later(options)
80
+ def telegram_gateway_api_key
81
+ @telegram_gateway_api_key ||= ENV.fetch('TELEGRAM_GATEWAY_API_KEY', nil)
82
+ end
83
+
84
+ def telegram_gateway_adapter
85
+ @telegram_gateway_adapter ||= TelegramGatewayAdapter::Client.new
31
86
  end
32
87
 
33
88
  def sms_from_number
@@ -38,6 +93,8 @@ module SpreeCmCommissioner
38
93
  end
39
94
 
40
95
  def send_email
96
+ context.pin_code.update!(delivery_channel: :email)
97
+
41
98
  SpreeCmCommissioner::PinCodeMailer.send_pin_code(
42
99
  context.pin_code.id,
43
100
  context.pin_code.readable_type,
@@ -3,6 +3,9 @@ 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)
8
+
6
9
  def call
7
10
  available_slots = fetch_available_slots
8
11
  return unless available_slots.positive?
@@ -10,7 +13,12 @@ module SpreeCmCommissioner
10
13
  long_waiting_guests = fetch_long_waiting_guests(available_slots)
11
14
  calling_all(long_waiting_guests)
12
15
 
13
- mark_as(full: long_waiting_guests.size >= available_slots, available_slots: available_slots - long_waiting_guests.size)
16
+ mark_as(
17
+ full: long_waiting_guests.size >= available_slots,
18
+ available_slots: available_slots - long_waiting_guests.size,
19
+ avg_wait_to_enter_seconds: compute_avg_wait_to_enter_seconds(long_waiting_guests),
20
+ avg_queue_to_enter_seconds: compute_avg_queue_to_enter_seconds(long_waiting_guests)
21
+ )
14
22
  end
15
23
 
16
24
  def fetch_available_slots
@@ -19,18 +27,47 @@ module SpreeCmCommissioner
19
27
  max_sessions - active_sessions
20
28
  end
21
29
 
22
- # This query required index. create them in Firebase beforehand.
23
- # Client side must create waiting_guests document with :queued_at & :allow_to_enter_room_at to null to allow fillter & order.
30
+ # This query requires an index; create it in Firebase beforehand.
31
+ # Client must create waiting_guests documents with :queued_at and :allow_to_enter_room_at set to nil to allow filter + order queries.
32
+ #
33
+ # Yesterday's guests are always older than today's, so fill from yesterday first, then use any
34
+ # leftover slots for today. This way no one queued before the midnight rollover gets skipped.
35
+ # e.g. 5 slots, 2 waiting in yesterday -> take both, then take 3 from today.
24
36
  def fetch_long_waiting_guests(available_slots)
25
- firestore.col('waiting_guests')
26
- .doc(current_date)
27
- .col('records')
37
+ previous_guests = eligible_guests_in(previous_records_path, available_slots)
38
+
39
+ # Pre-flip window: the lobby pointer still points at yesterday, so both paths resolve to the
40
+ # same partition — return now to avoid querying (and double-counting) it twice.
41
+ return previous_guests if records_path == previous_records_path
42
+
43
+ remaining_slots = available_slots - previous_guests.size
44
+ return previous_guests if remaining_slots <= 0
45
+
46
+ previous_guests + eligible_guests_in(records_path, remaining_slots)
47
+ end
48
+
49
+ def eligible_guests_in(records_path, limit)
50
+ firestore.col(records_path)
28
51
  .where('allow_to_enter_room_at', '==', nil)
29
52
  .order('queued_at')
30
- .limit(available_slots)
53
+ .limit(limit)
31
54
  .get.to_a
32
55
  end
33
56
 
57
+ # Published path is authoritative; fall back to the server's own date if not yet published.
58
+ def records_path
59
+ lobby_data&.dig(:waiting_guests_records_path).presence || default_records_path(current_date)
60
+ end
61
+
62
+ # Drain target is derived from the server date, never the (possibly stale) lobby pointer.
63
+ def previous_records_path
64
+ default_records_path(previous_date)
65
+ end
66
+
67
+ def default_records_path(date)
68
+ "waiting_guests/#{date}/records"
69
+ end
70
+
34
71
  # For alert waiting guests to enter room, we just update :allow_to_enter_room_at.
35
72
  # App will listen to firebase & start refresh session token to enter room.
36
73
  def calling_all(waiting_guests)
@@ -45,9 +82,48 @@ module SpreeCmCommissioner
45
82
  Time.zone.now.strftime('%Y-%m-%d')
46
83
  end
47
84
 
48
- # When open app, app request to check whether room is full or not via Firebase instead of server to minimize server requests.
49
- def mark_as(full:, available_slots:)
50
- firestore.col('waiting_rooms').doc('lobby').set({ full: full, available_slots: available_slots })
85
+ def previous_date
86
+ 1.day.ago.strftime('%Y-%m-%d')
87
+ end
88
+
89
+ # merge: true so we preserve the published `waiting_guests_records_path` on the lobby doc.
90
+ def mark_as(full:, available_slots:, avg_wait_to_enter_seconds: nil, avg_queue_to_enter_seconds: nil)
91
+ data = { full: full, available_slots: available_slots }
92
+ data[:avg_wait_to_enter_seconds] = avg_wait_to_enter_seconds if avg_wait_to_enter_seconds
93
+ data[:avg_queue_to_enter_seconds] = avg_queue_to_enter_seconds if avg_queue_to_enter_seconds
94
+ lobby_document.set(data, merge: true)
95
+ end
96
+
97
+ # Average total wait (queued_at → allow_to_enter_room_at) for just-called guests.
98
+ # Used for the Waiting Room step ETA: how long until I get in from when I joined.
99
+ def compute_avg_wait_to_enter_seconds(guests)
100
+ return nil if guests.empty?
101
+
102
+ now = Time.zone.now
103
+ total = guests.sum { |doc| (now - doc.data[:queued_at].to_time).to_f }
104
+ [(total / guests.size).round, MIN_WAIT_TO_ENTER_SECONDS].max
105
+ end
106
+
107
+ # Average queue wait (position_set_at → allow_to_enter_room_at) for just-called guests.
108
+ # Used for the Queue step ETA: how long after getting a position until I actually enter.
109
+ # Returns nil when none of the called guests had position_set_at (first run, or no prior stamp).
110
+ def compute_avg_queue_to_enter_seconds(guests)
111
+ return nil if guests.empty?
112
+
113
+ now = Time.zone.now
114
+ positioned = guests.select { |doc| doc.data[:position_set_at] }
115
+ return nil if positioned.empty?
116
+
117
+ total = positioned.sum { |doc| (now - doc.data[:position_set_at].to_time).to_f }
118
+ [(total / positioned.size).round, MIN_QUEUE_TO_ENTER_SECONDS].max
119
+ end
120
+
121
+ def lobby_data
122
+ @lobby_data ||= lobby_document.get.data
123
+ end
124
+
125
+ def lobby_document
126
+ @lobby_document ||= firestore.col('waiting_rooms').doc('lobby')
51
127
  end
52
128
 
53
129
  def fetch_max_sessions
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ class ExpireTicketTransferJob < ApplicationJob
3
+ def perform(transfer_id)
4
+ ticket_transfer = SpreeCmCommissioner::TicketTransfer.find_by(id: transfer_id)
5
+ return unless ticket_transfer
6
+
7
+ SpreeCmCommissioner::TicketTransfers::Expire.call(ticket_transfer: ticket_transfer)
8
+ end
9
+ end
10
+ 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,18 @@
1
+ module SpreeCmCommissioner
2
+ module TelegramGateway
3
+ # options = { pin_code_id:, from:, request_id:, tenant_id: }
4
+ class PinCodeSenderJob < SpreeCmCommissioner::SmsJob
5
+ def perform(options = {})
6
+ pin_code = SpreeCmCommissioner::PinCode.find_by(id: options[:pin_code_id])
7
+ return if pin_code.nil?
8
+
9
+ SpreeCmCommissioner::TelegramGateway::PinCodeSender.call(
10
+ pin_code: pin_code,
11
+ from: options[:from],
12
+ request_id: options[:request_id],
13
+ tenant_id: options[:tenant_id]
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module SpreeCmCommissioner
2
+ module VendorRouteMetrics
3
+ class DecreaseTripCountJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ vendor_route_metric = SpreeCmCommissioner::VendorRouteMetric.find_by(
6
+ vendor_id: options[:vendor_id],
7
+ origin_place_id: options[:origin_place_id],
8
+ destination_place_id: options[:destination_place_id],
9
+ route_type: options[:route_type]
10
+ )
11
+ return if vendor_route_metric.nil?
12
+
13
+ SpreeCmCommissioner::VendorRouteMetrics::DecreaseTripCount.call(vendor_route_metric: vendor_route_metric)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module SpreeCmCommissioner
2
+ module VendorRouteMetrics
3
+ class IncreaseTripCountJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ SpreeCmCommissioner::VendorRouteMetrics::IncreaseTripCount.call(
6
+ vendor_id: options[:vendor_id],
7
+ origin_place_id: options[:origin_place_id],
8
+ destination_place_id: options[:destination_place_id],
9
+ route_type: options[:route_type]
10
+ )
11
+ end
12
+ end
13
+ end
14
+ 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
@@ -0,0 +1,17 @@
1
+ # waiting_room_lobby_path_publisher:
2
+ # cron: "0 0 * * * Asia/Phnom_Penh" # Once per day at local midnight (date rollover)
3
+ # class: "SpreeCmCommissioner::WaitingRoom::PublishLobbyPathJob"
4
+ module SpreeCmCommissioner
5
+ module WaitingRoom
6
+ class PublishLobbyPathJob < ApplicationJob
7
+ queue_as :waiting_room
8
+
9
+ def perform
10
+ return if ENV['WAITING_ROOM_DISABLED'] == 'yes'
11
+
12
+ # call! so a publish failure raises (Sidekiq retries/alerts) instead of being silently swallowed.
13
+ SpreeCmCommissioner::WaitingRoom::PublishLobbyPath.call!
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,84 @@
1
+ require 'google/cloud/firestore'
2
+
3
+ module SpreeCmCommissioner
4
+ module WaitingRoom
5
+ class StampQueuePositionsJob < ApplicationJob
6
+ queue_as :waiting_room
7
+
8
+ STAMP_LIMIT = (ENV['WAITING_ROOM_POSITION_STAMP_LIMIT'] || 1000).to_i
9
+
10
+ def perform
11
+ paths = stamp_paths
12
+ avg_queue_to_enter = lobby_data[:avg_queue_to_enter_seconds].to_i
13
+ total = paths.sum { |p| waiting_count(p) }
14
+ return if total.zero?
15
+
16
+ stamped_at = Time.zone.now
17
+ global = 0
18
+
19
+ paths.each do |path|
20
+ remaining = STAMP_LIMIT - global
21
+ break if remaining <= 0
22
+
23
+ docs = waiting_query(path).order('queued_at').limit(remaining).get.to_a
24
+ docs.each_slice(500) do |slice|
25
+ firestore.batch do |b|
26
+ slice.each do |doc|
27
+ global += 1
28
+ data = {
29
+ position: global,
30
+ queue_total: total,
31
+ estimated_wait_seconds: avg_queue_to_enter,
32
+ status_updated_at: stamped_at
33
+ }
34
+ data[:position_set_at] = stamped_at unless doc.data[:position_set_at]
35
+ b.update(doc.ref, data)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def stamp_paths
45
+ [previous_records_path, records_path]
46
+ end
47
+
48
+ def records_path
49
+ default_records_path(Time.zone.now.strftime('%Y-%m-%d'))
50
+ end
51
+
52
+ def previous_records_path
53
+ default_records_path(1.day.ago.strftime('%Y-%m-%d'))
54
+ end
55
+
56
+ def default_records_path(date)
57
+ "waiting_guests/#{date}/records"
58
+ end
59
+
60
+ def waiting_count(path)
61
+ waiting_query(path).aggregate_query.add_count.get.first.get.to_i
62
+ end
63
+
64
+ def waiting_query(path)
65
+ firestore.col(path).where('allow_to_enter_room_at', '==', nil)
66
+ end
67
+
68
+ def lobby_data
69
+ @lobby_data ||= firestore.col('waiting_rooms').doc('lobby').get.data || {}
70
+ end
71
+
72
+ def firestore
73
+ @firestore ||= Google::Cloud::Firestore.new(
74
+ project_id: service_account[:project_id],
75
+ credentials: service_account
76
+ )
77
+ end
78
+
79
+ def service_account
80
+ @service_account ||= Rails.application.credentials.cloud_firestore_service_account
81
+ end
82
+ end
83
+ end
84
+ end