spree_cm_commissioner 2.8.1.pre.pre.1 โ†’ 2.8.1.pre.3

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +12 -0
  3. data/Gemfile.lock +1 -1
  4. data/app/controllers/concerns/spree_cm_commissioner/order_concern.rb +1 -0
  5. data/app/controllers/spree/admin/classifications_controller.rb +1 -1
  6. data/app/controllers/spree/admin/featured_trips_controller.rb +135 -0
  7. data/app/controllers/spree/admin/inventory_holds_controller.rb +64 -0
  8. data/app/controllers/spree/admin/inventory_monitorings_controller.rb +7 -3
  9. data/app/controllers/spree/admin/oauth_applications_controller.rb +19 -0
  10. data/app/controllers/spree/admin/stock_managements_controller.rb +10 -1
  11. data/app/controllers/spree/api/v2/operator/recalculate_tickets_controller.rb +1 -1
  12. data/app/controllers/spree/api/v2/storefront/pricing_previews_controller.rb +54 -0
  13. data/app/controllers/spree/api/v2/storefront/transit/item_availabilities_controller.rb +66 -0
  14. data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +1 -0
  15. data/app/controllers/spree/api/v2/tenant/intercity_taxi/draft_orders_controller.rb +1 -0
  16. data/app/controllers/spree/api/v2/tenant/pricing_previews_controller.rb +60 -0
  17. data/app/controllers/spree/api/v2/tenant/votes_controller.rb +2 -2
  18. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +88 -0
  19. data/app/finders/spree_cm_commissioner/accommodations/find.rb +6 -5
  20. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +2 -2
  21. data/app/finders/spree_cm_commissioner/inventory_items/recently_changed_finder.rb +43 -33
  22. data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +11 -1
  23. data/app/jobs/spree_cm_commissioner/audit_event_job.rb +29 -0
  24. data/app/jobs/spree_cm_commissioner/inventory_holds/bulk_release_stale_job.rb +11 -0
  25. data/app/jobs/spree_cm_commissioner/inventory_holds/bulk_release_stale_payment_locked_job.rb +11 -0
  26. data/app/jobs/spree_cm_commissioner/inventory_holds/release_job.rb +29 -0
  27. data/app/jobs/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_on_hold_job.rb +20 -0
  28. data/app/models/concerns/spree_cm_commissioner/audit_wrapper.rb +139 -0
  29. data/app/models/concerns/spree_cm_commissioner/homepage_section_relatable_concern.rb +25 -0
  30. data/app/models/concerns/spree_cm_commissioner/integrations/inventory_sync_cachable.rb +9 -0
  31. data/app/models/concerns/spree_cm_commissioner/order_holdable.rb +71 -0
  32. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +13 -7
  33. data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +10 -1
  34. data/app/models/concerns/spree_cm_commissioner/variant_metadata.rb +24 -0
  35. data/app/models/concerns/spree_cm_commissioner/vehicle_kind.rb +1 -1
  36. data/app/models/spree_cm_commissioner/audit_event.rb +11 -0
  37. data/app/models/spree_cm_commissioner/currency_rate.rb +15 -0
  38. data/app/models/spree_cm_commissioner/homepage_background.rb +3 -0
  39. data/app/models/spree_cm_commissioner/homepage_section.rb +3 -0
  40. data/app/models/spree_cm_commissioner/integration.rb +31 -0
  41. data/app/models/spree_cm_commissioner/integrations/book_me_bus_v1.rb +56 -0
  42. data/app/models/spree_cm_commissioner/integrations/larryta.rb +81 -0
  43. data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +4 -10
  44. data/app/models/spree_cm_commissioner/integrations/vireak_buntham.rb +82 -0
  45. data/app/models/spree_cm_commissioner/inventory_hold.rb +30 -0
  46. data/app/models/spree_cm_commissioner/inventory_item.rb +14 -0
  47. data/app/models/spree_cm_commissioner/line_item_decorator.rb +10 -0
  48. data/app/models/spree_cm_commissioner/maintenance_tasks/cache_invalidation.rb +14 -0
  49. data/app/models/spree_cm_commissioner/maintenance_tasks/voting_session.rb +20 -5
  50. data/app/models/spree_cm_commissioner/menu_decorator.rb +3 -0
  51. data/app/models/spree_cm_commissioner/menu_item_decorator.rb +12 -0
  52. data/app/models/spree_cm_commissioner/order_decorator.rb +7 -6
  53. data/app/models/spree_cm_commissioner/payment_decorator.rb +12 -0
  54. data/app/models/spree_cm_commissioner/place.rb +2 -0
  55. data/app/models/spree_cm_commissioner/pricing_action.rb +2 -2
  56. data/app/models/spree_cm_commissioner/pricing_actions/create_guest_adjustments.rb +34 -42
  57. data/app/models/spree_cm_commissioner/pricing_actions/create_line_item_adjustments.rb +17 -10
  58. data/app/models/spree_cm_commissioner/pricing_actions/create_route_adjustments.rb +38 -39
  59. data/app/models/spree_cm_commissioner/pricing_model.rb +13 -15
  60. data/app/models/spree_cm_commissioner/pricing_rule.rb +1 -1
  61. data/app/models/spree_cm_commissioner/pricing_rules/age_group.rb +9 -12
  62. data/app/models/spree_cm_commissioner/pricing_rules/extra_drop_off_distance.rb +2 -2
  63. data/app/models/spree_cm_commissioner/pricing_rules/extra_pick_up_distance.rb +2 -2
  64. data/app/models/spree_cm_commissioner/pricing_rules/nationality.rb +18 -19
  65. data/app/models/spree_cm_commissioner/pricing_rules/nationality_group.rb +7 -16
  66. data/app/models/spree_cm_commissioner/product_decorator.rb +5 -4
  67. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +16 -9
  68. data/app/models/spree_cm_commissioner/reserved_block.rb +4 -0
  69. data/app/models/spree_cm_commissioner/route.rb +4 -0
  70. data/app/models/spree_cm_commissioner/seat_layout.rb +2 -0
  71. data/app/models/spree_cm_commissioner/show_contestant_video.rb +11 -0
  72. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +7 -0
  73. data/app/models/spree_cm_commissioner/taxon_decorator.rb +4 -3
  74. data/app/models/spree_cm_commissioner/tenant.rb +2 -0
  75. data/app/models/spree_cm_commissioner/trip.rb +9 -1
  76. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -0
  77. data/app/models/spree_cm_commissioner/variant_decorator.rb +4 -0
  78. data/app/models/spree_cm_commissioner/vehicle.rb +3 -0
  79. data/app/models/spree_cm_commissioner/vehicle_type.rb +1 -0
  80. data/app/models/spree_cm_commissioner/vendor_decorator.rb +21 -6
  81. data/app/models/spree_cm_commissioner/vendor_place.rb +2 -0
  82. data/app/models/spree_cm_commissioner/voting_contestant.rb +1 -1
  83. data/app/models/spree_cm_commissioner/voting_session.rb +31 -0
  84. data/app/overrides/spree/admin/oauth_applications/_form/tenant_fields.html.erb.deface +0 -6
  85. data/app/overrides/spree/admin/products/_form/enable_inventory_hold.html.erb.deface +18 -0
  86. data/app/overrides/spree/admin/shared/sub_menu/_promotion/featured_trips.html.erb.deface +6 -0
  87. data/app/overrides/spree/admin/shared/sub_menu/_stock/inventory_holds_tab.html.erb.deface +3 -0
  88. data/app/queries/spree_cm_commissioner/multi_leg_trips_query.rb +10 -1
  89. data/app/queries/spree_cm_commissioner/single_leg_trips_query.rb +19 -0
  90. data/app/request_schemas/spree_cm_commissioner/intercity_taxi_draft_order_update_schema.rb +1 -0
  91. data/app/serializables/spree_cm_commissioner/item_availability_sync.rb +10 -0
  92. data/app/serializers/spree/v2/tenant/preview_adjustment_serializer.rb +10 -0
  93. data/app/serializers/spree/v2/tenant/pricing_preview_serializer.rb +13 -0
  94. data/app/serializers/spree/v2/tenant/voting_session_serializer.rb +1 -1
  95. data/app/serializers/spree_cm_commissioner/v2/storefront/item_availability_sync_serializer.rb +10 -0
  96. data/app/serializers/spree_cm_commissioner/v2/storefront/preview_adjustment_serializer.rb +10 -0
  97. data/app/serializers/spree_cm_commissioner/v2/storefront/pricing_preview_serializer.rb +13 -0
  98. data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +26 -8
  99. data/app/services/spree_cm_commissioner/audit_logger.rb +59 -0
  100. data/app/services/spree_cm_commissioner/checkout/advance_decorator.rb +1 -1
  101. data/app/services/spree_cm_commissioner/checkout/update_decorator.rb +1 -1
  102. data/app/services/spree_cm_commissioner/currency_converter/create.rb +27 -0
  103. data/app/services/spree_cm_commissioner/currency_rates/manage.rb +99 -0
  104. data/app/services/spree_cm_commissioner/fraud_check.rb +15 -11
  105. data/app/services/spree_cm_commissioner/google_maps_geocoder.rb +41 -0
  106. data/app/services/spree_cm_commissioner/guests/update_seat.rb +55 -0
  107. data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +1 -2
  108. data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +11 -0
  109. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/client.rb +261 -0
  110. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/external_client/connection.rb +147 -0
  111. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_item_status.rb +52 -0
  112. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/external_inventory_items.rb +91 -0
  113. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/inventory/unstock_inventory.rb +140 -0
  114. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_inventory.rb +52 -0
  115. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_item_availability.rb +249 -0
  116. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_locations.rb +103 -0
  117. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_product.rb +66 -0
  118. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_route_stops.rb +113 -0
  119. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_routes.rb +128 -0
  120. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_seat_layout.rb +342 -0
  121. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_service_calendars.rb +59 -0
  122. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trip_stop.rb +179 -0
  123. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_trips.rb +597 -0
  124. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_variant.rb +58 -0
  125. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_vehicle_types.rb +87 -0
  126. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/time_parser.rb +19 -0
  127. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/base.rb +40 -0
  128. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/branch.rb +65 -0
  129. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/direction.rb +51 -0
  130. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/inventory_item_status.rb +148 -0
  131. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/location.rb +55 -0
  132. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/operator.rb +17 -0
  133. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/reservation_cart.rb +85 -0
  134. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat.rb +128 -0
  135. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat_layout.rb +363 -0
  136. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/stop.rb +84 -0
  137. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/trip.rb +199 -0
  138. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/wallet_payment.rb +40 -0
  139. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_manager.rb +22 -0
  140. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/full_sync_strategy.rb +117 -0
  141. data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/sync_strategies/incremental_sync_strategy.rb +64 -0
  142. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/client.rb +344 -0
  143. data/app/services/spree_cm_commissioner/integrations/larryta/external_client/connection.rb +74 -0
  144. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_item_status.rb +53 -0
  145. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/external_inventory_items.rb +106 -0
  146. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/hold_seats.rb +143 -0
  147. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/release_seats.rb +90 -0
  148. data/app/services/spree_cm_commissioner/integrations/larryta/inventory/unstock_inventory.rb +113 -0
  149. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_item_availability.rb +261 -0
  150. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_product.rb +72 -0
  151. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_routes.rb +142 -0
  152. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_seat_layout.rb +255 -0
  153. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_stock_item.rb +55 -0
  154. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trip_stop.rb +167 -0
  155. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_trips.rb +320 -0
  156. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_variant.rb +63 -0
  157. data/app/services/spree_cm_commissioner/integrations/larryta/polling/sync_vehicle_type.rb +79 -0
  158. data/app/services/spree_cm_commissioner/integrations/larryta/resources/base.rb +39 -0
  159. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking.rb +113 -0
  160. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_session.rb +43 -0
  161. data/app/services/spree_cm_commissioner/integrations/larryta/resources/booking_transaction.rb +236 -0
  162. data/app/services/spree_cm_commissioner/integrations/larryta/resources/direction.rb +71 -0
  163. data/app/services/spree_cm_commissioner/integrations/larryta/resources/passenger.rb +48 -0
  164. data/app/services/spree_cm_commissioner/integrations/larryta/resources/schedule.rb +150 -0
  165. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat.rb +31 -0
  166. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_availability.rb +49 -0
  167. data/app/services/spree_cm_commissioner/integrations/larryta/resources/seat_layout.rb +211 -0
  168. data/app/services/spree_cm_commissioner/integrations/larryta/sync_manager.rb +17 -0
  169. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/full_sync_strategy.rb +79 -0
  170. data/app/services/spree_cm_commissioner/integrations/larryta/sync_strategies/incremental_sync_strategy.rb +58 -0
  171. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +14 -15
  172. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +7 -4
  173. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +6 -3
  174. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +0 -3
  175. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +1 -3
  176. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +1 -3
  177. data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +1 -2
  178. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/client.rb +206 -0
  179. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/external_client/connection.rb +59 -0
  180. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_item_status.rb +48 -0
  181. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/external_inventory_items.rb +97 -0
  182. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/inventory/unstock_inventory.rb +208 -0
  183. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_item_availability.rb +270 -0
  184. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_locations.rb +96 -0
  185. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_nationality_pricing.rb +76 -0
  186. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_product.rb +69 -0
  187. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_routes.rb +110 -0
  188. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_seat_layout.rb +207 -0
  189. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_stock_item.rb +43 -0
  190. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trip_stop.rb +185 -0
  191. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_trips.rb +300 -0
  192. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_variant.rb +56 -0
  193. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/polling/sync_vehicle_type.rb +51 -0
  194. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/base.rb +32 -0
  195. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking.rb +48 -0
  196. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/booking_transaction.rb +46 -0
  197. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/cell.rb +53 -0
  198. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/confirm_booking_request.rb +43 -0
  199. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/location.rb +37 -0
  200. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/schedule.rb +138 -0
  201. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat.rb +48 -0
  202. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/resources/seat_layout.rb +79 -0
  203. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_manager.rb +17 -0
  204. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/full_sync_strategy.rb +87 -0
  205. data/app/services/spree_cm_commissioner/integrations/vireak_buntham/sync_strategies/incremental_sync_strategy.rb +53 -0
  206. data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +5 -2
  207. data/app/services/spree_cm_commissioner/inventory_holds/acquire.rb +175 -0
  208. data/app/services/spree_cm_commissioner/inventory_holds/bulk_release_stale.rb +41 -0
  209. data/app/services/spree_cm_commissioner/inventory_holds/bulk_release_stale_payment_locked.rb +11 -0
  210. data/app/services/spree_cm_commissioner/inventory_holds/convert.rb +86 -0
  211. data/app/services/spree_cm_commissioner/inventory_holds/release.rb +100 -0
  212. data/app/services/spree_cm_commissioner/inventory_holds/validate_limits.rb +65 -0
  213. data/app/services/spree_cm_commissioner/inventory_items/generate_permanent_item.rb +48 -0
  214. data/app/services/spree_cm_commissioner/inventory_items/reset.rb +23 -1
  215. data/app/services/spree_cm_commissioner/order_holds/hold.rb +88 -0
  216. data/app/services/spree_cm_commissioner/order_holds/lock_for_payment.rb +153 -0
  217. data/app/services/spree_cm_commissioner/order_holds/release.rb +73 -0
  218. data/app/services/spree_cm_commissioner/order_holds/reserve.rb +46 -0
  219. data/app/services/spree_cm_commissioner/place_resolver.rb +95 -0
  220. data/app/services/spree_cm_commissioner/pricing_models/activate.rb +41 -0
  221. data/app/services/spree_cm_commissioner/pricing_models/preview.rb +93 -0
  222. data/app/services/spree_cm_commissioner/reserved_blocks/hold.rb +4 -6
  223. data/app/services/spree_cm_commissioner/url_embed/youtube_embed.rb +11 -1
  224. data/app/services/spree_cm_commissioner/vendor_places/base.rb +2 -7
  225. data/app/services/spree_cm_commissioner/vendor_places/bulk_create.rb +3 -3
  226. data/app/services/spree_cm_commissioner/vote_counters/audit_counters.rb +15 -7
  227. data/app/services/spree_cm_commissioner/vote_counters/base.rb +4 -4
  228. data/app/services/spree_cm_commissioner/vote_counters/increment.rb +26 -16
  229. data/app/services/spree_cm_commissioner/vote_counters/per_contestant_counter.rb +50 -9
  230. data/app/services/spree_cm_commissioner/vote_counters/rebuild_from_db.rb +43 -14
  231. data/app/services/spree_cm_commissioner/vote_processor.rb +21 -10
  232. data/app/views/spree/admin/featured_trips/index.html.erb +130 -0
  233. data/app/views/spree/admin/integrations/_book_me_bus_v1_fields.html.erb +47 -0
  234. data/app/views/spree/admin/integrations/_form.html.erb +12 -0
  235. data/app/views/spree/admin/integrations/_larryta_fields.html.erb +58 -0
  236. data/app/views/spree/admin/integrations/_vireak_buntham_fields.html.erb +75 -0
  237. data/app/views/spree/admin/integrations/index.html.erb +1 -1
  238. data/app/views/spree/admin/inventory_holds/_search.html.erb +128 -0
  239. data/app/views/spree/admin/inventory_holds/index.html.erb +122 -0
  240. data/app/views/spree/admin/inventory_monitorings/index.html.erb +45 -24
  241. data/app/views/spree/admin/stock_managements/index.html.erb +32 -2
  242. data/app/views/spree/admin/tenants/_form.html.erb +11 -0
  243. data/config/initializers/spree_permitted_attributes.rb +1 -0
  244. data/config/locales/en.yml +22 -0
  245. data/config/locales/km.yml +22 -0
  246. data/config/routes.rb +12 -0
  247. data/db/migrate/20260327090000_create_cm_inventory_holds.rb +27 -0
  248. data/db/migrate/20260327090001_add_quantity_on_hold_to_cm_inventory_items.rb +5 -0
  249. data/db/migrate/20260327143200_create_cm_audit_events.rb +21 -0
  250. data/db/migrate/20260410045815_create_cm_currency_rates.rb +23 -0
  251. data/db/migrate/20260410080000_add_payment_locked_at_to_cm_reserved_blocks.rb +5 -0
  252. data/db/migrate/20260411102139_add_metadata_to_cm_inventory_items.rb +6 -0
  253. data/db/migrate/20260504100000_add_public_and_private_metadata_to_cm_routes.rb +6 -0
  254. data/db/migrate/20260506090000_remove_default_from_cm_guests_nationality_group.rb +6 -0
  255. data/db/migrate/20260508103605_add_sms_sender_id_to_cm_tenants.rb +6 -0
  256. data/db/migrate/20260511000000_add_featured_until_to_cm_trips.rb +6 -0
  257. data/db/migrate/20260520000001_optimize_cm_votes_indexes.rb +22 -0
  258. data/docs/tenant/test.com.md +88 -0
  259. data/lib/spree_cm_commissioner/cached_inventory_item.rb +10 -1
  260. data/lib/spree_cm_commissioner/pricing_models/guest_context.rb +81 -0
  261. data/lib/spree_cm_commissioner/pricing_models/line_item_context.rb +63 -0
  262. data/lib/spree_cm_commissioner/pricing_models/order_context.rb +62 -0
  263. data/lib/spree_cm_commissioner/pricing_models/preview_adjustment.rb +20 -0
  264. data/lib/spree_cm_commissioner/pricing_models/pricing_preview.rb +16 -0
  265. data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +11 -0
  266. data/lib/spree_cm_commissioner/test_helper/factories/inventory_hold_factory.rb +43 -0
  267. data/lib/spree_cm_commissioner/test_helper/factories/pricing_action_factory.rb +4 -0
  268. data/lib/spree_cm_commissioner/test_helper/factories/pricing_rule_group_factory.rb +1 -0
  269. data/lib/spree_cm_commissioner/version.rb +1 -1
  270. data/lib/spree_cm_commissioner.rb +5 -0
  271. metadata +168 -5
  272. data/app/interactors/spree_cm_commissioner/guest_seat_updater.rb +0 -45
  273. data/app/models/concerns/spree_cm_commissioner/order_seatable.rb +0 -81
  274. data/app/models/spree_cm_commissioner/pricing_model_handler/order.rb +0 -73
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19fd000fa15ceffed974595795de236e66b34973806086d138bc5867183bc9c0
4
- data.tar.gz: e9cc2d23feeb9fd6b66384c3b3e2febeadc51ecfac906a3e546613578db19156
3
+ metadata.gz: 8250d9a96dcceb058991c416ad4b91380fa42f5d43e301c366e74bfe9eb0e27b
4
+ data.tar.gz: a8c9301047c15f6ef760bdda1cc9087ac4f7a3539cd6aa86b43a99ab97fc6f16
5
5
  SHA512:
6
- metadata.gz: 87e417d02f3082ee1ec8905db14343bfe33309f2d7b4b04274c7abf2f86e22cb8998374bc34c64361632afdff581198a725d281c8c0f2b2bbe0ce91419a7a1cc
7
- data.tar.gz: 75e78fd7b59fc796318dc24a70483fb51ab8ade2dc6df5714bdf2c6266d2055d8438679e6edf6d5089d8382501f8dc52d0d8684334f4c73e6a92820db6c6f110
6
+ metadata.gz: 87bba18fe41a801abf62fc7a83c444452f5b3780b23edaa44bbf86b98271b06877bc8c52bddfc5761b5fdee5953226201c52556cfba30405c1e1f95af2067861
7
+ data.tar.gz: 2a7fd81827d6be81291146d17f46ff54a61a59aa4194a552f89ccc59463dc93a4ccc742c0534fd5c18354136314b2e9db571e97b6e8822fff3bbc6c116f3114d
data/.env.example CHANGED
@@ -50,3 +50,15 @@ EXPORT_PRESIGNED_URL_EXPIRATION_MINUTES=15
50
50
 
51
51
  # import order batch size
52
52
  IMPORT_ORDERS_BATCH_SIZE=50
53
+
54
+ # Inventory Hold System
55
+ # See: app/services/spree_cm_commissioner/order_holds/hold.rb
56
+ HOLD_DURATION_IN_MINUTES=8 # How long a hold is active after checkout begins (address step)
57
+
58
+ # See: app/services/spree_cm_commissioner/order_holds/lock_for_payment.rb
59
+ PAYMENT_LOCK_MIN_DURATION_IN_MINUTES=5 # Minimum remaining hold time required when entering the payment step; hold is extended if below this threshold
60
+
61
+ # See: app/services/spree_cm_commissioner/inventory_holds/validate_limits.rb
62
+ MAX_ACTIVE_HOLDS_PER_USER=3 # Max concurrent active holds allowed per user across all orders
63
+ MAX_HOLDS_PER_IP_PER_HOUR=5 # Max hold attempts per IP address within a rolling 1-hour window (abuse prevention)
64
+ HOLD_COOLDOWN_AFTER_EXPIRY_IN_MINUTES=2 # Cooldown period before a user can re-acquire a hold after one expired on them
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.8.1.pre.pre.1)
37
+ spree_cm_commissioner (2.8.1.pre.3)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -11,6 +11,7 @@ module SpreeCmCommissioner
11
11
  raise CanCan::AccessDenied
12
12
  end
13
13
 
14
+ @spree_current_order.last_ip_address = request.remote_ip
14
15
  @spree_current_order
15
16
  end
16
17
 
@@ -5,7 +5,7 @@ module Spree
5
5
 
6
6
  def recalculate_conversions
7
7
  if @taxon.parent.event?
8
- SpreeCmCommissioner::MaintenanceTasks::Event.pending.find_or_create_by(
8
+ SpreeCmCommissioner::MaintenanceTasks::Event.pending.create_or_find_by(
9
9
  maintainable_type: 'Spree::Taxon',
10
10
  maintainable_id: @taxon.parent.id
11
11
  ) do |task|
@@ -0,0 +1,135 @@
1
+ # rubocop:disable Rails/I18nLocaleTexts
2
+ module Spree
3
+ module Admin
4
+ class FeaturedTripsController < BaseController
5
+ before_action :require_taxon_manager
6
+
7
+ def index
8
+ @status = filter_status
9
+ @query = params[:q].to_s.strip
10
+ @vendor_id = params[:vendor_id].presence
11
+ @origin_place_id = params[:origin_place_id].presence
12
+ @destination_place_id = params[:destination_place_id].presence
13
+ @vendors = Spree::Vendor.order(:name)
14
+ @places = SpreeCmCommissioner::Place.order(:name)
15
+ @active_count = SpreeCmCommissioner::Trip.currently_featured.count
16
+ @expired_count = SpreeCmCommissioner::Trip.where.not(featured_until: nil).where('featured_until < ?', Time.current).count
17
+ @featured_trips = filtered_featured_trips
18
+ @available_trips = available_trips
19
+ end
20
+
21
+ def create
22
+ trip = SpreeCmCommissioner::Trip.find(params[:trip_id])
23
+ featured_until = parse_featured_until(params[:featured_until])
24
+
25
+ if featured_until.present? && featured_until > Time.current && trip.update(featured_until: featured_until)
26
+ flash[:success] = 'Trip was added to featured trips.'
27
+ else
28
+ flash[:error] = 'Featured until must be a future date and time.'
29
+ end
30
+
31
+ redirect_to spree.admin_featured_trips_path
32
+ end
33
+
34
+ def update
35
+ trip = SpreeCmCommissioner::Trip.find(params[:id])
36
+
37
+ if ActiveModel::Type::Boolean.new.cast(params[:remove])
38
+ trip.update(featured_until: nil)
39
+ flash[:success] = 'Trip was removed from featured trips.'
40
+ else
41
+ update_featured_until(trip)
42
+ end
43
+
44
+ redirect_to spree.admin_featured_trips_path(
45
+ status: params[:status],
46
+ vendor_id: params[:vendor_id],
47
+ origin_place_id: params[:origin_place_id],
48
+ destination_place_id: params[:destination_place_id],
49
+ q: params[:q]
50
+ )
51
+ end
52
+
53
+ private
54
+
55
+ def require_taxon_manager
56
+ return if can?(:manage, Spree::Taxon)
57
+
58
+ flash[:error] = 'You are not authorized to access this page.'
59
+ redirect_to spree.admin_promotions_path
60
+ end
61
+
62
+ def update_featured_until(trip)
63
+ featured_until = parse_featured_until(params[:featured_until])
64
+
65
+ if featured_until.present? && featured_until > Time.current && trip.update(featured_until: featured_until)
66
+ flash[:success] = 'Featured trip was updated.'
67
+ else
68
+ flash[:error] = 'Featured until must be a future date and time.'
69
+ end
70
+ end
71
+
72
+ def filtered_featured_trips
73
+ scope = SpreeCmCommissioner::Trip.includes(:vendor, :product, :origin_place, :destination_place, :vehicle_type)
74
+ .where.not(featured_until: nil)
75
+ scope = apply_status_filter(scope)
76
+ scope = apply_place_filter(scope)
77
+ scope = apply_vendor_filter(scope)
78
+ scope = apply_text_filter(scope)
79
+ scope.order(featured_until: :asc, departure_time: :asc).page(params[:page]).per(50)
80
+ end
81
+
82
+ def available_trips
83
+ scope = SpreeCmCommissioner::Trip.includes(:vendor, :product, :origin_place, :destination_place, :vehicle_type)
84
+ .not_currently_featured
85
+ scope = apply_place_filter(scope)
86
+ scope = apply_vendor_filter(scope)
87
+ scope = apply_text_filter(scope)
88
+ scope.order(departure_time: :desc).limit(200)
89
+ end
90
+
91
+ def apply_status_filter(scope)
92
+ case @status
93
+ when 'expired'
94
+ scope.where('cm_trips.featured_until < ?', Time.current)
95
+ when 'all'
96
+ scope
97
+ else
98
+ scope.currently_featured
99
+ end
100
+ end
101
+
102
+ def apply_vendor_filter(scope)
103
+ return scope if @vendor_id.blank?
104
+
105
+ scope.where(vendor_id: @vendor_id)
106
+ end
107
+
108
+ def apply_place_filter(scope)
109
+ scope = scope.where(origin_place_id: @origin_place_id) if @origin_place_id.present?
110
+ scope = scope.where(destination_place_id: @destination_place_id) if @destination_place_id.present?
111
+ scope
112
+ end
113
+
114
+ def apply_text_filter(scope)
115
+ return scope if @query.blank?
116
+
117
+ scope.left_joins(:product, :vendor)
118
+ .where('spree_products.name ILIKE :query OR spree_vendors.name ILIKE :query', query: "%#{@query}%")
119
+ end
120
+
121
+ def filter_status
122
+ %w[active expired all].include?(params[:status]) ? params[:status] : 'active'
123
+ end
124
+
125
+ def parse_featured_until(value)
126
+ return if value.blank?
127
+
128
+ Time.zone.parse(value.to_s)
129
+ rescue ArgumentError, TypeError
130
+ nil
131
+ end
132
+ end
133
+ end
134
+ end
135
+ # rubocop:enable Rails/I18nLocaleTexts
@@ -0,0 +1,64 @@
1
+ module Spree
2
+ module Admin
3
+ class InventoryHoldsController < BaseController
4
+ def index
5
+ authorize! :manage, SpreeCmCommissioner::InventoryHold
6
+
7
+ q = params.fetch(:q, {})
8
+ q = q.respond_to?(:to_unsafe_h) ? q.to_unsafe_h.deep_dup : q.deep_dup
9
+ q['s'] ||= 'created_at desc'
10
+ q['created_at_gt'] = parse_time_or_nil(q['created_at_gt'], :beginning_of_day) if q['created_at_gt'].present?
11
+ q['created_at_lt'] = parse_time_or_nil(q['created_at_lt'], :end_of_day) if q['created_at_lt'].present?
12
+
13
+ @search = scope.ransack(q)
14
+ result_scope = @search.result(distinct: true)
15
+
16
+ @holds = result_scope
17
+ .includes(order: [:user, { line_items: { variant: { product: :vendor } } }])
18
+ .page(params[:page])
19
+ .per(params[:per_page] || Spree::Backend::Config[:admin_orders_per_page])
20
+
21
+ @status_counts = result_scope.reorder(nil).group(:status).count
22
+ end
23
+
24
+ def release
25
+ authorize! :manage, SpreeCmCommissioner::InventoryHold
26
+
27
+ hold = SpreeCmCommissioner::InventoryHold.find(params[:id])
28
+
29
+ if hold.finalized?
30
+ flash[:error] = "Hold ##{hold.id} is already #{hold.status} and cannot be released."
31
+ return redirect_to admin_inventory_holds_path(back_params)
32
+ end
33
+
34
+ result = SpreeCmCommissioner::InventoryHolds::Release.call(hold: hold, reason: :user_canceled)
35
+
36
+ if result.success?
37
+ flash[:success] = "Hold ##{hold.id} for order #{hold.order.number} has been released."
38
+ else
39
+ flash[:error] = "Failed to release hold ##{hold.id}: #{result.error}"
40
+ end
41
+
42
+ redirect_to admin_inventory_holds_path(back_params)
43
+ end
44
+
45
+ private
46
+
47
+ def back_params
48
+ raw_params = params[:back_params].presence || params
49
+ ActionController::Parameters.new(raw_params).permit(:page, :per_page, q: {})
50
+ end
51
+
52
+ def scope
53
+ SpreeCmCommissioner::InventoryHold
54
+ .accessible_by(current_ability, :manage)
55
+ end
56
+
57
+ def parse_time_or_nil(value, boundary)
58
+ Time.zone.parse(value).public_send(boundary)
59
+ rescue StandardError
60
+ nil
61
+ end
62
+ end
63
+ end
64
+ end
@@ -5,19 +5,20 @@ module Spree
5
5
  authorize! :manage, SpreeCmCommissioner::InventoryItem
6
6
 
7
7
  @time_range = params[:time_range] || 7
8
- @filter_type = params[:filter_type] || 'all'
8
+ @product_type = params[:product_type].presence
9
9
  @vendor_id = params[:vendor_id]
10
10
 
11
11
  finder = SpreeCmCommissioner::InventoryItems::RecentlyChangedFinder.new(
12
12
  time_range: @time_range.to_i.days.ago,
13
13
  limit: 1000,
14
14
  vendor_id: @vendor_id,
15
- filter_type: @filter_type
15
+ product_type: @product_type
16
16
  )
17
17
 
18
18
  @inventory_items = finder.execute
19
19
  @total_count = @inventory_items.size
20
20
  @out_of_sync_count = @inventory_items.count { |item| item[:out_of_sync] }
21
+ @on_hold_out_of_sync_count = @inventory_items.count { |item| item[:on_hold_out_of_sync] }
21
22
  end
22
23
 
23
24
  def reset
@@ -32,7 +33,10 @@ module Spree
32
33
  flash[:error] = "Failed to reset inventory: #{result.message}"
33
34
  end
34
35
 
35
- redirect_to action: :index, time_range: params[:time_range], filter_type: params[:filter_type]
36
+ redirect_to action: :index,
37
+ time_range: params[:time_range],
38
+ product_type: params[:product_type],
39
+ vendor_id: params[:vendor_id]
36
40
  end
37
41
  end
38
42
  end
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ module Admin
3
+ class OauthApplicationsController < Spree::Admin::ResourceController
4
+ # Override default scopes behavior - only auto-assign 'admin' scope for new applications
5
+ # Edit forms allow users to modify scopes without forced default
6
+ before_action :set_default_scopes, only: [:new]
7
+
8
+ private
9
+
10
+ def create_turbo_stream_enabled?
11
+ true
12
+ end
13
+
14
+ def set_default_scopes
15
+ @object.scopes = 'admin' if @object.scopes.blank?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -5,7 +5,7 @@ module Spree
5
5
 
6
6
  before_action :load_parent
7
7
 
8
- helper_method :inventory_item_message
8
+ helper_method :inventory_item_message, :inventory_item_hold_message
9
9
 
10
10
  def load_parent
11
11
  @product = Spree::Product.find_by(slug: params[:product_id])
@@ -75,6 +75,15 @@ module Spree
75
75
  end
76
76
  end
77
77
 
78
+ def inventory_item_hold_message(inventory_item, cached_inventory_item)
79
+ synced = inventory_item.quantity_on_hold == cached_inventory_item.quantity_on_hold
80
+ if synced
81
+ "Synced: Quantity on hold matches in both DB and Redis (#{cached_inventory_item.quantity_on_hold})."
82
+ else
83
+ "Out of sync: Redis shows #{cached_inventory_item.quantity_on_hold} on hold, which doesn't match the database."
84
+ end
85
+ end
86
+
78
87
  private
79
88
 
80
89
  def load_inventories
@@ -7,7 +7,7 @@ module Spree
7
7
  before_action :load_taxon, only: :create
8
8
 
9
9
  def create
10
- SpreeCmCommissioner::MaintenanceTasks::Event.pending.find_or_create_by(
10
+ SpreeCmCommissioner::MaintenanceTasks::Event.pending.create_or_find_by(
11
11
  maintainable: @taxon
12
12
  ).async_execute
13
13
 
@@ -0,0 +1,54 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class PricingPreviewsController < ::Spree::Api::V2::BaseController
6
+ # GET /api/v2/tenant/pricing_previews
7
+ def index
8
+ permitted_preview_params = params.permit(SpreeCmCommissioner::PricingModels::OrderContext::PREVIEW_PERMITTED_PARAMS)
9
+ order_context = SpreeCmCommissioner::PricingModels::OrderContext.load!(permitted_preview_params[:order_context])
10
+ pricing_models_updated_at = SpreeCmCommissioner::PricingModel.joins(:vendor)
11
+ .where(spree_vendors: { tenant_id: nil })
12
+ .maximum(:updated_at)
13
+ cache_key = 'pricing:preview:v2:' \
14
+ "#{Digest::SHA256.hexdigest(permitted_preview_params.to_json)}:" \
15
+ "#{pricing_models_updated_at.to_i}"
16
+
17
+ cached_result = Rails.cache.fetch(cache_key, expires_in: 60.seconds) do
18
+ result = SpreeCmCommissioner::PricingModels::Preview.call(order_context: order_context)
19
+ if result.success?
20
+ {
21
+ success: true,
22
+ payload: serialize_resource(result.value[:pricing_preview]).as_json
23
+ }
24
+ else
25
+ {
26
+ success: false,
27
+ error: result.error.to_s
28
+ }
29
+ end
30
+ end
31
+
32
+ if cached_result[:success]
33
+ render_serialized_payload { cached_result[:payload] }
34
+ else
35
+ render_error_payload(cached_result[:error], 422)
36
+ end
37
+ rescue ArgumentError => e
38
+ render_error_payload(e.message, 422)
39
+ end
40
+
41
+ # override
42
+ def default_resource_includes
43
+ ['pricing_adjustments']
44
+ end
45
+
46
+ # override
47
+ def resource_serializer
48
+ SpreeCmCommissioner::V2::Storefront::PricingPreviewSerializer
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,66 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module Transit
6
+ class ItemAvailabilitiesController < ::Spree::Api::V2::ResourceController
7
+ include SpreeCmCommissioner::Integrations::InventorySyncCachable
8
+
9
+ def create
10
+ sync_result = sync_item_availability!
11
+
12
+ unless sync_result.success?
13
+ error_message = sync_result.to_h.dig(:error, :message) || 'Sync failed'
14
+ return render_error_payload(error_message, 422)
15
+ end
16
+
17
+ item_availability = SpreeCmCommissioner::ItemAvailabilitySync.new(
18
+ id: trip.id,
19
+ last_sync_at: sync_result.last_synced_at
20
+ )
21
+ render_serialized_payload { serialize_resource(item_availability) }
22
+ rescue ActiveRecord::RecordNotFound => e
23
+ render_error_payload(e.message, 404)
24
+ end
25
+
26
+ private
27
+
28
+ def trip
29
+ @trip ||= SpreeCmCommissioner::Trip.find(params[:trip_id])
30
+ end
31
+
32
+ def integration
33
+ @integration ||= trip.vendor&.integration
34
+ end
35
+
36
+ def sync_item_availability!
37
+ # Return success result if vendor has no integration
38
+ return SpreeCmCommissioner::Integrations::Base::SyncResult.new if integration.nil?
39
+
40
+ # Check cache first for successful results only
41
+ cached_result = Rails.cache.read(sync_cache_key)
42
+ return cached_result if cached_result
43
+
44
+ # Perform sync
45
+ result = integration.sync_item_availability!(trip_id: trip.id, on_date: params[:on_date])
46
+
47
+ # Only cache successful results to avoid sticky failures
48
+ Rails.cache.write(sync_cache_key, result, expires_in: sync_cache_ttl) if result.success?
49
+
50
+ result
51
+ end
52
+
53
+ def sync_cache_key
54
+ # Include trip.updated_at to auto-bust cache when trip is updated by background syncs
55
+ "item_availability_syncs:#{trip.id}:#{params[:on_date]}:#{trip.updated_at.to_i}"
56
+ end
57
+
58
+ def resource_serializer
59
+ SpreeCmCommissioner::V2::Storefront::ItemAvailabilitySyncSerializer
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -39,6 +39,7 @@ module Spree
39
39
  params[:date],
40
40
  params[:route_type],
41
41
  params[:vendor_id],
42
+ params[:featured],
42
43
  params[:number_of_guests],
43
44
  resource_includes&.sort&.join(','),
44
45
  sparse_fields&.sort&.join(','),
@@ -29,6 +29,7 @@ module Spree
29
29
  result = SpreeCmCommissioner::IntercityTaxiOrder::Update.call(
30
30
  order: spree_current_order,
31
31
  remark: params[:remark],
32
+ passenger_count: params[:passenger_count],
32
33
  pickup_map_place_attributes: params[:pickup_map_place_attributes]&.permit(
33
34
  :place_name,
34
35
  :lat,
@@ -0,0 +1,60 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class PricingPreviewsController < BaseController
6
+ # GET /api/v2/tenant/pricing_previews
7
+ def index
8
+ permitted_preview_params = params.permit(SpreeCmCommissioner::PricingModels::OrderContext::PREVIEW_PERMITTED_PARAMS)
9
+ order_context = SpreeCmCommissioner::PricingModels::OrderContext.load!(
10
+ permitted_preview_params[:order_context]&.merge(tenant_id: current_tenant.id)
11
+ )
12
+
13
+ pricing_models_updated_at = SpreeCmCommissioner::PricingModel.joins(:vendor)
14
+ .where(spree_vendors: { tenant_id: current_tenant.id })
15
+ .maximum(:updated_at)
16
+ cache_key = 'pricing:preview:v2:' \
17
+ "#{Digest::SHA256.hexdigest(permitted_preview_params.to_json)}:" \
18
+ "#{pricing_models_updated_at.to_i}"
19
+
20
+ cached_result = Rails.cache.fetch(cache_key, expires_in: 60.seconds) do
21
+ result = SpreeCmCommissioner::PricingModels::Preview.call(order_context: order_context)
22
+
23
+ if result.success?
24
+ {
25
+ success: true,
26
+ payload: serialize_resource(result.value[:pricing_preview]).as_json
27
+ }
28
+ else
29
+ {
30
+ success: false,
31
+ error: result.error.to_s
32
+ }
33
+ end
34
+ end
35
+
36
+ if cached_result[:success]
37
+ render_serialized_payload { cached_result[:payload] }
38
+ else
39
+ render_error_payload(cached_result[:error], 422)
40
+ end
41
+ rescue ArgumentError => e
42
+ render_error_payload(e.message, 422)
43
+ end
44
+
45
+ private
46
+
47
+ # override
48
+ def default_resource_includes
49
+ ['pricing_adjustments']
50
+ end
51
+
52
+ # override
53
+ def resource_serializer
54
+ Spree::V2::Tenant::PricingPreviewSerializer
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -23,13 +23,13 @@ module Spree
23
23
  vendor_id: current_vendor.id
24
24
  )
25
25
 
26
- result = SpreeCmCommissioner::VoteProcessor.new(
26
+ result = SpreeCmCommissioner::VoteProcessor.call(
27
27
  voting_session: voting_session,
28
28
  contestant: contestant,
29
29
  user: spree_current_user,
30
30
  params: processor_params,
31
31
  request: request
32
- ).call
32
+ )
33
33
 
34
34
  if result.success?
35
35
  render_serialized_payload(201) { serialize_resource(result.value) }
@@ -48,6 +48,8 @@ module SpreeCmCommissioner
48
48
  end
49
49
 
50
50
  def line_item_content(line_item)
51
+ return intercity_taxi_line_item_content(line_item) if intercity_taxi?(line_item)
52
+
51
53
  text = []
52
54
 
53
55
  text << bold(line_item.product.name.to_s)
@@ -82,6 +84,8 @@ module SpreeCmCommissioner
82
84
  text << "Email: #{inline_code(order.email)}" if order.email.present?
83
85
  text << "Delivery Address: #{formatted_shipping_address.presence || 'N/A'}" if order.delivery_required?
84
86
 
87
+ append_intercity_taxi_footer(text) if intercity_taxi_order?
88
+
85
89
  if show_details_link && order.guests.any?
86
90
  text << ''
87
91
  text << 'View Tickets:'
@@ -125,5 +129,89 @@ module SpreeCmCommissioner
125
129
 
126
130
  formatted_rows
127
131
  end
132
+
133
+ private
134
+
135
+ def intercity_taxi?(line_item)
136
+ trip_for(line_item)&.intercity_taxi?
137
+ end
138
+
139
+ def intercity_taxi_order?
140
+ selected_line_items.any? { |li| intercity_taxi?(li) }
141
+ end
142
+
143
+ def trip_for(line_item)
144
+ return nil unless line_item.respond_to?(:trip_id) && line_item.trip_id.present?
145
+
146
+ @trip_cache ||= {}
147
+ @trip_cache[line_item.trip_id] ||= SpreeCmCommissioner::Trip.find_by(id: line_item.trip_id)
148
+ end
149
+
150
+ def intercity_taxi_line_item_content(line_item)
151
+ text = []
152
+ route_text = taxi_route_text(line_item)
153
+ departure_text = taxi_departure_text(line_item)
154
+ text << bold('๐Ÿš— Trip Details')
155
+ text << "Route: #{route_text}" if route_text.present?
156
+ text << "Passengers: #{line_item.passenger_count || line_item.quantity}"
157
+ text << "Departure: #{departure_text}" if departure_text.present?
158
+
159
+ text.compact.join("\n")
160
+ end
161
+
162
+ def taxi_route_text(line_item)
163
+ trip = trip_for(line_item)
164
+ return line_item.product.name if trip.blank?
165
+
166
+ origin = trip.origin_place&.name
167
+ destination = trip.destination_place&.name
168
+ vehicle = trip.vehicle_type&.name
169
+
170
+ parts = [origin, destination].compact_blank.join(' โ†’ ')
171
+ vehicle.present? ? "#{parts} (#{vehicle})" : parts
172
+ end
173
+
174
+ def taxi_departure_text(line_item)
175
+ return nil unless line_item.date_present?
176
+
177
+ line_item.from_date.strftime('%b %d, %Y ยท %H:%M')
178
+ end
179
+
180
+ def nationality_text
181
+ values = order.saved_guests.map { |sg| sg.nationality&.name.presence || sg.nationality_group&.humanize }.compact.uniq
182
+ return nil if values.empty?
183
+
184
+ values.join(', ')
185
+ end
186
+
187
+ def append_intercity_taxi_footer(text)
188
+ text << "Nationality: #{nationality_text}" if nationality_text.present?
189
+
190
+ pickup = selected_line_items.filter_map(&:pickup_map_place).first
191
+ dropoff = selected_line_items.filter_map(&:dropoff_map_place).first
192
+
193
+ append_map_place(text, '๐Ÿ“ Pick-up Location', pickup) if pickup
194
+ append_map_place(text, '๐Ÿ“ Drop-off Location', dropoff) if dropoff
195
+
196
+ remark = selected_line_items.filter_map { |li| li.remark.presence }.first
197
+ return if remark.blank?
198
+
199
+ text << ''
200
+ text << bold('๐Ÿ—’๏ธ Notes to Driver')
201
+ text << ERB::Util.html_escape(remark)
202
+ end
203
+
204
+ def append_map_place(text, title, map_place)
205
+ text << ''
206
+ text << bold(title)
207
+ text << "Address: #{ERB::Util.html_escape(map_place.place_name)}" if map_place.place_name.present?
208
+ text << "Google Maps: #{ERB::Util.html_escape(google_maps_link(map_place))}" if google_maps_link(map_place).present?
209
+ end
210
+
211
+ def google_maps_link(map_place)
212
+ return nil if map_place.lat.blank? || map_place.lng.blank?
213
+
214
+ "https://www.google.com/maps/search/?api=1&query=#{map_place.lat},#{map_place.lng}"
215
+ end
128
216
  end
129
217
  end