spree_cm_commissioner 2.13.2 → 2.13.3.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 (278) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +1 -0
  3. data/Gemfile.lock +1 -1
  4. data/app/controllers/concerns/spree_cm_commissioner/operator/event_authorization.rb +19 -0
  5. data/app/controllers/spree/admin/homepage_section_controller.rb +32 -8
  6. data/app/controllers/spree/admin/integrations_controller.rb +5 -7
  7. data/app/controllers/spree/admin/inventory_items_controller.rb +12 -0
  8. data/app/controllers/spree/admin/notification_users_controller.rb +39 -7
  9. data/app/controllers/spree/admin/service_fee_rules_controller.rb +83 -0
  10. data/app/controllers/spree/admin/stock_managements_controller.rb +5 -5
  11. data/app/controllers/spree/api/switch_cm/base_controller.rb +98 -0
  12. data/app/controllers/spree/api/switch_cm/ota/avail_notifs_controller.rb +17 -0
  13. data/app/controllers/spree/api/switch_cm/ota/base_controller.rb +27 -0
  14. data/app/controllers/spree/api/switch_cm/ota/product_notifs_controller.rb +44 -0
  15. data/app/controllers/spree/api/switch_cm/ota/pushes_controller.rb +46 -0
  16. data/app/controllers/spree/api/switch_cm/ota/rate_amount_notifs_controller.rb +17 -0
  17. data/app/controllers/spree/api/switch_cm/ota/res_modify_notifs_controller.rb +31 -0
  18. data/app/controllers/spree/api/switch_cm/ota/res_notifs_controller.rb +29 -0
  19. data/app/controllers/spree/api/switch_cm/ota/reservation_streams_controller.rb +83 -0
  20. data/app/controllers/spree/api/switch_cm/tokens_controller.rb +68 -0
  21. data/app/controllers/spree/api/switch_cm/xml/availabilities_controller.rb +39 -0
  22. data/app/controllers/spree/api/switch_cm/xml/base_controller.rb +37 -0
  23. data/app/controllers/spree/api/switch_cm/xml/reservation_summaries_controller.rb +28 -0
  24. data/app/controllers/spree/api/switch_cm/xml/reservations_controller.rb +56 -0
  25. data/app/controllers/spree/api/switch_cm/xml/room_rate_availabilities_controller.rb +56 -0
  26. data/app/controllers/spree/api/switch_cm/xml/room_rates_controller.rb +32 -0
  27. data/app/controllers/spree/api/v2/operator/check_ins_controller.rb +7 -6
  28. data/app/controllers/spree/api/v2/operator/event_charts_controller.rb +8 -10
  29. data/app/controllers/spree/api/v2/operator/event_ticket_aggregators_controller.rb +14 -4
  30. data/app/controllers/spree/api/v2/operator/pie_chart_event_aggregators_controller.rb +15 -1
  31. data/app/controllers/spree/api/v2/platform/homepage_section_relatable_options_controller.rb +28 -0
  32. data/app/controllers/spree/api/v2/storefront/cross_app_handoff_tokens_controller.rb +41 -0
  33. data/app/controllers/spree/api/v2/storefront/homepage_sections_controller.rb +4 -2
  34. data/app/controllers/spree/api/v2/storefront/transit/item_availabilities_controller.rb +66 -0
  35. data/app/controllers/spree/api/v2/tenant/checkout_controller.rb +11 -0
  36. data/app/controllers/spree/api/v2/tenant/vendor_photos_controller.rb +25 -0
  37. data/app/controllers/spree_cm_commissioner/admin/orders_controller_decorator.rb +9 -1
  38. data/app/controllers/spree_cm_commissioner/api/v2/storefront/checkout_controller_decorator.rb +15 -0
  39. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +6 -2
  40. data/app/helpers/spree/admin/customer_notifications_helper.rb +57 -0
  41. data/app/helpers/spree_cm_commissioner/admin/homepage_segment_helper.rb +45 -23
  42. data/app/interactors/spree_cm_commissioner/customer_notification_sender.rb +18 -9
  43. data/app/interactors/spree_cm_commissioner/user_cross_app_handoff_authenticator.rb +17 -0
  44. data/app/jobs/spree_cm_commissioner/audit_event_job.rb +29 -0
  45. data/app/models/concerns/spree_cm_commissioner/audit_wrapper.rb +139 -0
  46. data/app/models/concerns/spree_cm_commissioner/homepage_section_bitwise.rb +37 -1
  47. data/app/models/concerns/spree_cm_commissioner/integrations/inventory_sync_cachable.rb +9 -0
  48. data/app/models/concerns/spree_cm_commissioner/line_item_open_dated_trippable.rb +2 -2
  49. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +10 -0
  50. data/app/models/concerns/spree_cm_commissioner/phone_number_sanitizer.rb +5 -1
  51. data/app/models/concerns/spree_cm_commissioner/user_notification.rb +3 -1
  52. data/app/models/concerns/spree_cm_commissioner/variant_metadata.rb +24 -0
  53. data/app/models/concerns/spree_cm_commissioner/vehicle_kind.rb +1 -1
  54. data/app/models/spree_cm_commissioner/adjustable/adjuster/service_fee.rb +31 -0
  55. data/app/models/spree_cm_commissioner/adjustment_decorator.rb +1 -0
  56. data/app/models/spree_cm_commissioner/audit_event.rb +11 -0
  57. data/app/models/spree_cm_commissioner/calculators/service_fee_flat_amount.rb +17 -0
  58. data/app/models/spree_cm_commissioner/calculators/service_fee_percent.rb +20 -0
  59. data/app/models/spree_cm_commissioner/currency_rate.rb +15 -0
  60. data/app/models/spree_cm_commissioner/customer_notification.rb +15 -1
  61. data/app/models/spree_cm_commissioner/homepage_section.rb +25 -0
  62. data/app/models/spree_cm_commissioner/integration.rb +46 -0
  63. data/app/models/spree_cm_commissioner/integrations/book_me_bus_v1.rb +64 -0
  64. data/app/models/spree_cm_commissioner/integrations/larryta.rb +81 -0
  65. data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +4 -10
  66. data/app/models/spree_cm_commissioner/integrations/switch_cm.rb +86 -0
  67. data/app/models/spree_cm_commissioner/integrations/vireak_buntham.rb +82 -0
  68. data/app/models/spree_cm_commissioner/inventory_item.rb +18 -0
  69. data/app/models/spree_cm_commissioner/line_item_decorator.rb +2 -0
  70. data/app/models/spree_cm_commissioner/order_decorator.rb +13 -1
  71. data/app/models/spree_cm_commissioner/order_updater_decorator.rb +31 -0
  72. data/app/models/spree_cm_commissioner/place.rb +2 -0
  73. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +2 -1
  74. data/app/models/spree_cm_commissioner/route.rb +1 -0
  75. data/app/models/spree_cm_commissioner/seat_layout.rb +1 -0
  76. data/app/models/spree_cm_commissioner/service_fee_rule.rb +183 -0
  77. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +1 -1
  78. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +1 -1
  79. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +4 -0
  80. data/app/models/spree_cm_commissioner/trip.rb +2 -1
  81. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -0
  82. data/app/models/spree_cm_commissioner/user_decorator.rb +4 -0
  83. data/app/models/spree_cm_commissioner/variant_decorator.rb +5 -0
  84. data/app/models/spree_cm_commissioner/vehicle.rb +3 -0
  85. data/app/models/spree_cm_commissioner/vehicle_type.rb +1 -0
  86. data/app/models/spree_cm_commissioner/vendor_decorator.rb +17 -0
  87. data/app/models/spree_cm_commissioner/vendor_place.rb +2 -0
  88. data/app/overrides/spree/admin/orders/index/line_items_row.html.erb.deface +14 -0
  89. data/app/overrides/spree/admin/orders/index/row_actions.html.erb.deface +17 -0
  90. data/app/overrides/spree/admin/shared/sub_menu/_configuration/menu_items.html.erb.deface +5 -0
  91. data/app/queries/spree_cm_commissioner/users_by_event_fetcher_query.rb +6 -7
  92. data/app/serializables/spree_cm_commissioner/item_availability_sync.rb +10 -0
  93. data/app/serializers/spree/api/v2/platform/service_fee_rule_serializer.rb +17 -0
  94. data/app/serializers/spree/v2/storefront/homepage_section_serializer.rb +1 -1
  95. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +8 -2
  96. data/app/serializers/spree/v2/tenant/cart_serializer.rb +2 -1
  97. data/app/serializers/spree/v2/tenant/line_item_serializer.rb +7 -1
  98. data/app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb +2 -1
  99. data/app/serializers/spree_cm_commissioner/v2/storefront/cart_serializer_decorator.rb +2 -1
  100. data/app/serializers/spree_cm_commissioner/v2/storefront/cross_app_handoff_token_serializer.rb +9 -0
  101. data/app/serializers/spree_cm_commissioner/v2/storefront/item_availability_sync_serializer.rb +10 -0
  102. data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +1 -1
  103. data/app/services/spree_cm_commissioner/audit_logger.rb +59 -0
  104. data/app/services/spree_cm_commissioner/checkout/advance_to_payment.rb +27 -0
  105. data/app/services/spree_cm_commissioner/cross_app_handoff_tokens/decode.rb +42 -0
  106. data/app/services/spree_cm_commissioner/cross_app_handoff_tokens/encode.rb +47 -0
  107. data/app/services/spree_cm_commissioner/currency_converter/create.rb +27 -0
  108. data/app/services/spree_cm_commissioner/currency_rates/manage.rb +99 -0
  109. data/app/services/spree_cm_commissioner/draft_orders/create.rb +1 -0
  110. data/app/services/spree_cm_commissioner/google_maps_geocoder.rb +41 -0
  111. data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +1 -2
  112. data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +11 -0
  113. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/client.rb +230 -0
  114. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/connection.rb +147 -0
  115. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_item_status.rb +52 -0
  116. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_items.rb +91 -0
  117. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/unstock_inventory.rb +188 -0
  118. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_inventory.rb +79 -0
  119. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_item_availability.rb +354 -0
  120. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_locations.rb +103 -0
  121. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_nationality_pricing.rb +93 -0
  122. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_product.rb +70 -0
  123. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_route_stops.rb +121 -0
  124. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_routes.rb +128 -0
  125. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_seat_layout.rb +412 -0
  126. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_service_calendars.rb +59 -0
  127. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb +224 -0
  128. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trips.rb +769 -0
  129. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_variant.rb +69 -0
  130. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb +92 -0
  131. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/time_parser.rb +19 -0
  132. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/base.rb +40 -0
  133. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/inventory_item_status.rb +148 -0
  134. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/location.rb +55 -0
  135. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/reservation_cart.rb +85 -0
  136. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat.rb +128 -0
  137. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat_layout.rb +386 -0
  138. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/stop.rb +84 -0
  139. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip.rb +237 -0
  140. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/wallet_payment.rb +48 -0
  141. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_manager.rb +22 -0
  142. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/full_sync_strategy.rb +116 -0
  143. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/incremental_sync_strategy.rb +63 -0
  144. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/client.rb +314 -0
  145. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/connection.rb +74 -0
  146. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_item_status.rb +53 -0
  147. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_items.rb +106 -0
  148. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/hold_seats.rb +143 -0
  149. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/release_seats.rb +90 -0
  150. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/unstock_inventory.rb +143 -0
  151. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_item_availability.rb +325 -0
  152. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_product.rb +72 -0
  153. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_routes.rb +142 -0
  154. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_seat_layout.rb +255 -0
  155. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_stock_item.rb +55 -0
  156. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trip_stop.rb +171 -0
  157. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trips.rb +397 -0
  158. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_variant.rb +63 -0
  159. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_vehicle_type.rb +79 -0
  160. data/app/services/spree_cm_commissioner/integrations/larryta/resources/base.rb +39 -0
  161. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking.rb +113 -0
  162. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_session.rb +43 -0
  163. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_transaction.rb +236 -0
  164. data/app/services/spree_cm_commissioner/integrations/larryta/resources/direction.rb +71 -0
  165. data/app/services/spree_cm_commissioner/integrations/larryta/resources/passenger.rb +48 -0
  166. data/app/services/spree_cm_commissioner/integrations/larryta/resources/schedule.rb +150 -0
  167. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat.rb +31 -0
  168. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_availability.rb +49 -0
  169. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_layout.rb +211 -0
  170. data/app/services/spree_cm_commissioner/integrations/larryta/sync_manager.rb +17 -0
  171. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/full_sync_strategy.rb +79 -0
  172. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/incremental_sync_strategy.rb +58 -0
  173. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +14 -15
  174. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +7 -4
  175. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +6 -3
  176. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +0 -3
  177. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +1 -3
  178. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +1 -3
  179. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +1 -2
  180. data/app/services/spree_cm_commissioner/integrations/switch_cm/accept_push.rb +101 -0
  181. data/app/services/spree_cm_commissioner/integrations/switch_cm/accommodation_lock.rb +41 -0
  182. data/app/services/spree_cm_commissioner/integrations/switch_cm/acknowledge_reservations.rb +43 -0
  183. data/app/services/spree_cm_commissioner/integrations/switch_cm/apply_ari.rb +119 -0
  184. data/app/services/spree_cm_commissioner/integrations/switch_cm/ari_update.rb +24 -0
  185. data/app/services/spree_cm_commissioner/integrations/switch_cm/availability_readback.rb +86 -0
  186. data/app/services/spree_cm_commissioner/integrations/switch_cm/mark_seen.rb +53 -0
  187. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/avail_notif_parser.rb +60 -0
  188. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/base_parser.rb +60 -0
  189. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/push_response.rb +38 -0
  190. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/rate_amount_notif_parser.rb +46 -0
  191. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/reservation_presenter.rb +99 -0
  192. data/app/services/spree_cm_commissioner/integrations/switch_cm/ota/response_builder.rb +52 -0
  193. data/app/services/spree_cm_commissioner/integrations/switch_cm/pushed_nights.rb +79 -0
  194. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservation.rb +118 -0
  195. data/app/services/spree_cm_commissioner/integrations/switch_cm/reservation_feed.rb +143 -0
  196. data/app/services/spree_cm_commissioner/integrations/switch_cm/room_catalog.rb +56 -0
  197. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_manager.rb +16 -0
  198. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/full_sync_strategy.rb +39 -0
  199. data/app/services/spree_cm_commissioner/integrations/switch_cm/sync_strategies/incremental_sync_strategy.rb +51 -0
  200. data/app/services/spree_cm_commissioner/integrations/switch_cm/unseen_room_nights.rb +39 -0
  201. data/app/services/spree_cm_commissioner/integrations/switch_cm/validate_ari_updates.rb +136 -0
  202. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/availability_parser.rb +103 -0
  203. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/push_response.rb +45 -0
  204. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/reservation_presenter.rb +74 -0
  205. data/app/services/spree_cm_commissioner/integrations/switch_cm/xml/response_builder.rb +19 -0
  206. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/client.rb +170 -0
  207. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/connection.rb +59 -0
  208. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_item_status.rb +48 -0
  209. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_items.rb +92 -0
  210. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/unstock_inventory.rb +230 -0
  211. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_item_availability.rb +270 -0
  212. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_locations.rb +96 -0
  213. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_nationality_pricing.rb +76 -0
  214. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_product.rb +69 -0
  215. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_routes.rb +110 -0
  216. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_seat_layout.rb +207 -0
  217. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_stock_item.rb +43 -0
  218. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trip_stop.rb +192 -0
  219. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trips.rb +377 -0
  220. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_variant.rb +56 -0
  221. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_vehicle_type.rb +51 -0
  222. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/base.rb +32 -0
  223. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking.rb +48 -0
  224. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking_transaction.rb +46 -0
  225. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/cell.rb +53 -0
  226. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/confirm_booking_request.rb +43 -0
  227. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/location.rb +37 -0
  228. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/schedule.rb +138 -0
  229. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat.rb +48 -0
  230. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat_layout.rb +79 -0
  231. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_manager.rb +17 -0
  232. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/full_sync_strategy.rb +87 -0
  233. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/incremental_sync_strategy.rb +53 -0
  234. data/app/services/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_by_variant.rb +4 -0
  235. data/app/services/spree_cm_commissioner/inventory_items/generate_permanent_item.rb +48 -0
  236. data/app/services/spree_cm_commissioner/inventory_items/reset.rb +4 -0
  237. data/app/services/spree_cm_commissioner/order_holds/hold.rb +17 -0
  238. data/app/services/spree_cm_commissioner/order_holds/release.rb +17 -0
  239. data/app/services/spree_cm_commissioner/place_resolver.rb +95 -0
  240. data/app/services/spree_cm_commissioner/transit_order/create.rb +1 -0
  241. data/app/services/spree_cm_commissioner/user_authenticator.rb +20 -12
  242. data/app/views/spree/admin/customer_notifications/_form.html.erb +9 -9
  243. data/app/views/spree/admin/customer_notifications/index.html.erb +36 -19
  244. data/app/views/spree/admin/homepage_section/_form.html.erb +19 -2
  245. data/app/views/spree/admin/homepage_section/_tabs.html.erb +7 -7
  246. data/app/views/spree/admin/homepage_section/index.html.erb +73 -6
  247. data/app/views/spree/admin/integrations/_book_me_bus_v1_fields.html.erb +47 -0
  248. data/app/views/spree/admin/integrations/_form.html.erb +16 -0
  249. data/app/views/spree/admin/integrations/_larryta_fields.html.erb +58 -0
  250. data/app/views/spree/admin/integrations/_switch_cm_fields.html.erb +53 -0
  251. data/app/views/spree/admin/integrations/_vireak_buntham_fields.html.erb +75 -0
  252. data/app/views/spree/admin/integrations/index.html.erb +1 -1
  253. data/app/views/spree/admin/notifications/index.html.erb +2 -1
  254. data/app/views/spree/admin/orders/line_items.html.erb +53 -0
  255. data/app/views/spree/admin/service_fee_rules/_form.html.erb +119 -0
  256. data/app/views/spree/admin/service_fee_rules/edit.html.erb +11 -0
  257. data/app/views/spree/admin/service_fee_rules/index.html.erb +91 -0
  258. data/app/views/spree/admin/service_fee_rules/new.html.erb +11 -0
  259. data/app/views/spree/admin/shared/_send_all_form.html.erb +1 -0
  260. data/app/views/spree/admin/shared/_send_test_form.html.erb +3 -3
  261. data/app/views/spree/admin/stock_managements/index.html.erb +14 -0
  262. data/config/locales/en.yml +36 -0
  263. data/config/locales/km.yml +32 -0
  264. data/config/routes.rb +34 -0
  265. data/db/migrate/20260327143200_create_cm_audit_events.rb +21 -0
  266. data/db/migrate/20260410045815_create_cm_currency_rates.rb +23 -0
  267. data/db/migrate/20260411102139_add_metadata_to_cm_inventory_items.rb +6 -0
  268. data/db/migrate/20260914000000_add_mini_app_to_homepage_sections.rb +5 -0
  269. data/db/migrate/20260917085551_create_cm_service_fee_rules.rb +16 -0
  270. data/db/migrate/20260917085552_add_service_fee_total_to_spree_line_items_and_orders.rb +6 -0
  271. data/db/migrate/20260928000000_add_oauth_application_id_to_cm_integrations.rb +5 -0
  272. data/db/migrate/20260928000001_add_closed_to_cm_inventory_items.rb +5 -0
  273. data/lib/spree_cm_commissioner/cached_inventory_item.rb +7 -1
  274. data/lib/spree_cm_commissioner/engine.rb +3 -0
  275. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_factory.rb +7 -0
  276. data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +17 -0
  277. data/lib/spree_cm_commissioner/version.rb +1 -1
  278. metadata +187 -4
@@ -0,0 +1,83 @@
1
+ module Spree::Api::SwitchCm
2
+ module Ota
3
+ # Booking.com's two OTA reservation streams share one shape; subclasses name the stream.
4
+ class ReservationStreamsController < BaseController
5
+ # GET /api/switch_cm/hotels/ota/OTA_HotelResNotif (ResNotifsController): new reservations
6
+ # GET /api/switch_cm/hotels/ota/OTA_HotelResModifyNotif (ResModifyNotifsController): cancellations
7
+ # ?hotel_ids=&id=&last_change=&limit= returns what hasn't been acknowledged; `id` or
8
+ # `last_change` re-reads regardless.
9
+ def index
10
+ integrations, unknown_hotel_ids = requested_integrations(params[:hotel_ids])
11
+ return render_fault(:forbidden, 'Not authorized for these hotels') if integrations.empty?
12
+
13
+ reservations = SpreeCmCommissioner::Integrations::SwitchCm::ReservationFeed.new(integrations).public_send(
14
+ feed_method,
15
+ id: params[:id],
16
+ last_change: SpreeCmCommissioner::Integrations::SwitchCm::ReservationFeed.parse_last_change(params[:last_change]),
17
+ limit: params[:limit]
18
+ )
19
+
20
+ document = SpreeCmCommissioner::Integrations::SwitchCm::Ota::ResponseBuilder.notification(
21
+ notification_root, ruid: ruid, warnings: hotel_warnings(unknown_hotel_ids)
22
+ ) do |xml|
23
+ xml.send(container) do
24
+ reservations.each do |reservation|
25
+ present(SpreeCmCommissioner::Integrations::SwitchCm::Ota::ReservationPresenter.new(reservation), xml)
26
+ end
27
+ end
28
+ end
29
+ render xml: document
30
+ end
31
+
32
+ # POST /api/switch_cm/hotels/ota/OTA_HotelResNotif (ResNotifsController)
33
+ # POST /api/switch_cm/hotels/ota/OTA_HotelResModifyNotif (ResModifyNotifsController)
34
+ # Acknowledges by reservation id: <...RS><Success/><...><HotelReservationID ResID_Value="..."/></...></...RS>
35
+ def acknowledge
36
+ # Booking.com's contract: <Success/> takes the reservations off the queue, while <Errors>
37
+ # (code 193) means the channel manager couldn't process them and they stay queued.
38
+ return render_success if processing_failed?
39
+
40
+ result = SpreeCmCommissioner::Integrations::SwitchCm::AcknowledgeReservations.call(
41
+ integrations: switch_cm_integrations.includes(:vendor).to_a,
42
+ reservation_ids: request_document.xpath('//HotelReservationID/@ResID_Value').map(&:value),
43
+ status: acknowledged_status
44
+ )
45
+
46
+ warnings = result.value[:unknown].map do |reservation_id|
47
+ { type: 3, text: "Reservation #{reservation_id} not found for acknowledgement", record_id: reservation_id }
48
+ end
49
+ render_success(warnings: warnings)
50
+ end
51
+
52
+ private
53
+
54
+ def feed_method
55
+ raise NotImplementedError, "#{self.class.name} must implement feed_method"
56
+ end
57
+
58
+ def acknowledged_status
59
+ raise NotImplementedError, "#{self.class.name} must implement acknowledged_status"
60
+ end
61
+
62
+ def notification_root
63
+ raise NotImplementedError, "#{self.class.name} must implement notification_root"
64
+ end
65
+
66
+ def container
67
+ raise NotImplementedError, "#{self.class.name} must implement container"
68
+ end
69
+
70
+ def present(_presenter, _xml)
71
+ raise NotImplementedError, "#{self.class.name} must implement present"
72
+ end
73
+
74
+ def processing_failed?
75
+ request_document.at_xpath('/*/Errors').present? && request_document.at_xpath('/*/Success').nil?
76
+ end
77
+
78
+ def hotel_warnings(unknown_hotel_ids)
79
+ unknown_hotel_ids.map { |hotel_id| { type: 6, text: "Not authorized for hotel #{hotel_id}", record_id: hotel_id } }
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,68 @@
1
+ module Spree::Api::SwitchCm
2
+ class TokensController < BaseController
3
+ # Booking.com's tokens also last an hour; connectors refresh on that schedule.
4
+ TOKEN_LIFETIME = 1.hour
5
+
6
+ skip_before_action :authenticate_switch_cm!
7
+
8
+ # POST /api/switch_cm/token-based-authentication/exchange
9
+ # Client id and secret in (JSON or XML), { jwt, ruid } out, then `Authorization: Bearer` on every
10
+ # other call. The value is a Doorkeeper access token rather than a signed JWT, so a connector
11
+ # that only forwards it works but one that decodes it won't.
12
+ def create
13
+ client_id, client_secret = credentials
14
+ return render_fault(:bad_request, 'client_id and client_secret are required') if client_id.blank? || client_secret.blank?
15
+
16
+ @oauth_application = Spree::OauthApplication.find_by(uid: client_id)
17
+ unless @oauth_application&.secret_matches?(client_secret) && switch_cm_integrations.exists?
18
+ return render_fault(:unauthorized, 'Invalid client credentials')
19
+ end
20
+
21
+ render_token(issue_token.plaintext_token)
22
+ end
23
+
24
+ private
25
+
26
+ def issue_token
27
+ Spree::OauthAccessToken.create_for(
28
+ application: @oauth_application,
29
+ resource_owner: nil,
30
+ scopes: '',
31
+ expires_in: TOKEN_LIFETIME.to_i,
32
+ use_refresh_token: false
33
+ )
34
+ end
35
+
36
+ def credentials
37
+ return [params[:client_id], params[:client_secret]] unless xml_request?
38
+
39
+ %w[client_id client_secret].map { |name| request_document.at_xpath("/request/#{name}")&.text&.strip }
40
+ end
41
+
42
+ def xml_request?
43
+ request.content_mime_type&.symbol == :xml
44
+ end
45
+
46
+ def render_token(token)
47
+ return render json: { jwt: token, ruid: ruid } unless xml_request?
48
+
49
+ document = SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.build('response', ruid: ruid) do |xml|
50
+ xml.jwt token
51
+ xml.ruid ruid
52
+ end
53
+ render xml: document
54
+ end
55
+
56
+ # override
57
+ def render_fault(status, text, code: nil)
58
+ code ||= Rack::Utils.status_code(status)
59
+
60
+ if xml_request?
61
+ render xml: SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.fault('response', code: code, text: text, ruid: ruid),
62
+ status: status
63
+ else
64
+ render json: { errors: [{ code: code, message: text }], ruid: ruid }, status: status
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,39 @@
1
+ module Spree::Api::SwitchCm
2
+ module Xml
3
+ class AvailabilitiesController < BaseController
4
+ before_action :require_active_integration!
5
+
6
+ # POST /api/switch_cm/hotels/xml/availability
7
+ # Rooms to sell, prices and closed in one message, validated and applied in the request
8
+ # (AcceptPush); the answer is the real outcome.
9
+ def create
10
+ result = SpreeCmCommissioner::Integrations::SwitchCm::AcceptPush.call(
11
+ integration: current_integration,
12
+ message: 'availability',
13
+ updates: parser.updates,
14
+ request_id: ruid
15
+ )
16
+
17
+ push_response = SpreeCmCommissioner::Integrations::SwitchCm::Xml::PushResponse.new(result: result.value, ruid: ruid)
18
+ render xml: push_response.to_xml, status: push_response.http_status
19
+ end
20
+
21
+ private
22
+
23
+ # override
24
+ def response_root
25
+ 'availability'
26
+ end
27
+
28
+ def parser
29
+ @parser ||= SpreeCmCommissioner::Integrations::SwitchCm::Xml::AvailabilityParser.new(request_document)
30
+ end
31
+
32
+ # B.XML pushes carry no hotel id; the rooms name it.
33
+ # override
34
+ def accommodation_id_from_request
35
+ vendor_id_of_room(parser.first_room_id)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,37 @@
1
+ module Spree::Api::SwitchCm
2
+ module Xml
3
+ # Booking.com's B.XML family (/hotels/xml/...): a request that fails as a whole answers with
4
+ # a <fault code string/> under the call's root element. Requests carry their parameters
5
+ # in a <request> body.
6
+ class BaseController < Spree::Api::SwitchCm::BaseController
7
+ private
8
+
9
+ def response_root
10
+ raise NotImplementedError, "#{self.class.name} must implement response_root"
11
+ end
12
+
13
+ # override
14
+ def render_fault(status, text, code: nil)
15
+ code ||= Rack::Utils.status_code(status)
16
+ render xml: SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.fault(response_root, code: code, text: text, ruid: ruid),
17
+ status: status
18
+ end
19
+
20
+ def render_document(&block)
21
+ render xml: SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.build(response_root, ruid: ruid, &block)
22
+ end
23
+
24
+ # override
25
+ def accommodation_id_from_request
26
+ request_value('hotel_id')
27
+ end
28
+
29
+ # A parameter from the <request> body, falling back to the query string.
30
+ def request_value(name)
31
+ return params[name] if request.raw_post.blank?
32
+
33
+ request_document.at_xpath("/request/#{name}")&.text&.strip.presence || params[name]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ module Spree::Api::SwitchCm
2
+ module Xml
3
+ class ReservationSummariesController < BaseController
4
+ before_action :require_integration!
5
+
6
+ # POST /api/switch_cm/hotels/xml/reservationssummary with <request><hotel_id/></request>
7
+ # The hotel's confirmed stays checking out from tomorrow on, however long ago they were
8
+ # booked. The channel manager imports these while setting the hotel up, so they count against
9
+ # its availability before its first push. Read-only: nothing is marked seen.
10
+ def create
11
+ reservations = SpreeCmCommissioner::Integrations::SwitchCm::ReservationFeed.new([current_integration]).summary
12
+
13
+ render_document do |xml|
14
+ reservations.each do |reservation|
15
+ SpreeCmCommissioner::Integrations::SwitchCm::Xml::ReservationPresenter.new(reservation, summary: true).build(xml)
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ # override
23
+ def response_root
24
+ 'reservations'
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,56 @@
1
+ module Spree::Api::SwitchCm
2
+ module Xml
3
+ class ReservationsController < BaseController
4
+ # POST /api/switch_cm/hotels/xml/reservations with <request><hotel_id/><last_change/><limit/><id/></request>
5
+ # New and cancelled reservations in one stream. B.XML has no acknowledgement, so returning a
6
+ # reservation is what marks it seen; `last_change` re-reads recent ones regardless.
7
+ def create
8
+ integrations, = requested_integrations(request_value('hotel_id') || request_value('hotel_ids'))
9
+ return render_fault(:forbidden, 'Not authorized for these hotels') if integrations.empty?
10
+
11
+ reservations = SpreeCmCommissioner::Integrations::SwitchCm::ReservationFeed.new(integrations).changed_reservations(
12
+ id: request_value('id'),
13
+ last_change: SpreeCmCommissioner::Integrations::SwitchCm::ReservationFeed.parse_last_change(request_value('last_change')),
14
+ limit: request_value('limit')
15
+ )
16
+
17
+ document = SpreeCmCommissioner::Integrations::SwitchCm::Xml::ResponseBuilder.build(response_root, ruid: ruid) do |xml|
18
+ reservations.each { |reservation| SpreeCmCommissioner::Integrations::SwitchCm::Xml::ReservationPresenter.new(reservation).build(xml) }
19
+ end
20
+
21
+ # Marked only once the document is built. One that fails to mark is simply returned
22
+ # again next time, which is harmless: its status is the same.
23
+ failed = mark_seen(reservations)
24
+ log_mark_seen_failures(failed) if failed.any?
25
+
26
+ render xml: document
27
+ end
28
+
29
+ private
30
+
31
+ # override
32
+ def response_root
33
+ 'reservations'
34
+ end
35
+
36
+ def mark_seen(reservations)
37
+ reservations.each_with_object([]) do |reservation, failed|
38
+ SpreeCmCommissioner::Integrations::SwitchCm::MarkSeen.call(
39
+ integration: reservation.integration, order: reservation.order, status: reservation.status
40
+ )
41
+ rescue StandardError => e
42
+ failed << {
43
+ order_id: reservation.id,
44
+ integration_id: reservation.integration.id,
45
+ error_class: e.class.name,
46
+ error_message: e.message
47
+ }
48
+ end
49
+ end
50
+
51
+ def log_mark_seen_failures(failed)
52
+ CmAppLogger.error(label: "#{self.class.name}#mark_seen failed", data: { ruid: ruid, failed: failed })
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,56 @@
1
+ module Spree::Api::SwitchCm
2
+ module Xml
3
+ class RoomRateAvailabilitiesController < BaseController
4
+ DAYS_RANGE = (1..31)
5
+
6
+ before_action :require_integration!
7
+
8
+ # POST /api/switch_cm/hotels/xml/roomrateavailability with <request><hotel_id/><start_date/><number_of_days/><room_id/></request>
9
+ # What we hold per room and night, so the channel manager can check its pushes landed.
10
+ # number_of_days is 1-31, 31 by default, as at Booking.com.
11
+ def create
12
+ start_date = parse_date(request_value('start_date')) || Time.zone.today
13
+ days = (request_value('number_of_days').presence || DAYS_RANGE.max).to_i
14
+ return render_fault(:bad_request, "number_of_days must be between #{DAYS_RANGE.min} and #{DAYS_RANGE.max}") unless DAYS_RANGE.cover?(days)
15
+
16
+ rows = SpreeCmCommissioner::Integrations::SwitchCm::AvailabilityReadback.new(
17
+ integration: current_integration,
18
+ dates: (start_date...(start_date + days)).to_a,
19
+ room_id: request_value('room_id')
20
+ ).rows
21
+
22
+ render_document { |xml| rows.each { |row| build_row(xml, row) } }
23
+ end
24
+
25
+ private
26
+
27
+ # override
28
+ def response_root
29
+ 'result'
30
+ end
31
+
32
+ def build_row(xml, row)
33
+ xml.roomrate(
34
+ {
35
+ date: row.date.iso8601,
36
+ room_id: row.room_id,
37
+ rate_id: row.rate_id,
38
+ rooms_to_sell: row.rooms_to_sell,
39
+ booked: row.booked,
40
+ cancelled: row.cancelled,
41
+ closed: row.closed ? 1 : 0,
42
+ price: row.price && format('%.2f', row.price),
43
+ currency: row.currency,
44
+ pushed_at: row.pushed_at&.utc&.iso8601
45
+ }.compact
46
+ )
47
+ end
48
+
49
+ def parse_date(value)
50
+ Date.iso8601(value) if value.present?
51
+ rescue Date::Error
52
+ nil
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ module Spree::Api::SwitchCm
2
+ module Xml
3
+ class RoomRatesController < BaseController
4
+ before_action :require_integration!
5
+
6
+ # POST /api/switch_cm/hotels/xml/roomrates with <request><hotel_id/></request>
7
+ # The hotel's rooms, each with its rates, which the channel manager maps to its own
8
+ # ("Refresh Room" in switch.cm).
9
+ def create
10
+ vendor = current_integration.vendor
11
+ rooms = SpreeCmCommissioner::Integrations::SwitchCm::RoomCatalog.new(vendor).rooms
12
+
13
+ render_document do |xml|
14
+ rooms.each do |room|
15
+ xml.room(id: room.id, hotel_id: vendor.id, hotel_name: vendor.name, room_name: room.name, max_children: room.max_children) do
16
+ xml.rates do
17
+ xml.rate(id: room.rate_id, rate_name: room.rate_name, max_persons: room.max_persons, is_child_rate: 0, readonly: 0)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ # override
27
+ def response_root
28
+ 'rooms'
29
+ end
30
+ end
31
+ end
32
+ end
@@ -3,6 +3,8 @@ module Spree
3
3
  module V2
4
4
  module Operator
5
5
  class CheckInsController < ::Spree::Api::V2::ResourceController
6
+ include SpreeCmCommissioner::Operator::EventAuthorization
7
+
6
8
  before_action :require_spree_current_user, only: %i[index create]
7
9
 
8
10
  # Check-in history data requires fresh/recent data for operator dashboards
@@ -45,15 +47,14 @@ module Spree
45
47
 
46
48
  private
47
49
 
48
- # Non-organizers may only see check-in history for an event they're assigned
49
- # to, and only while it's ongoing — once it ends, it becomes organizer-owned
50
- # history (admins assign themselves the organizer role explicitly if they
51
- # need access). A missing/nonexistent taxon_id is restricted the same way.
50
+ # The organizer who owns the event's vendor always sees check-in history.
51
+ # Crew/operators assigned via user.events only see it while the event is
52
+ # ongoing — once it ends, it becomes organizer-owned history. A missing/
53
+ # nonexistent taxon_id is restricted the same way.
52
54
  def restricted?
53
- return false if spree_current_user.organizer?
54
-
55
55
  taxon = Spree::Taxon.find_by(id: params[:taxon_id])
56
56
  return true if taxon.nil?
57
+ return false if organizer_for_event?(taxon)
57
58
  return true unless spree_current_user.events.exists?(id: taxon.id)
58
59
 
59
60
  taxon.ended?
@@ -3,6 +3,10 @@ module Spree
3
3
  module V2
4
4
  module Operator
5
5
  class EventChartsController < ::Spree::Api::V2::ResourceController
6
+ include SpreeCmCommissioner::Operator::EventAuthorization
7
+
8
+ before_action :require_spree_current_user
9
+
6
10
  ACCEPTED_KYC_FIELDS = %i[guest_gender guest_occupation guest_nationality].freeze
7
11
  ADDITIONAL_FIELDS = %i[participation entry_type].freeze
8
12
 
@@ -40,18 +44,12 @@ module Spree
40
44
 
41
45
  private
42
46
 
43
- # Non-organizers may only see chart data for an event they're assigned to,
44
- # and only while it's ongoing — once it ends, it becomes organizer-owned
45
- # history (admins assign themselves the organizer role explicitly if they
46
- # need access). A missing/nonexistent taxon_id is restricted the same way.
47
+ # Only the organizer who owns the event's vendor may see chart data —
48
+ # crew/operators assigned via user.events never see it, regardless of
49
+ # whether the event is ongoing or ended. See organizer_for_event? (EventAuthorization).
47
50
  def restricted?
48
- return false if spree_current_user&.organizer?
49
-
50
51
  taxon = Spree::Taxon.find_by(id: params[:taxon_id])
51
- return true if taxon.nil?
52
- return true unless spree_current_user&.events&.exists?(id: taxon.id)
53
-
54
- taxon.ended?
52
+ !organizer_for_event?(taxon)
55
53
  end
56
54
 
57
55
  def event_chart_aggregator_queries
@@ -3,11 +3,21 @@ module Spree
3
3
  module V2
4
4
  module Operator
5
5
  class EventTicketAggregatorsController < ::Spree::Api::V2::ResourceController
6
+ include SpreeCmCommissioner::Operator::EventAuthorization
7
+
8
+ before_action :require_spree_current_user
9
+
6
10
  def resource
7
- @resource = SpreeCmCommissioner::EventTicketAggregatorQuery.new(
8
- taxon_id: params[:taxon_id],
9
- refreshed: params[:refreshed] || false
10
- ).call
11
+ taxon = Spree::Taxon.find_by(id: params[:taxon_id])
12
+
13
+ @resource = if organizer_for_event?(taxon)
14
+ SpreeCmCommissioner::EventTicketAggregatorQuery.new(
15
+ taxon_id: params[:taxon_id],
16
+ refreshed: params[:refreshed] || false
17
+ ).call
18
+ else
19
+ SpreeCmCommissioner::EventTicketAggregator.new(id: params[:taxon_id], value: [])
20
+ end
11
21
  end
12
22
 
13
23
  def resource_serializer
@@ -3,8 +3,22 @@ module Spree
3
3
  module V2
4
4
  module Operator
5
5
  class PieChartEventAggregatorsController < ::Spree::Api::V2::ResourceController
6
+ include SpreeCmCommissioner::Operator::EventAuthorization
7
+
8
+ before_action :require_spree_current_user
9
+
6
10
  def resource
7
- @resource = pie_chart_event_aggregator_queries.call
11
+ taxon = Spree::Taxon.find_by(id: params[:taxon_id])
12
+
13
+ @resource = if organizer_for_event?(taxon)
14
+ pie_chart_event_aggregator_queries.call
15
+ else
16
+ SpreeCmCommissioner::EventChart.new(
17
+ id: params[:chart_type] || 'participation',
18
+ chart_type: params[:chart_type] || 'participation',
19
+ product_charts: []
20
+ )
21
+ end
8
22
  end
9
23
 
10
24
  def resource_serializer
@@ -14,6 +14,34 @@ module Spree
14
14
  def resource_serializer
15
15
  ::SpreeCmCommissioner::Api::V2::Platform::HomepageSectionRelatableOptionsSerializer
16
16
  end
17
+
18
+ # Opt-in only (params[:taxon_scope] == 'events'/params[:tenant_id]), so existing callers
19
+ # that search across all taxons/vendors (e.g. ticket transfer rules, homepage sections)
20
+ # are unaffected.
21
+ def scope
22
+ result = super
23
+ result = result.merge(Spree::Taxon.events) if model_class == Spree::Taxon && params[:taxon_scope] == 'events'
24
+ result = scoped_to_tenant(result) if params[:tenant_id].present?
25
+
26
+ result
27
+ end
28
+
29
+ private
30
+
31
+ # Product has its own tenant_id (set from its vendor — see
32
+ # SpreeCmCommissioner::ProductDecorator#set_tenant). Taxon (Event) has no tenant_id
33
+ # column of its own, so it's derived through the products that reference it as their
34
+ # event.
35
+ def scoped_to_tenant(result)
36
+ case model_class.name
37
+ when 'Spree::Vendor', 'Spree::Product'
38
+ result.where(tenant_id: params[:tenant_id])
39
+ when 'Spree::Taxon'
40
+ result.where(id: Spree::Product.where(tenant_id: params[:tenant_id]).select(:event_id))
41
+ else
42
+ result
43
+ end
44
+ end
17
45
  end
18
46
  end
19
47
  end
@@ -0,0 +1,41 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class CrossAppHandoffTokensController < ::Spree::Api::V2::ResourceController
6
+ # No AR model backs a handoff token (see CrossAppHandoffTokens::Encode) — this just gives
7
+ # the generated {token:, expires_at:} pair an #id so it can go through the same
8
+ # serializer/JSON:API pipeline as every other storefront resource.
9
+ Resource = Struct.new(:token, :expires_at) do
10
+ def id
11
+ token
12
+ end
13
+ end
14
+
15
+ def create
16
+ return render_error_payload(I18n.t('authenticator.incorrect_login'), 401) if spree_current_user.blank?
17
+
18
+ result = SpreeCmCommissioner::CrossAppHandoffTokens::Encode.call(user: spree_current_user, purpose: purpose)
19
+ return render_error_payload(result.error, 422) unless result.success?
20
+
21
+ render_serialized_payload(201) { serialize_resource(Resource.new(result.value[:token], result.value[:expires_at])) }
22
+ end
23
+
24
+ def serialize_resource(resource)
25
+ resource_serializer.new(resource).serializable_hash
26
+ end
27
+
28
+ def resource_serializer
29
+ SpreeCmCommissioner::V2::Storefront::CrossAppHandoffTokenSerializer
30
+ end
31
+
32
+ private
33
+
34
+ def purpose
35
+ params[:purpose].to_s
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -16,10 +16,12 @@ module Spree
16
16
  end
17
17
 
18
18
  def collection
19
- @collection ||= model_class.filter_by_segment(params[:homepage_id] || :general)
20
- .active
19
+ # for_feed applies the segment itself, and its fallback only counts rows that survive
20
+ # the filters above, so it goes last.
21
+ @collection ||= model_class.active
21
22
  .where(tenant_id: nil)
22
23
  .where(preview: false)
24
+ .for_feed(params[:homepage_id] || :general, params[:mini_app])
23
25
  .order(position: :asc)
24
26
  end
25
27
  end