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
@@ -2,10 +2,15 @@ module SpreeCmCommissioner
2
2
  class InventoryItem < Base
3
3
  include SpreeCmCommissioner::ProductType
4
4
  include SpreeCmCommissioner::StoreMetadata
5
+ include SpreeCmCommissioner::Integrations::IntegrationMappable
5
6
 
6
7
  MAX_DISPLAY_STOCK = 20
7
8
  LOW_STOCK_THRESHOLD = 20
8
9
 
10
+ store_public_metadata :last_synced_at, :datetime
11
+ store_public_metadata :original_price, :integer
12
+ store_public_metadata :currency, :string
13
+
9
14
  # Association
10
15
  belongs_to :variant, class_name: 'Spree::Variant', optional: false
11
16
 
@@ -36,6 +41,11 @@ module SpreeCmCommissioner
36
41
 
37
42
  before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
38
43
 
44
+ # A night whose inventory a partner owns (Integration#inventory_external_wins?) starts closed: its
45
+ # count is only our seed from the physical room count, which the partner may already have sold
46
+ # elsewhere. The partner's first availability push opens it.
47
+ before_create -> { self.closed = true }, if: -> { permanent_stock? && variant&.inventory_external_wins? }
48
+
39
49
  after_commit :schedule_product_cache_invalidation
40
50
  after_commit :publish_live_stock_status_to_firestore
41
51
 
@@ -109,6 +119,14 @@ module SpreeCmCommissioner
109
119
  # This method is only used when admin update stock
110
120
  def adjust_quantity!(quantity)
111
121
  with_lock do
122
+ # Warm the Redis cache from the current DB value BEFORE applying the delta below.
123
+ # adjust_quantity_in_redis treats a missing key as 0, so a negative delta against a
124
+ # never-cached key would compute e.g. 0 - 3 = -3, clamp to 0, and permanently cache the
125
+ # wrong value (the read path only self-heals from a genuinely *missing* key, not a wrong
126
+ # one). This is the same warm-before-delta pattern already used by
127
+ # InventoryItems::AdjustAvailableQuantity for the same reason.
128
+ SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new([self]).call
129
+
112
130
  # IMPORTANT: Apply quantity changes directly without defensive clamping.
113
131
  # The model validation will catch any attempts to go negative, surfacing bugs
114
132
  # in upstream logic rather than silently losing data.
@@ -3,6 +3,8 @@ module SpreeCmCommissioner
3
3
  def self.prepended(base) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
4
4
  include_modules(base)
5
5
 
6
+ base.money_methods :service_fee_total
7
+
6
8
  base.belongs_to :accepter, class_name: 'Spree::User', optional: true
7
9
  base.belongs_to :rejecter, class_name: 'Spree::User', optional: true
8
10
  base.belongs_to :event, class_name: 'Spree::Taxon', optional: true, inverse_of: :line_items
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  module OrderDecorator
3
- def self.prepended(base) # rubocop:disable Metrics/MethodLength
3
+ def self.prepended(base) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
4
4
  base.include SpreeCmCommissioner::OrderChannelBitwise
5
5
  base.include SpreeCmCommissioner::StoreMetadata
6
6
  base.include SpreeCmCommissioner::PhoneNumberSanitizer
@@ -11,6 +11,7 @@ module SpreeCmCommissioner
11
11
  base.include SpreeCmCommissioner::OrderScopes
12
12
  base.include SpreeCmCommissioner::TicketTransferable
13
13
  base.include SpreeCmCommissioner::OrderQrCodeable
14
+ base.include SpreeCmCommissioner::Integrations::IntegrationMappable
14
15
 
15
16
  base.before_create :link_by_phone_number
16
17
  base.before_create :associate_customer
@@ -21,6 +22,8 @@ module SpreeCmCommissioner
21
22
  base.store_private_metadata :preload_main_trip_ids, :array, default: []
22
23
  base.store_public_metadata :order_options, :array, default: []
23
24
 
25
+ base.money_methods :service_fee_total
26
+
24
27
  base.validates :promo_total, base::MONEY_VALIDATION
25
28
  base.validates :phone_number, presence: true, if: :require_phone_number
26
29
 
@@ -49,6 +52,7 @@ module SpreeCmCommissioner
49
52
  base.has_many :reserved_blocks, through: :guests, class_name: 'SpreeCmCommissioner::ReservedBlock'
50
53
  base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variants
51
54
  base.has_many :product_completion_steps, class_name: 'SpreeCmCommissioner::ProductCompletionStep', through: :line_items
55
+ base.has_many :audit_events, class_name: 'SpreeCmCommissioner::AuditEvent', as: :auditable
52
56
 
53
57
  base.delegate :customer, to: :user, allow_nil: true
54
58
 
@@ -156,6 +160,14 @@ module SpreeCmCommissioner
156
160
  line_items.any?(&:delivery_required?)
157
161
  end
158
162
 
163
+ # Mirrors create_tax_charge! (core/app/models/spree/order.rb) — wired into the same
164
+ # checkout transitions via SpreeCmCommissioner::OrderStateMachine, plus called directly
165
+ # from TransitOrder::Create and DraftOrders::Create, which build orders outside the
166
+ # checkout state machine entirely.
167
+ def create_service_fee_charge!
168
+ SpreeCmCommissioner::ServiceFeeRule.adjust(self, line_items)
169
+ end
170
+
159
171
  # overrided
160
172
  def payment_required?
161
173
  return false if need_confirmation?
@@ -5,6 +5,37 @@ module SpreeCmCommissioner
5
5
  order.item_total = line_items.sum(&:amount)
6
6
  update_order_total
7
7
  end
8
+
9
+ # Rolls line items' service_fee_total up into the order (display total only —
10
+ # the fee is already folded into order.adjustment_total/total via the
11
+ # non_taxable_adjustment_total bucket, see Adjustable::Adjuster::ServiceFee).
12
+ def update_adjustment_total
13
+ super
14
+ order.service_fee_total = line_items.sum(:service_fee_total)
15
+ end
16
+
17
+ # Duplicates Spree::OrderUpdater#persist_totals' update_columns call (spree_core 4.5.3)
18
+ # rather than calling super + a second update_columns for service_fee_total, to keep this
19
+ # to a single UPDATE per order -- with read replicas/connection pooling in front of this,
20
+ # a second round-trip per persist_totals call isn't free. Must be kept in sync if core ever
21
+ # changes that column list.
22
+ def persist_totals
23
+ order.update_columns( # rubocop:disable Rails/SkipsModelValidations
24
+ payment_state: order.payment_state,
25
+ shipment_state: order.shipment_state,
26
+ item_total: order.item_total,
27
+ item_count: order.item_count,
28
+ adjustment_total: order.adjustment_total,
29
+ included_tax_total: order.included_tax_total,
30
+ additional_tax_total: order.additional_tax_total,
31
+ payment_total: order.payment_total,
32
+ shipment_total: order.shipment_total,
33
+ promo_total: order.promo_total,
34
+ total: order.total,
35
+ service_fee_total: order.service_fee_total,
36
+ updated_at: Time.current
37
+ )
38
+ end
8
39
  end
9
40
  end
10
41
 
@@ -1,5 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  class Place < Base
3
+ include SpreeCmCommissioner::Integrations::IntegrationMappable
4
+
3
5
  acts_as_nested_set
4
6
 
5
7
  validates :reference, presence: true, if: :validate_reference?
@@ -49,7 +49,8 @@ module SpreeCmCommissioner
49
49
  quantity_locked_on_hold: cache_inventory(locked_hold_keys[index], item, item.quantity_locked_on_hold, locked_hold[index]),
50
50
  max_capacity: item.max_capacity,
51
51
  inventory_item_id: item.id,
52
- variant_id: item.variant_id
52
+ variant_id: item.variant_id,
53
+ closed: item.closed
53
54
  )
54
55
  end
55
56
 
@@ -1,6 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  class Route < Base
3
3
  include SpreeCmCommissioner::RouteType
4
+ include SpreeCmCommissioner::Integrations::IntegrationMappable
4
5
  include SpreeCmCommissioner::StoreMetadata
5
6
  include SpreeCmCommissioner::ServiceOrigin
6
7
 
@@ -1,5 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  class SeatLayout < Base
3
+ include SpreeCmCommissioner::Integrations::IntegrationMappable
3
4
  include StoreMetadata
4
5
 
5
6
  # Whole-screen theming for the app's seat-selection scaffold (background / app bar / status
@@ -0,0 +1,183 @@
1
+ module SpreeCmCommissioner
2
+ class ServiceFeeRule < SpreeCmCommissioner::Base
3
+ ALLOWED_RULEABLE_TYPES = %w[Spree::Product Spree::Taxon Spree::Vendor].freeze
4
+
5
+ include Spree::AdjustmentSource
6
+ include Spree::CalculatedAdjustments
7
+
8
+ # Spree::AdjustmentSource's own `has_many :adjustments, as: :source` has no class_name, so
9
+ # Rails infers it from the association name relative to *this* class's namespace
10
+ # (SpreeCmCommissioner::Adjustment), which doesn't exist. Redeclaring it here overrides that
11
+ # default — same fix SpreeCmCommissioner::PricingAction already needed for the same reason.
12
+ has_many :adjustments, as: :source, class_name: 'Spree::Adjustment'
13
+
14
+ belongs_to :ruleable, polymorphic: true, optional: true
15
+
16
+ # Only meaningful on a ruleable-less row (see the tenant_id absence validation below): a
17
+ # tenant's own default, distinct from the true platform-wide default (tenant_id AND ruleable
18
+ # both nil). Not used on Product/Vendor/Event-scoped rows — their tenant is already implied by
19
+ # the ruleable itself (a vendor belongs to exactly one tenant), so storing it there too would
20
+ # just be a second copy that could drift out of sync.
21
+ multi_tenant :tenant, class_name: 'SpreeCmCommissioner::Tenant'
22
+
23
+ before_validation :normalize_ruleable, :normalize_currency
24
+
25
+ validates :currency, presence: true
26
+ validates :ruleable_type, inclusion: { in: ALLOWED_RULEABLE_TYPES }, allow_nil: true
27
+ validates :ruleable_id, presence: true, if: -> { ruleable_type.present? }
28
+ validates :tenant_id, absence: true, if: -> { ruleable_type.present? }
29
+
30
+ # Only one active rule per (ruleable, tenant, currency). The global rule is a
31
+ # ruleable-less row, so ruleable_type / ruleable_id are both nil for it; tenant_id nil on
32
+ # that same row means "true platform-wide," not "unscoped."
33
+ validates :currency,
34
+ uniqueness: {
35
+ scope: %i[ruleable_type ruleable_id tenant_id active],
36
+ conditions: -> { where(active: true) },
37
+ case_sensitive: false,
38
+ message: lambda { |object, _data|
39
+ I18n.t('service_fee.rules.rule_already_exists_for_scope',
40
+ scope_type: object.ruleable_type_label, currency: object.currency
41
+ )
42
+ }
43
+ },
44
+ if: :active?
45
+
46
+ scope :active_rules, -> { where(active: true) }
47
+ scope :for_currency, -> (currency) { where(currency: currency.to_s.upcase) }
48
+
49
+ # Finds the active rule for the given ruleable (nil = global) and currency.
50
+ scope :active_for, lambda { |ruleable:, currency:|
51
+ active_rules
52
+ .for_currency(currency)
53
+ .where(ruleable_type: ruleable&.class&.base_class&.name, ruleable_id: ruleable&.id)
54
+ }
55
+
56
+ # Finds the ruleable-less rule for a specific tenant (nil tenant_id = true platform-wide).
57
+ scope :active_for_tenant, lambda { |tenant_id:, currency:|
58
+ active_rules
59
+ .for_currency(currency)
60
+ .where(ruleable_type: nil, ruleable_id: nil, tenant_id: tenant_id)
61
+ }
62
+
63
+ # Deletes all service-fee adjustments on these line items, then recreates them from
64
+ # whichever rule currently resolves for each one. Mirrors Spree::TaxRate.adjust: a plain
65
+ # destroy-then-create (not create_unique_adjustment, which only guards against duplicates
66
+ # and would leave a stale amount in place after a rule edit or a quantity change).
67
+ def self.adjust(order, line_items)
68
+ Spree::Adjustment.where(adjustable: line_items).service_fee.destroy_all
69
+
70
+ line_items.each do |line_item|
71
+ rule = resolve_for(line_item: line_item, currency: order.currency)
72
+ rule&.adjust(order, line_item)
73
+ end
74
+ end
75
+
76
+ # create_adjustment (from Spree::AdjustmentSource) is protected — callable with an explicit
77
+ # receiver only from inside another instance method of this class, not from the self.adjust
78
+ # class method above, where self is the class rather than an instance. This public wrapper is
79
+ # the same shape Spree::TaxRate#adjust uses for the identical reason.
80
+ def adjust(order, line_item)
81
+ create_adjustment(order, line_item)
82
+ end
83
+
84
+ # Walks product -> event -> vendor -> global, returning the first active rule that matches the
85
+ # currency. The product/event/vendor leg mirrors
86
+ # SpreeCmCommissioner::TicketTransfers::FeeCalculator's precedence.
87
+ #
88
+ # "Global" means two different things depending on whether the vendor belongs to a tenant:
89
+ # - No tenant (a base BookMe+ vendor): the true platform-wide default (tenant_id nil).
90
+ # - Has a tenant: ONLY that tenant's own default (tenant_id: vendor.tenant_id) — deliberately
91
+ # does NOT fall back to the platform-wide default if the tenant hasn't set one. Each
92
+ # white-label tenant is expected to configure their own fee (or explicitly have none); a
93
+ # tenant silently inheriting BookMe+'s own fee because they forgot to configure one would be
94
+ # a surprising, not a sensible, default.
95
+ def self.resolve_for(line_item:, currency:)
96
+ ruleables_for(line_item).each do |ruleable|
97
+ rule = active_for(ruleable: ruleable, currency: currency).first
98
+ return rule if rule.present?
99
+ end
100
+
101
+ active_for_tenant(tenant_id: vendor_for(line_item)&.tenant_id, currency: currency).first
102
+ end
103
+
104
+ def self.ruleables_for(line_item)
105
+ [line_item.product, line_item.event, vendor_for(line_item)]
106
+ end
107
+
108
+ def self.vendor_for(line_item)
109
+ product = line_item.product
110
+ product.respond_to?(:vendor) ? product.vendor : nil
111
+ end
112
+
113
+ # Spree calculators only expose a fixed, hardcoded set of calculable model types
114
+ # (Rails.application.config.spree.calculators is a plain Struct — tax_rates,
115
+ # shipping_methods, promotion_actions_*). There's no slot for a commissioner-owned
116
+ # model, so — same as SpreeCmCommissioner::PricingAction — we expose our own list
117
+ # instead of relying on the inherited `calculators` class method.
118
+ def self.available_calculator_types
119
+ [SpreeCmCommissioner::Calculators::ServiceFeePercent, SpreeCmCommissioner::Calculators::ServiceFeeFlatAmount]
120
+ end
121
+
122
+ def compute_amount(line_item)
123
+ calculator&.compute(line_item) || 0
124
+ end
125
+
126
+ # A short, human-readable summary for admin listings, e.g. "10%" or "$0.25" — so a rule's
127
+ # actual fee doesn't require opening it to see. Two calculator types (not one combined
128
+ # percent+flat calculator) means there's always exactly one number to show here.
129
+ def fee_description
130
+ case calculator
131
+ when SpreeCmCommissioner::Calculators::ServiceFeePercent
132
+ "#{calculator.preferred_percent.to_d.round(2).to_s('F')}%"
133
+ when SpreeCmCommissioner::Calculators::ServiceFeeFlatAmount
134
+ Spree::Money.new(calculator.preferred_flat_amount, currency: currency).to_s
135
+ when nil
136
+ Spree.t(:service_fee_rule_not_configured)
137
+ else
138
+ calculator.description
139
+ end
140
+ end
141
+
142
+ def scope_label
143
+ if ruleable.blank?
144
+ # A tenant-scoped row is that tenant's own "global" — it never falls back to the
145
+ # BookMe+-level one (see resolve_for), so it isn't really "global" in the broad sense;
146
+ # labeled as its own Tenant scope rather than reusing the word "Global" for both.
147
+ return Spree.t(:service_fee_rule_scope_tenant, name: tenant&.name || "##{tenant_id}") if tenant_id.present?
148
+
149
+ return Spree.t(:service_fee_rule_scope_global)
150
+ end
151
+
152
+ label_value = ruleable.respond_to?(:name) ? ruleable.name.to_s : "##{ruleable_id}"
153
+ "#{ruleable_type_label}: #{label_value}"
154
+ end
155
+
156
+ def ruleable_type_label
157
+ case ruleable_type
158
+ when 'Spree::Product' then Spree.t(:service_fee_rule_scope_product)
159
+ when 'Spree::Taxon' then Spree.t(:service_fee_rule_scope_event)
160
+ when 'Spree::Vendor' then Spree.t(:service_fee_rule_scope_vendor)
161
+ else ruleable_type&.demodulize || Spree.t(:service_fee_rule_scope_global)
162
+ end
163
+ end
164
+
165
+ protected
166
+
167
+ def label
168
+ 'Service Fee'
169
+ end
170
+
171
+ private
172
+
173
+ # "Global" dropdown sends "" not nil. Convert to nil so validations and uniqueness checks work.
174
+ def normalize_ruleable
175
+ self.ruleable_type = nil if ruleable_type.blank?
176
+ self.ruleable_id = nil if ruleable_type.nil?
177
+ end
178
+
179
+ def normalize_currency
180
+ self.currency = currency.to_s.upcase.presence
181
+ end
182
+ end
183
+ end
@@ -23,7 +23,7 @@ module SpreeCmCommissioner
23
23
  return true if variant.need_confirmation?
24
24
  return false if cached_inventory_items.empty?
25
25
 
26
- cached_inventory_items.all? { |item| item.active? && quantity_in_pool(item, include_locked: include_locked) >= quantity }
26
+ cached_inventory_items.all? { |item| item.sellable? && quantity_in_pool(item, include_locked: include_locked) >= quantity }
27
27
  end
28
28
 
29
29
  # The cache row carries both pools, so choosing between them is a read, not another Redis trip.
@@ -45,7 +45,7 @@ module SpreeCmCommissioner
45
45
  # stock, i.e. read sold out exactly when its allotment was still sellable.
46
46
  cached_inventory_items.all? do |item|
47
47
  quantity = line_item.locked_stock? ? item.quantity_locked : item.quantity_available
48
- item.active? && quantity >= line_item.quantity
48
+ item.sellable? && quantity >= line_item.quantity
49
49
  end
50
50
  end
51
51
  end
@@ -15,6 +15,10 @@ module SpreeCmCommissioner
15
15
  private
16
16
 
17
17
  def create_inventory_items
18
+ # Skip automatic inventory generation when variant has manual_generate_inventory_items set to true
19
+ # Use case: For variants where inventory items need to be manually created/managed (e.g., external integrations)
20
+ return if variant.manual_generate_inventory_items?
21
+
18
22
  SpreeCmCommissioner::InventoryItems::GenerateInventoryItemsJob.perform_later(variant_id: variant.id)
19
23
  end
20
24
 
@@ -2,6 +2,7 @@ module SpreeCmCommissioner
2
2
  class Trip < Base
3
3
  include SpreeCmCommissioner::StoreMetadata
4
4
  include SpreeCmCommissioner::RouteType
5
+ include SpreeCmCommissioner::Integrations::IntegrationMappable
5
6
  include SpreeCmCommissioner::ServiceOrigin
6
7
 
7
8
  attr_accessor :hours, :minutes, :seconds
@@ -38,7 +39,7 @@ module SpreeCmCommissioner
38
39
  after_commit :update_route_price_range, on: %i[create update]
39
40
 
40
41
  belongs_to :vendor, class_name: 'Spree::Vendor', inverse_of: :trips, optional: false
41
- belongs_to :product, class_name: 'Spree::Product', inverse_of: :trip, optional: false
42
+ belongs_to :product, class_name: 'Spree::Product', inverse_of: :trip, optional: true
42
43
 
43
44
  belongs_to :service_origin, class_name: 'Spree::Taxon', optional: true
44
45
 
@@ -1,5 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  class TripStop < Base
3
+ include SpreeCmCommissioner::Integrations::IntegrationMappable
4
+
3
5
  acts_as_list column: :sequence, scope: :trip_id
4
6
 
5
7
  belongs_to :trip, class_name: 'SpreeCmCommissioner::Trip', optional: false
@@ -59,6 +59,10 @@ module SpreeCmCommissioner
59
59
  base.store_public_metadata :primary_service_type, :string
60
60
  base.store_public_metadata :qr_data_version, :integer, default: 1
61
61
  base.store_public_metadata :qr_data_invalidated_at, :datetime
62
+ # Bumped on both mint and consume by SpreeCmCommissioner::CrossAppHandoffTokens — minting
63
+ # invalidates any earlier, not-yet-consumed token; consuming invalidates the token just used,
64
+ # so a replay within its TTL fails. See CrossAppHandoffTokens::Encode/Decode.
65
+ base.store_public_metadata :cross_app_handoff_version, :integer, default: 0
62
66
  base.store_public_metadata :branch_ids, :array, default: []
63
67
 
64
68
  # Why the block reason lives in metadata rather than its own column: it is display-only text
@@ -6,6 +6,7 @@ module SpreeCmCommissioner
6
6
  base.include SpreeCmCommissioner::VariantOptionsConcern
7
7
  base.include SpreeCmCommissioner::KycBitwise
8
8
  base.include SpreeCmCommissioner::Integrations::IntegrationMappable
9
+ base.include SpreeCmCommissioner::VariantMetadata
9
10
 
10
11
  base.after_commit :update_vendor_price
11
12
  base.validate :validate_option_types
@@ -80,6 +81,10 @@ module SpreeCmCommissioner
80
81
  delivery_option == 'delivery'
81
82
  end
82
83
 
84
+ def inventory_external_wins?
85
+ vendor&.integration&.inventory_external_wins?(self) || false
86
+ end
87
+
83
88
  def non_digital_ecommerce?
84
89
  !digital? && ecommerce?
85
90
  end
@@ -1,6 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  class Vehicle < Base
3
3
  include SpreeCmCommissioner::VehicleKind
4
+ include SpreeCmCommissioner::Integrations::IntegrationMappable
4
5
 
5
6
  belongs_to :vendor, class_name: 'Spree::Vendor'
6
7
  belongs_to :vehicle_type, class_name: 'SpreeCmCommissioner::VehicleType'
@@ -13,6 +14,8 @@ module SpreeCmCommissioner
13
14
  validates :name, presence: true
14
15
  validates :license_plate, uniqueness: true, allow_blank: true
15
16
 
17
+ has_one :seat_layout, through: :vehicle_type
18
+
16
19
  self.whitelisted_ransackable_attributes = %w[name license_plate kind]
17
20
 
18
21
  def display_name
@@ -2,6 +2,7 @@ module SpreeCmCommissioner
2
2
  class VehicleType < Base
3
3
  include SpreeCmCommissioner::StoreMetadata
4
4
  include SpreeCmCommissioner::VehicleKind
5
+ include SpreeCmCommissioner::Integrations::IntegrationMappable
5
6
 
6
7
  # This model has no seat_layout column (polymorphic association), so we store the preload_seat_layout_id ID in public_metadata.
7
8
  # This lets us check if a seat layout exists without triggering a database query.
@@ -33,6 +33,7 @@ module SpreeCmCommissioner
33
33
  base.has_many :nearby_places, -> { order(position: :asc) }, class_name: 'SpreeCmCommissioner::VendorPlace', dependent: :destroy
34
34
 
35
35
  base.has_many :stock_items, through: :variants, class_name: 'Spree::StockItem'
36
+ base.has_many :currency_rates, class_name: 'SpreeCmCommissioner::CurrencyRate', dependent: :destroy
36
37
 
37
38
  base.has_many :taxon_vendors, class_name: 'SpreeCmCommissioner::TaxonVendor'
38
39
  base.has_many :taxons, through: :taxon_vendors
@@ -104,6 +105,7 @@ module SpreeCmCommissioner
104
105
  base.has_many :boarding_trip_stops, through: :trips, class_name: 'SpreeCmCommissioner::TripStop', source: :boarding_trip_stops
105
106
  base.has_many :drop_off_trip_stops, through: :trips, class_name: 'SpreeCmCommissioner::TripStop', source: :drop_off_trip_stops
106
107
  base.has_many :pricing_models, class_name: 'SpreeCmCommissioner::PricingModel', foreign_key: :vendor_id, dependent: :destroy
108
+ base.has_many :orders, -> { distinct }, through: :variants, source: :orders
107
109
 
108
110
  base.has_many :agency_categories, -> { where(kind: :agency_category) }, class_name: 'Spree::Taxon', foreign_key: :vendor_id
109
111
 
@@ -248,6 +250,21 @@ module SpreeCmCommissioner
248
250
 
249
251
  delegate :present?, to: :tenant, prefix: true
250
252
 
253
+ # Returns the exchange rate between two currencies
254
+ # If no direct rate exists, calculates the inverse rate from the reverse pair
255
+ # Returns 1.0 for identical currencies, nil if no rate is found
256
+ def exchange_rate(from_currency, to_currency)
257
+ return 1.0 if from_currency == to_currency
258
+
259
+ pair = currency_rates.by_pair(from_currency, to_currency).first
260
+ return pair.rate if pair.present?
261
+
262
+ reverse_pair = currency_rates.by_pair(to_currency, from_currency).first
263
+ return (1.0 / reverse_pair.rate).round(6) if reverse_pair.present?
264
+
265
+ nil
266
+ end
267
+
251
268
  def validate_sellable_product_types
252
269
  valid_types = SpreeCmCommissioner::ProductType::PRODUCT_TYPES.map(&:to_s)
253
270
  return if sellable_product_types.all? { |type| valid_types.include?(type.to_s) }
@@ -1,5 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  class VendorPlace < Base
3
+ include SpreeCmCommissioner::Integrations::IntegrationMappable
4
+
3
5
  enum place_type: { venue: 0, branch: 1, stop: 2, location: 3 }
4
6
  acts_as_list scope: :vendor
5
7
 
@@ -0,0 +1,14 @@
1
+ <!-- insert_after "[data-hook='admin_orders_index_rows']" -->
2
+
3
+ <tr>
4
+ <td colspan="100" class="p-0 border-top-0">
5
+ <div class="collapse" id="line-items-collapse-<%= order.id %>">
6
+ <div class="mb-3 overflow-hidden border rounded-lg">
7
+ <%= turbo_frame_tag "order_#{order.id}_line_items",
8
+ src: line_items_admin_order_path(order), loading: 'lazy' do %>
9
+ <div class="p-3 text-center text-muted"><%= Spree.t(:loading) %>&hellip;</div>
10
+ <% end %>
11
+ </div>
12
+ </div>
13
+ </td>
14
+ </tr>
@@ -0,0 +1,17 @@
1
+ <!-- replace "[data-hook='admin_orders_index_row_actions']" -->
2
+
3
+ <td class='actions' data-hook="admin_orders_index_row_actions">
4
+ <span class="d-flex justify-content-end align-items-center">
5
+ <%= link_to_edit_url cart_admin_order_path(order), title: "admin_edit_#{dom_id(order)}", no_text: true if can?(:edit, order) %>
6
+
7
+ <%= link_to_with_icon 'credit-card.svg', Spree.t(:payments), admin_order_payments_path(order),
8
+ class: 'icon_link btn btn-light btn-sm ml-1', no_text: true if can?(:index, Spree::Payment) %>
9
+
10
+ <button type="button" data-toggle="collapse" data-target="#line-items-collapse-<%= order.id %>"
11
+ aria-expanded="false" aria-controls="line-items-collapse-<%= order.id %>"
12
+ class="pl-3 ml-1 btn btn-sm btn-light">
13
+ Line Items
14
+ <%= svg_icon(name: 'chevron-down.svg', classes: 'icon', width: 12, height: 12) %>
15
+ </button>
16
+ </span>
17
+ </td>
@@ -3,3 +3,8 @@
3
3
  <%= configurations_sidebar_menu_item(Spree.t(:icons), spree.admin_vectors_icons_path) if can? :manage, Spree::Product %>
4
4
  <%= configurations_sidebar_menu_item(Spree.t(:exports), spree.admin_exports_path) if can? :manage, SpreeCmCommissioner::Export %>
5
5
  <%= configurations_sidebar_menu_item(Spree.t(:imports), spree.admin_imports_path) if can? :manage, SpreeCmCommissioner::Import %>
6
+ <%= configurations_sidebar_menu_item(
7
+ Spree.t(:service_fee_rules),
8
+ spree.admin_service_fee_rules_path(status: 'active'),
9
+ { is_selected: controller.controller_name == 'service_fee_rules' }
10
+ ) if can? :manage, SpreeCmCommissioner::ServiceFeeRule %>
@@ -1,17 +1,16 @@
1
1
  module SpreeCmCommissioner
2
2
  class UsersByEventFetcherQuery
3
- attr_reader :taxon_id
3
+ attr_reader :event_id
4
4
 
5
- def initialize(taxon_id)
6
- @taxon_id = taxon_id
5
+ def initialize(event_id:)
6
+ @event_id = event_id
7
7
  end
8
8
 
9
+ # Fetches distinct users with a completed order containing a line item for the given event(s)
9
10
  def call
10
- classifications = Spree::Classification.where(taxon_id: @taxon_id)
11
- product_ids = classifications.pluck(:product_id)
12
- Spree::User.joins(orders: { line_items: :variant })
11
+ Spree::User.joins(orders: :line_items)
13
12
  .where(spree_orders: { state: 'complete' })
14
- .where(spree_variants: { product_id: product_ids })
13
+ .where(spree_line_items: { event_id: @event_id })
15
14
  .distinct
16
15
  end
17
16
  end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ class ItemAvailabilitySync
3
+ attr_reader :id, :last_sync_at
4
+
5
+ def initialize(id:, last_sync_at:)
6
+ @id = id
7
+ @last_sync_at = last_sync_at
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class ServiceFeeRuleSerializer < BaseSerializer
6
+ attribute :type_name do |rule|
7
+ rule.class.name.underscore
8
+ end
9
+
10
+ attribute :fee_description
11
+
12
+ has_one :calculator, if: proc { |rule| rule.respond_to?(:calculator) }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -2,7 +2,7 @@ module Spree
2
2
  module V2
3
3
  module Storefront
4
4
  class HomepageSectionSerializer < BaseSerializer
5
- attributes :id, :title, :segments, :description, :active, :position, :preview, :created_at, :updated_at
5
+ attributes :id, :title, :segments, :mini_apps, :description, :active, :position, :preview, :created_at, :updated_at
6
6
  has_many :homepage_section_relatables
7
7
  end
8
8
  end