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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0774f6d0c4fad7ae396f14326fd6d0965164b74d89f355c3966bc8d1fd9563f9
4
- data.tar.gz: 002d84c9f1273b54f01d5a25bdecc5f3b475a12e07b32d8e1846c19107fe776f
3
+ metadata.gz: f46f710655ba8b15a2ffa7413d62a1bfa7d82ec3f433e340730876d0a5d710fb
4
+ data.tar.gz: 21b459ea09087666eda42a19d4d0fcc2e048e29330a26d39d06145e7d936b1e9
5
5
  SHA512:
6
- metadata.gz: 260d54db1f9dd63dbfef02edf2944d02e2c582972b8903815462e438be9cbfb6f5561a7b4a4b64c2cda4af18908c8c7a91958213c06764d1f7ee4d8e2c16dec3
7
- data.tar.gz: 003d6ecd547b0215851266f06e823f2f7c5e7a8da35f5b4a8051784855a4d095d088b79f700dcadca8820d561d7a9989a3ee0d9eb7940943c3c5777cc7a4742c
6
+ metadata.gz: 3e78ff73c329e6afa9e95abd02e4aeeb9b0b3837eb1dfafbaaaf14bbcc54215e19f527aaee17326604fb2166d8ac93f519f72525121667343587d824b27e2cfa
7
+ data.tar.gz: 812c79c609318c28596a1826afc0bbc2ce9c53b40a8d04927da225d7b074d3a0f9dd0b63bd3c1bd7679194a86926de4d27245cb3db479be9aaaa92582ebaa822
@@ -164,7 +164,8 @@ jobs:
164
164
  env:
165
165
  DATABASE_URL: postgres://myuser:mypassword@localhost:5432/test_db
166
166
  run: |
167
- bundle exec brakeman --no-exit-on-warn
167
+ # Using --no-threads to bypass a known Ruby 3.2.0 GC segmentation fault in CI.
168
+ bundle exec brakeman --no-threads --no-exit-on-warn
168
169
 
169
170
  test:
170
171
  needs: [setup]
data/.gitignore CHANGED
@@ -34,3 +34,9 @@ vendor/bundle/
34
34
  # Cursor
35
35
  .cursor
36
36
  dump.rdb
37
+ # Added by code-review-graph
38
+ .code-review-graph/
39
+ .cursorrules
40
+ .claude
41
+ CLAUDE.md
42
+ AGENTS.md
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.8.3)
37
+ spree_cm_commissioner (2.8.4.pre.pre2)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -16,6 +16,7 @@ module SpreeCmCommissioner
16
16
  GuestCardClassesController
17
17
  SeatLayoutController
18
18
  CmsPagesController
19
+ AdvertisementsController
19
20
  ].freeze
20
21
 
21
22
  # Priority 2: Semi-Static (1 hour)
@@ -25,6 +26,7 @@ module SpreeCmCommissioner
25
26
  HomepageDataController
26
27
  PopularRoutesController
27
28
  RoutePlacesController
29
+ ShowsController
28
30
  ].freeze
29
31
 
30
32
  # Priority 3: Moderate Freshness (30 minutes)
@@ -55,7 +55,7 @@ module Spree
55
55
  if result.success?
56
56
  flash[:success] = "Guest #{@guest.full_name} has been checked in."
57
57
  else
58
- flash[:error] = result.message.to_s.titleize
58
+ flash[:error] = result.error.value.to_s.titleize
59
59
  end
60
60
 
61
61
  redirect_to collection_url
@@ -51,7 +51,10 @@ module Spree
51
51
  def permitted_resource_params
52
52
  segment_value = helpers.calculate_segment_value(params[:spree_cm_commissioner_homepage_section])
53
53
 
54
- params.require(:spree_cm_commissioner_homepage_section).permit(:title, :description, :active, :tenant_id).merge(segment: segment_value)
54
+ params
55
+ .require(:spree_cm_commissioner_homepage_section)
56
+ .permit(:title, :description, :active, :tenant_id, :preview)
57
+ .merge(segment: segment_value)
55
58
  end
56
59
 
57
60
  def load_tenants
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ module Admin
3
+ class OrderTicketTransfersController < Spree::Admin::ResourceController
4
+ include Spree::Admin::OrderConcern
5
+
6
+ helper SpreeCmCommissioner::Admin::TicketTransfersHelper
7
+
8
+ before_action :load_order
9
+
10
+ def show
11
+ @ticket_transfer = model_class.includes(:order, from_guest: :event).find(params[:id])
12
+ end
13
+
14
+ private
15
+
16
+ def model_class
17
+ SpreeCmCommissioner::TicketTransfer
18
+ end
19
+
20
+ def object_name
21
+ 'spree_cm_commissioner_ticket_transfer'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -92,7 +92,7 @@ module Spree
92
92
  .call
93
93
  .index_by(&:inventory_item_id)
94
94
  @reserved_stocks = Spree::LineItem
95
- .complete
95
+ .affecting_inventory
96
96
  .where(variant_id: @variants.pluck(:id))
97
97
  .group('spree_line_items.variant_id')
98
98
  .sum(:quantity)
@@ -4,7 +4,10 @@ module Spree
4
4
  class WaitingRoomController < Spree::Admin::BaseController
5
5
  def show
6
6
  @fetcher = SpreeCmCommissioner::WaitingRoomSystemMetadataFetcher.new
7
+ @lobby_fetcher = SpreeCmCommissioner::WaitingRoomLobbyMetadataFetcher.new
8
+
7
9
  @fetcher.load_document_data
10
+ @lobby_fetcher.load_document_data
8
11
 
9
12
  @active_sesions_count = SpreeCmCommissioner::WaitingRoomSession.active.count
10
13
  end
@@ -29,6 +32,18 @@ module Spree
29
32
 
30
33
  redirect_back fallback_location: admin_system_waiting_room_path
31
34
  end
35
+
36
+ def publish_lobby_path
37
+ result = SpreeCmCommissioner::WaitingRoom::PublishLobbyPath.call
38
+
39
+ if result.success?
40
+ flash[:success] = "Published waiting guests records path: #{result.value[:records_path]}"
41
+ else
42
+ flash[:error] = result.error.to_s
43
+ end
44
+
45
+ redirect_back fallback_location: admin_system_waiting_room_path
46
+ end
32
47
  end
33
48
  end
34
49
  end
@@ -3,6 +3,7 @@ module Spree
3
3
  module TaxonsControllerDecorator
4
4
  def self.prepended(base)
5
5
  base.before_action :build_assets, only: %i[create update]
6
+ base.before_action :normalize_sti_taxon_params, only: %i[create update]
6
7
  end
7
8
 
8
9
  # override
@@ -27,6 +28,10 @@ module Spree
27
28
  remove_asset(@taxon.home_banner)
28
29
  end
29
30
 
31
+ def remove_ad_banner
32
+ remove_asset(@taxon.ad_banner)
33
+ end
34
+
30
35
  def remove_video_banner
31
36
  remove_asset(@taxon.video_banner)
32
37
  end
@@ -48,8 +53,27 @@ module Spree
48
53
  @taxon.build_app_banner(attachment: permitted_resource_params.delete(:app_banner)) if permitted_resource_params[:app_banner]
49
54
  @taxon.build_web_banner(attachment: permitted_resource_params.delete(:web_banner)) if permitted_resource_params[:web_banner]
50
55
  @taxon.build_home_banner(attachment: permitted_resource_params.delete(:home_banner)) if permitted_resource_params[:home_banner]
56
+ @taxon.build_ad_banner(attachment: permitted_resource_params.delete(:ad_banner)) if permitted_resource_params[:ad_banner]
51
57
  @taxon.build_video_banner(attachment: permitted_resource_params.delete(:video_banner)) if permitted_resource_params[:video_banner]
52
58
  end
59
+
60
+ # Some admin forms submit STI records under their class key
61
+ # (e.g. :spree_cm_commissioner_show) while Spree update reads :taxon.
62
+ # Merge both into :taxon so all edited fields are persisted.
63
+ def normalize_sti_taxon_params
64
+ return unless @taxon
65
+
66
+ sti_key = @taxon.class.model_name.param_key.to_sym
67
+ sti_params = params[sti_key]
68
+ return unless sti_params.is_a?(::ActionController::Parameters)
69
+
70
+ base_params = params[:taxon]
71
+ base_hash = base_params.is_a?(::ActionController::Parameters) ? base_params.to_unsafe_h : {}
72
+ sti_hash = sti_params.to_unsafe_h
73
+ merged_params = ::ActionController::Parameters.new(base_hash.merge(sti_hash))
74
+
75
+ params[:taxon] = merged_params
76
+ end
53
77
  end
54
78
  end
55
79
  end
@@ -0,0 +1,47 @@
1
+ module Spree
2
+ module Admin
3
+ class TicketTransferRulesController < Spree::Admin::ResourceController
4
+ def index
5
+ @search = model_class.ransack(params[:q])
6
+ @ticket_transfer_rules = @search.result
7
+ .order(ruleable_type: :asc, ruleable_id: :asc, currency: :asc)
8
+ .page(params[:page])
9
+ .per(25)
10
+ end
11
+
12
+ private
13
+
14
+ def model_class
15
+ SpreeCmCommissioner::TicketTransferRule
16
+ end
17
+
18
+ def object_name
19
+ 'spree_cm_commissioner_ticket_transfer_rule'
20
+ end
21
+
22
+ def collection_url
23
+ admin_ticket_transfer_rules_url
24
+ end
25
+
26
+ def location_after_save
27
+ edit_admin_ticket_transfer_rule_path(@object)
28
+ end
29
+
30
+ def permitted_resource_params
31
+ params.require(:spree_cm_commissioner_ticket_transfer_rule).permit(
32
+ :ruleable_type,
33
+ :ruleable_id,
34
+ :active,
35
+ :currency,
36
+ :fee_percentage,
37
+ :fee_flat_amount,
38
+ :cooldown_hours,
39
+ :max_transfers_per_ticket,
40
+ :event_cutoff_hours,
41
+ :min_price,
42
+ :max_price
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,61 @@
1
+ module Spree
2
+ module Admin
3
+ class TicketTransfersController < Spree::Admin::ResourceController
4
+ helper SpreeCmCommissioner::Admin::TicketTransfersHelper
5
+
6
+ def index
7
+ @search = model_class.includes(:from_user, :to_user, :from_guest).ransack(params[:q])
8
+ @ticket_transfers = @search.result.order(created_at: :desc).page(params[:page]).per(15)
9
+ end
10
+
11
+ def show
12
+ @ticket_transfer = model_class.includes(:order, from_guest: [:event, { line_item: :order }]).find(params[:id])
13
+ end
14
+
15
+ def state_changes
16
+ @ticket_transfer = model_class.find(params[:id])
17
+ @state_changes = @ticket_transfer.state_changes.includes(:user).order(created_at: :desc)
18
+ end
19
+
20
+ def cancel
21
+ @ticket_transfer = model_class.find(params[:id])
22
+ result = SpreeCmCommissioner::TicketTransfers::Cancel.call(
23
+ ticket_transfer: @ticket_transfer,
24
+ acting_user: try_spree_current_user
25
+ )
26
+
27
+ if result.success?
28
+ flash[:success] = 'Ticket transfer has been cancelled' # rubocop:disable Rails/I18nLocaleTexts
29
+ else
30
+ flash[:error] = result.error.value
31
+ end
32
+ redirect_to admin_ticket_transfer_path(@ticket_transfer)
33
+ end
34
+
35
+ def update_settlement_status
36
+ @ticket_transfer = model_class.find(params[:id])
37
+ new_status = params[:settlement_status]
38
+
39
+ unless @ticket_transfer.purchase? && @ticket_transfer.completed?
40
+ flash[:error] = 'Settlement status can only be updated for completed purchase transfers' # rubocop:disable Rails/I18nLocaleTexts
41
+ redirect_to admin_ticket_transfer_path(@ticket_transfer)
42
+ return
43
+ end
44
+
45
+ if @ticket_transfer.update(settlement_status: new_status)
46
+ @ticket_transfer.log_settlement_status_changes(user_id: try_spree_current_user&.id)
47
+ flash[:success] = "Settlement status updated to #{new_status}"
48
+ else
49
+ flash[:error] = @ticket_transfer.errors.full_messages.to_sentence.presence || 'Failed to update settlement status'
50
+ end
51
+ redirect_to admin_ticket_transfer_path(@ticket_transfer)
52
+ end
53
+
54
+ private
55
+
56
+ def model_class
57
+ SpreeCmCommissioner::TicketTransfer
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,32 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module Account
6
+ class RecipientsController < ::Spree::Api::V2::ResourceController
7
+ before_action :require_spree_current_user
8
+
9
+ def show
10
+ identifier = params[:q].to_s.strip
11
+ return render_error_payload('Missing q parameter', 400) if identifier.blank?
12
+
13
+ user = SpreeCmCommissioner::RecipientSearcherQuery.new(identifier, MultiTenant.current_tenant_id).call
14
+
15
+ if user
16
+ render_serialized_payload { serialize_resource(user) }
17
+ else
18
+ render_error_payload('User not found', 404)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def resource_serializer
25
+ SpreeCmCommissioner::V2::Storefront::RecipientSerializer
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -31,13 +31,15 @@ module Spree
31
31
  def create
32
32
  spree_authorize! :update, spree_current_order, order_token
33
33
 
34
- resource = scope.new(guest_params)
34
+ result = SpreeCmCommissioner::Cart::CreateGuest.call(
35
+ line_item: parent,
36
+ options: guest_params
37
+ )
35
38
 
36
- if resource.save
37
- resource.save_and_move_to_next_stage
38
- render_serialized_payload(201) { serialize_resource(resource) }
39
+ if result.success?
40
+ render_serialized_payload(201) { serialize_resource(result.value) }
39
41
  else
40
- render_error_payload(resource.errors, 422)
42
+ render_error_payload(result.value.errors, 422)
41
43
  end
42
44
  end
43
45
 
@@ -19,6 +19,7 @@ module Spree
19
19
  @collection ||= model_class.filter_by_segment(params[:homepage_id] || :general)
20
20
  .active
21
21
  .where(tenant_id: nil)
22
+ .where(preview: false)
22
23
  .order(position: :asc)
23
24
  end
24
25
  end
@@ -0,0 +1,48 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class PreviewProductsController < ::Spree::Api::V2::Storefront::ProductsController
6
+ before_action :require_spree_current_user
7
+
8
+ private
9
+
10
+ def scope(**args)
11
+ taxon_type = model_class.reflect_on_association(:taxons).klass.polymorphic_name
12
+
13
+ # IDs of products that have been explicitly assigned their own PreviewRole.
14
+ products_with_own_roles = SpreeCmCommissioner::PreviewRole
15
+ .where(previewable_type: model_class.polymorphic_name)
16
+ .select(:previewable_id)
17
+
18
+ # Base: all active preview products from the parent scope.
19
+ base = super(**args).rewhere(preview: true).where(status: :active)
20
+
21
+ # Path A: product has its own PreviewRole → user must hold that role directly.
22
+ # Taxon membership is irrelevant for these products.
23
+ via_product_role = base
24
+ .where(id: products_with_own_roles)
25
+ .where(id: spree_current_user.preview_roles
26
+ .where(previewable_type: model_class.polymorphic_name)
27
+ .select(:previewable_id)
28
+ )
29
+
30
+ # Path B: product has no own PreviewRole → inherit access from its taxons.
31
+ # Accessible if the user holds a preview role for any taxon the product belongs to.
32
+ via_taxon = base
33
+ .where.not(id: products_with_own_roles)
34
+ .joins(:taxons)
35
+ .where(spree_taxons: { id: spree_current_user.preview_roles
36
+ .where(previewable_type: taxon_type)
37
+ .select(:previewable_id)
38
+ }
39
+ )
40
+
41
+ # via_taxon uses joins(:taxons) which is incompatible with or — wrap as subquery.
42
+ via_product_role.or(base.where(id: via_taxon.select(:id)))
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class PreviewSectionsController < HomepageSectionsController
6
+ before_action :require_spree_current_user
7
+
8
+ # override
9
+ def collection
10
+ allowed_ids = spree_current_user.preview_roles.where(previewable_type: Spree::Taxon.polymorphic_name).pluck(:previewable_id)
11
+
12
+ section_ids = SpreeCmCommissioner::HomepageSectionRelatable
13
+ .where(relatable_type: 'Spree::Taxon', relatable_id: allowed_ids.presence || [])
14
+ .select(:homepage_section_id)
15
+
16
+ @collection ||= model_class.filter_by_segment(params[:homepage_id] || :general)
17
+ .active
18
+ .where(tenant_id: nil)
19
+ .where(preview: true)
20
+ .where(id: section_ids)
21
+ .order(position: :asc)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class PreviewTaxonsController < ::Spree::Api::V2::Storefront::TaxonsController
6
+ before_action :require_spree_current_user
7
+
8
+ private
9
+
10
+ def scope(**args)
11
+ allowed_ids = spree_current_user.preview_roles.where(previewable_type: model_class.polymorphic_name).select(:previewable_id)
12
+ super(**args).rewhere(preview: true).where(id: allowed_ids)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module ProductsControllerDecorator
6
+ def scope
7
+ super.where(preview: false)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Spree::Api::V2::Storefront::ProductsController.prepend(Spree::Api::V2::Storefront::ProductsControllerDecorator)
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module TaxonsControllerDecorator
6
+ def scope
7
+ super.where(preview: false)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Spree::Api::V2::Storefront::TaxonsController.prepend(Spree::Api::V2::Storefront::TaxonsControllerDecorator)
@@ -0,0 +1,32 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class TicketTransferAcceptancesController < ::Spree::Api::V2::ResourceController
6
+ before_action :require_spree_current_user
7
+
8
+ def create
9
+ ticket_transfer = SpreeCmCommissioner::TicketTransfer.find_by!(token: params[:ticket_transfer_id])
10
+
11
+ result = SpreeCmCommissioner::TicketTransfers::Accept.call(
12
+ ticket_transfer: ticket_transfer,
13
+ buyer: spree_current_user
14
+ )
15
+
16
+ if result.success?
17
+ render_serialized_payload(201) { serialize_resource(result.value[:ticket_transfer]) }
18
+ else
19
+ render_error_payload(result.error)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def resource_serializer
26
+ SpreeCmCommissioner::V2::Storefront::TicketTransferSerializer
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,117 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class TicketTransfersController < ::Spree::Api::V2::ResourceController
6
+ before_action :require_spree_current_user
7
+
8
+ def index
9
+ render_serialized_payload do
10
+ serialize_collection(paginated_collection)
11
+ end
12
+ end
13
+
14
+ def show
15
+ ticket_transfer = model_class.includes(:order, from_guest: [:event, { line_item: :product }]).find_by!(token: params[:id])
16
+ return render_error_payload(I18n.t('ticket_transfer.unauthorized_view'), 403) unless authorized_to_view?(ticket_transfer)
17
+
18
+ render_serialized_payload { serialize_resource(ticket_transfer) }
19
+ end
20
+
21
+ def preview_rule
22
+ result = SpreeCmCommissioner::TicketTransfers::PreviewRule.call(
23
+ from_guest_id: params[:guest_id],
24
+ currency: params[:currency] || 'USD'
25
+ )
26
+
27
+ if result.success?
28
+ render json: { data: result.value }
29
+ else
30
+ render_error_payload(result.error)
31
+ end
32
+ end
33
+
34
+ def create
35
+ result = SpreeCmCommissioner::TicketTransfers::Initiate.call(
36
+ from_user_id: spree_current_user.id,
37
+ to_user_id: ticket_transfer_params[:to_user_id],
38
+ from_guest_id: ticket_transfer_params[:guest_id],
39
+ price: ticket_transfer_params[:price] || 0,
40
+ currency: ticket_transfer_params[:currency] || 'USD',
41
+ expires_at: ticket_transfer_params[:expires_at],
42
+ transfer_type: ticket_transfer_params[:transfer_type] || 'purchase'
43
+ )
44
+
45
+ if result.success?
46
+ render_serialized_payload(201) { serialize_resource(result.value[:ticket_transfer]) }
47
+ else
48
+ render_error_payload(result.error)
49
+ end
50
+ end
51
+
52
+ def cancel
53
+ ticket_transfer = model_class.find_by!(token: params[:id])
54
+
55
+ result = SpreeCmCommissioner::TicketTransfers::Cancel.call(
56
+ ticket_transfer: ticket_transfer,
57
+ acting_user: spree_current_user
58
+ )
59
+
60
+ if result.success?
61
+ render_serialized_payload { serialize_resource(result.value[:ticket_transfer]) }
62
+ else
63
+ render_error_payload(result.error)
64
+ end
65
+ end
66
+
67
+ def reject
68
+ ticket_transfer = model_class.find_by!(token: params[:id])
69
+
70
+ result = SpreeCmCommissioner::TicketTransfers::Reject.call(
71
+ ticket_transfer: ticket_transfer,
72
+ acting_user: spree_current_user
73
+ )
74
+
75
+ if result.success?
76
+ render_serialized_payload { serialize_resource(result.value[:ticket_transfer]) }
77
+ else
78
+ render_error_payload(result.error)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def model_class
85
+ SpreeCmCommissioner::TicketTransfer
86
+ end
87
+
88
+ def collection
89
+ SpreeCmCommissioner::TicketTransfer.includes(:order,
90
+ from_guest: [:event, { line_item: :product }]
91
+ ).where(from_user_id: spree_current_user.id)
92
+ end
93
+
94
+ def resource_serializer
95
+ SpreeCmCommissioner::V2::Storefront::TicketTransferSerializer
96
+ end
97
+
98
+ def collection_serializer
99
+ SpreeCmCommissioner::V2::Storefront::TicketTransferSerializer
100
+ end
101
+
102
+ def ticket_transfer_params
103
+ params.require(:ticket_transfer).permit(:to_user_id, :guest_id, :price, :currency, :expires_at, :transfer_type)
104
+ end
105
+
106
+ # When sending to a specific user, only sender and that buyer can access.
107
+ def authorized_to_view?(ticket_transfer)
108
+ return true if ticket_transfer.to_user_id.blank?
109
+
110
+ current_user_id = spree_current_user&.id
111
+ ticket_transfer.from_user_id == current_user_id || ticket_transfer.to_user_id == current_user_id
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -11,6 +11,7 @@ module Spree
11
11
  # - inbound_date: Date of inbound transit (optional)
12
12
  # - outbound_legs: Array of outbound leg details (required)
13
13
  # - inbound_legs: Array of inbound leg details (optional)
14
+ # - include_return: Whether to add the trip's "return included" add-on as a line item (optional)
14
15
  def create
15
16
  @outbound_legs = params[:outbound_legs].is_a?(Array) && params[:outbound_legs].any? ? build_legs(:outbound, params[:outbound_legs]) : []
16
17
  @inbound_legs = params[:inbound_legs].is_a?(Array) && params[:inbound_legs].any? ? build_legs(:inbound, params[:inbound_legs]) : []
@@ -20,7 +21,8 @@ module Spree
20
21
  inbound_date: params[:inbound_date]&.to_date,
21
22
  outbound_legs: @outbound_legs,
22
23
  inbound_legs: @inbound_legs,
23
- user: spree_current_user
24
+ user: spree_current_user,
25
+ include_return: ActiveModel::Type::Boolean.new.cast(params[:include_return])
24
26
  )
25
27
 
26
28
  if result.success?