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,230 @@
1
+ module SpreeCmCommissioner::Integrations::BookMeBusV1::ExternalClient
2
+ class Client
3
+ include Connection
4
+ include SpreeCmCommissioner::AuditWrapper
5
+
6
+ BASE_PATH = '/api/v2'.freeze
7
+ RATE_LIMIT = 100
8
+
9
+ attr_reader :integration
10
+
11
+ def initialize(integration:)
12
+ @integration = integration
13
+ end
14
+
15
+ # Location Operations
16
+
17
+ # Get all available locations
18
+ # @return [Array<SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::Location>] Array of location objects
19
+ def get_locations! # rubocop:disable Naming/AccessorMethodName
20
+ endpoint = "#{BASE_PATH}/locations"
21
+
22
+ response = get(endpoint)
23
+ handle_response(response, SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::Location, collection: true)
24
+ end
25
+
26
+ # Trip Operations
27
+
28
+ # Search available trips for a given route and date
29
+ # @param origin_id [String] The origin location ID
30
+ # @param destination_id [String] The destination location ID
31
+ # @param on_date [String] The travel date (YYYY-MM-DD format)
32
+ # @return [Array<SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::Trip>] Array of available trip objects
33
+ def search_trips!(origin_id:, destination_id:, on_date:)
34
+ params = {
35
+ origin_id: origin_id,
36
+ destination_id: destination_id,
37
+ on_date: on_date,
38
+ # Scopes results to this integration's operator when multiple Integrations share one
39
+ # BookMeBus application (Application#use_multiple_operators?). Compacted out (rather than
40
+ # sent as an empty value) when unset, so a single-operator setup behaves exactly as before.
41
+ operator_id: integration.operator_id
42
+ }.compact
43
+ endpoint = "#{BASE_PATH}/trip_availabilities"
44
+
45
+ response = get(endpoint, params)
46
+ handle_response(response, SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::Trip, collection: true)
47
+ end
48
+
49
+ # Seat Layout Operations
50
+
51
+ # Get seat layout for a specific trip segment
52
+ # @param trip_id [String] The trip ID
53
+ # @param from_stop_id [String] The departure stop ID
54
+ # @param to_stop_id [String] The arrival stop ID
55
+ # @param on_date [String] The travel date (YYYY-MM-DD format)
56
+ # @param show_all_stops [Boolean] Whether to show all stops (default: false)
57
+ # @return [SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::SeatLayout] Seat layout object
58
+ def get_seat_layout!(trip_id:, from_stop_id:, to_stop_id:, on_date:, show_all_stops: false)
59
+ params = {
60
+ trip_id: trip_id,
61
+ from_stop_id: from_stop_id,
62
+ to_stop_id: to_stop_id,
63
+ on_date: on_date,
64
+ show_all_stops: show_all_stops
65
+ }
66
+ endpoint = "#{BASE_PATH}/seat_layouts"
67
+
68
+ response = get(endpoint, params)
69
+ handle_response(response, SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::SeatLayout)
70
+ end
71
+
72
+ # Reservation Operations
73
+
74
+ # Create a reservation cart in BookMeBus
75
+ # @param params [Hash] Reservation parameters
76
+ # @option params [Integer] :trip_id The BookMeBus trip ID
77
+ # @option params [Integer] :from_stop_id The boarding stop ID
78
+ # @option params [Integer] :to_stop_id The drop-off stop ID
79
+ # @option params [Integer] :from_stop_time_id The boarding stop time ID
80
+ # @option params [Integer] :to_stop_time_id The drop-off stop time ID
81
+ # @option params [String] :departure_date The travel date (YYYY-MM-DD format)
82
+ # @option params [Hash] :reserved_seats Reserved seats hash keyed by stop_id with seat detail objects
83
+ # @option params [String] :contact_name Passenger contact name
84
+ # @option params [String] :contact_email Passenger contact email
85
+ # @option params [String] :contact_phone Passenger contact phone
86
+ # @option params [Object] :internal_order Internal order for audit
87
+ # @return [SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::ReservationCart] The created reservation cart
88
+ def create_reservation!(params)
89
+ number_of_seats = params[:reserved_seats].values.flatten.count
90
+
91
+ body = {
92
+ reservations: [
93
+ {
94
+ trip_id: params[:trip_id],
95
+ from_stop_id: params[:from_stop_id],
96
+ to_stop_id: params[:to_stop_id],
97
+ from_stop_time_id: params[:from_stop_time_id],
98
+ to_stop_time_id: params[:to_stop_time_id],
99
+ departure_date: params[:departure_date],
100
+ number_of_seats: number_of_seats,
101
+ reserved_seats: params[:reserved_seats],
102
+ contact_name: params[:contact_name],
103
+ contact_email: params[:contact_email],
104
+ contact_phone: params[:contact_phone],
105
+ is_request_pickup: false
106
+ }.compact
107
+ ]
108
+ }
109
+
110
+ endpoint = "#{BASE_PATH}/trip_reservations"
111
+
112
+ with_audit(
113
+ event_base: 'bookmebus.reservations.post',
114
+ request: { method: 'POST', endpoint: endpoint, body: body },
115
+ auditable: params[:internal_order]
116
+ ) do
117
+ response = post(endpoint, body)
118
+ handle_response(response, SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::ReservationCart)
119
+ end
120
+ end
121
+
122
+ # Wallet Payment Operations
123
+ # Create a wallet payment for a reservation cart
124
+ # @param cart_id [String] The reservation cart ID
125
+ # @return [SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::WalletPayment] The created wallet payment
126
+ def create_wallet_payment!(cart_id:, internal_order:)
127
+ endpoint = "#{BASE_PATH}/wallet_payments?reservation_cart_id=#{cart_id}"
128
+
129
+ with_audit(
130
+ event_base: 'bookmebus.wallet_payments.post',
131
+ request: { method: 'POST', endpoint: endpoint, body: {} },
132
+ auditable: internal_order
133
+ ) do
134
+ response = post(endpoint, {})
135
+ handle_response(response, SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::WalletPayment)
136
+ end
137
+ end
138
+
139
+ # Confirm a wallet payment (deduct from user's BookMeBus wallet)
140
+ # @param payment_id [String] The wallet payment ID
141
+ # @return [SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::WalletPayment] The confirmed wallet payment
142
+ def confirm_wallet_payment!(payment_id:, internal_order:)
143
+ endpoint = "#{BASE_PATH}/bmb_wallets?payment_id=#{payment_id}"
144
+
145
+ with_audit(
146
+ event_base: 'bookmebus.wallet_payments.post',
147
+ request: { method: 'POST', endpoint: endpoint, body: {} },
148
+ auditable: internal_order
149
+ ) do
150
+ response = post(endpoint, {})
151
+ handle_response(response, SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::WalletPayment)
152
+ end
153
+ end
154
+
155
+ # Get passenger seat info for a specific seat
156
+ # @param stop_id [String] The stop ID
157
+ # @param trip_id [String] The trip ID
158
+ # @param seat [String] The seat number/label
159
+ # @param departure_date [String] The departure date (YYYY-MM-DD format)
160
+ # @return [SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::InventoryItemStatus] Seat/passenger info
161
+ def inventory_item_status!(stop_id:, trip_id:, label:, layer:, departure_date:)
162
+ params = {
163
+ stop_id: stop_id,
164
+ trip_id: trip_id,
165
+ seat: {
166
+ label: label,
167
+ layer: layer
168
+ },
169
+ departure_date: departure_date
170
+ }
171
+ endpoint = "#{BASE_PATH}/passenger_seat_infos"
172
+
173
+ with_audit(
174
+ event_base: 'bookmebus.passenger_seat_infos.get',
175
+ request: { method: 'GET', endpoint: endpoint, params: params },
176
+ auditable: integration
177
+ ) do
178
+ response = get(endpoint, params)
179
+ handle_response(response, SpreeCmCommissioner::Integrations::BookMeBusV1::Resources::InventoryItemStatus)
180
+ end
181
+ end
182
+
183
+ private
184
+
185
+ def handle_response(response, model_class, collection: false) # rubocop:disable Metrics/PerceivedComplexity
186
+ raise_api_error(response) unless response.success?
187
+
188
+ data = response.body
189
+
190
+ # Handle both JSON:API format and direct object format
191
+ if data.is_a?(Hash) && data['data'].present?
192
+ # JSON:API format
193
+ collection ? model_class.from_json_api(data) : model_class.from_json_api_single(data)
194
+
195
+ elsif collection && data.is_a?(Array)
196
+ # Direct array format
197
+ data.map { |item| model_class.new(item) }
198
+ elsif !collection && data.is_a?(Hash)
199
+ # Direct object format
200
+ model_class.new(data)
201
+ else
202
+ Rails.logger.warn "handle_response - returning nil, data: #{data.inspect}"
203
+ nil
204
+ end
205
+ end
206
+
207
+ def raise_api_error(response)
208
+ status = response.status
209
+ body = response.body
210
+
211
+ message = if body.is_a?(Hash)
212
+ error = body['errors']&.first || body['error'] || body['message']
213
+ case error
214
+ when Hash
215
+ error['detail'] || error['title'] || error.to_json
216
+ when String
217
+ error
218
+ else
219
+ "API request failed with status #{status}"
220
+ end
221
+ else
222
+ "API request failed with status #{status}"
223
+ end
224
+
225
+ message = "#{message} (body: #{body.to_json})" if status >= 500
226
+
227
+ raise SpreeCmCommissioner::Integrations::ExternalClientError.new(message, status)
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,147 @@
1
+ module SpreeCmCommissioner::Integrations::BookMeBusV1::ExternalClient
2
+ module Connection
3
+ OAUTH_TOKEN_PATH = '/oauth/token'.freeze
4
+ REQUEST_TIMEOUT = 30
5
+ OPEN_TIMEOUT = 10
6
+
7
+ def get(url, params = nil, headers = nil)
8
+ execute_request { connection.get(url, params, build_headers(headers)) }
9
+ end
10
+
11
+ def post(url, body = nil, headers = nil)
12
+ execute_request { connection.post(url, body, build_headers(headers)) }
13
+ end
14
+
15
+ def put(url, body = nil, headers = nil)
16
+ execute_request { connection.put(url, body, build_headers(headers)) }
17
+ end
18
+
19
+ def patch(url, body = nil, headers = nil)
20
+ execute_request { connection.patch(url, body, build_headers(headers)) }
21
+ end
22
+
23
+ def delete(url, params = nil, headers = nil)
24
+ execute_request { connection.delete(url, params, build_headers(headers)) }
25
+ end
26
+
27
+ def connection
28
+ @connection ||= Faraday.new(url: @integration.base_url) do |faraday|
29
+ faraday.request :json
30
+ faraday.response :json, content_type: /\bjson$/
31
+ faraday.options.timeout = REQUEST_TIMEOUT
32
+ faraday.options.open_timeout = OPEN_TIMEOUT
33
+ faraday.adapter Faraday.default_adapter
34
+ end
35
+ end
36
+
37
+ # Thread-safe token access with automatic refresh on expiration
38
+ def access_token
39
+ token_mutex.synchronize do
40
+ @access_token ||= @integration.access_token
41
+ @token_expires_at ||= @integration.token_expires_at
42
+
43
+ fetch_and_persist_access_token! if token_expired?
44
+
45
+ @access_token
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def token_mutex
52
+ @token_mutex ||= Mutex.new
53
+ end
54
+
55
+ def token_expired?
56
+ @access_token.blank? || @token_expires_at.blank? || Time.current >= @token_expires_at
57
+ end
58
+
59
+ def fetch_and_persist_access_token!
60
+ response = oauth_connection.post(OAUTH_TOKEN_PATH) do |req|
61
+ req.body = {
62
+ grant_type: 'client_credentials',
63
+ client_id: @integration.client_id,
64
+ client_secret: @integration.client_secret
65
+ }
66
+ end
67
+
68
+ unless response.success?
69
+ raise SpreeCmCommissioner::Integrations::ExternalClientError.new(
70
+ 'Failed to fetch access token',
71
+ response.status
72
+ )
73
+ end
74
+
75
+ token_data = response.body
76
+ @access_token = token_data['access_token']
77
+ expires_in = token_data['expires_in'] || 3600
78
+ @token_expires_at = Time.current + expires_in.seconds - 60.seconds
79
+
80
+ update_integration_token!
81
+ reset_connection
82
+ end
83
+
84
+ # Execute HTTP request with automatic token refresh on 401
85
+ def execute_request(retry_count: 0, &block)
86
+ response = block.call
87
+
88
+ if response.status == 401 && retry_count.zero?
89
+ invalidate_token
90
+ access_token
91
+ reset_connection
92
+ return execute_request(retry_count: 1, &block)
93
+ end
94
+
95
+ response
96
+ rescue Faraday::TimeoutError => e
97
+ raise SpreeCmCommissioner::Integrations::ExternalClientError.new(
98
+ "Request timeout: #{e.message}",
99
+ 408
100
+ )
101
+ rescue Faraday::Error => e
102
+ raise SpreeCmCommissioner::Integrations::ExternalClientError.new(
103
+ "Request failed: #{e.message}",
104
+ nil
105
+ )
106
+ end
107
+
108
+ def build_headers(extra_headers)
109
+ default_headers = {
110
+ 'Content-Type' => 'application/json',
111
+ 'Accept' => 'application/json',
112
+ 'Authorization' => "Bearer #{access_token}"
113
+ }
114
+ return default_headers if extra_headers.blank?
115
+
116
+ default_headers.merge(extra_headers)
117
+ end
118
+
119
+ def invalidate_token
120
+ token_mutex.synchronize do
121
+ @access_token = nil
122
+ @token_expires_at = nil
123
+ end
124
+ end
125
+
126
+ def oauth_connection
127
+ @oauth_connection ||= Faraday.new(url: @integration.base_url) do |faraday|
128
+ faraday.request :url_encoded
129
+ faraday.response :json, content_type: /\bjson$/
130
+ faraday.options.timeout = REQUEST_TIMEOUT
131
+ faraday.options.open_timeout = OPEN_TIMEOUT
132
+ faraday.adapter Faraday.default_adapter
133
+ end
134
+ end
135
+
136
+ def reset_connection
137
+ @connection = nil
138
+ connection
139
+ end
140
+
141
+ def update_integration_token!
142
+ @integration.access_token = @access_token
143
+ @integration.token_expires_at = @token_expires_at
144
+ @integration.save! if @integration.changed?
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpreeCmCommissioner
4
+ module Integrations
5
+ class BookMeBusV1
6
+ module Inventory
7
+ # Fetches detailed status for a specific inventory item (seat) from BookMeBus
8
+ # Uses the inventory_item_status! API (passenger_seat_infos endpoint)
9
+ class ExternalInventoryItemStatus
10
+ prepend ::Spree::ServiceModule::Base
11
+
12
+ def call(integration:, order:, options:)
13
+ @integration = integration
14
+ @order = order
15
+ @options = options
16
+
17
+ validate_params!
18
+ item_status = fetch_item_status
19
+
20
+ success(item_status: item_status)
21
+ rescue SpreeCmCommissioner::Integrations::ExternalClientError => e
22
+ failure(nil, e.message)
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :integration, :order, :options
28
+
29
+ def validate_params!
30
+ raise_error('stop_id is required') if options[:stop_id].blank?
31
+ raise_error('seat is required') if options[:label].blank?
32
+ raise_error('departure_date is required') if options[:departure_date].blank?
33
+ end
34
+
35
+ def fetch_item_status
36
+ integration.client.inventory_item_status!(
37
+ stop_id: options[:stop_id],
38
+ trip_id: options[:trip_id],
39
+ label: options[:label],
40
+ layer: options[:layer],
41
+ departure_date: options[:departure_date]
42
+ )
43
+ end
44
+
45
+ def raise_error(msg)
46
+ raise SpreeCmCommissioner::Integrations::SyncError, msg
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpreeCmCommissioner
4
+ module Integrations
5
+ class BookMeBusV1
6
+ module Inventory
7
+ # Fetches all inventory items (seats) for an order from BookMeBus
8
+ # Uses the get_seat_layout! API to retrieve seat availability
9
+ class ExternalInventoryItems
10
+ prepend ::Spree::ServiceModule::Base
11
+
12
+ def call(integration:, order:)
13
+ @integration = integration
14
+ @order = order
15
+
16
+ items = []
17
+ order.line_items.each do |line_item|
18
+ items.concat(fetch_items_for_line_item(line_item))
19
+ end
20
+
21
+ success(items: items)
22
+ rescue SpreeCmCommissioner::Integrations::ExternalClientError => e
23
+ failure(nil, e.message)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :integration, :order
29
+
30
+ def fetch_items_for_line_item(line_item)
31
+ trip_mapping = find_trip_mapping(line_item)
32
+ return [] if trip_mapping.nil?
33
+
34
+ seat_layout = fetch_seat_layout(line_item, trip_mapping)
35
+ build_inventory_items(seat_layout, line_item, trip_mapping)
36
+ end
37
+
38
+ def find_trip_mapping(line_item)
39
+ return nil if line_item.trip_id.blank?
40
+
41
+ SpreeCmCommissioner::IntegrationMapping.find_by(
42
+ integration_id: integration.id,
43
+ internal_type: 'SpreeCmCommissioner::Trip',
44
+ internal_id: line_item.trip_id,
45
+ status: :active
46
+ )
47
+ end
48
+
49
+ def fetch_seat_layout(line_item, trip_mapping)
50
+ integration.client.get_seat_layout!(
51
+ trip_id: trip_mapping.external_id.to_i,
52
+ from_stop_id: fetch_stop_id(trip_mapping, line_item.boarding_trip_stop_id, 'from_stop_id'),
53
+ to_stop_id: fetch_stop_id(trip_mapping, line_item.drop_off_trip_stop_id, 'to_stop_id'),
54
+ on_date: line_item.from_date.to_date.strftime('%Y-%m-%d')
55
+ )
56
+ end
57
+
58
+ def fetch_stop_id(trip_mapping, trip_stop_id, fallback_key)
59
+ return trip_mapping.external_payload[fallback_key] if trip_stop_id.blank?
60
+
61
+ stop_mapping = SpreeCmCommissioner::IntegrationMapping.find_by(
62
+ internal_type: 'SpreeCmCommissioner::TripStop',
63
+ internal_id: trip_stop_id,
64
+ status: :active
65
+ )
66
+ stop_mapping&.external_payload&.dig('stop', 'id') || trip_mapping.external_payload[fallback_key]
67
+ end
68
+
69
+ def build_inventory_items(seat_layout, line_item, trip_mapping)
70
+ items = []
71
+ items += seat_layout.seats.map do |seat|
72
+ {
73
+ id: "#{trip_mapping.external_id}-#{seat.label}",
74
+ seat_label: seat.label,
75
+ seat_type: seat.seat_type_name,
76
+ layer: seat.layer,
77
+ status: seat.available? ? 'available' : 'booked',
78
+ gender: seat.gender,
79
+ position: seat.position,
80
+ from_stop_id: seat_layout.from_stop_id,
81
+ departure_date: line_item.from_date.to_date.strftime('%Y-%m-%d'),
82
+ trip_id: trip_mapping.external_id.to_i
83
+ }
84
+ end
85
+ items
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end