spree_cm_commissioner 2.0.3.pre.pre.pre7 → 2.1.0.pre.pre1

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 (253) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +16 -2
  3. data/.gitignore +2 -1
  4. data/Gemfile.lock +39 -4
  5. data/Rakefile +33 -4
  6. data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +8 -11
  7. data/app/controllers/spree/admin/inventory_items_controller.rb +83 -0
  8. data/app/controllers/spree/admin/locations_controller.rb +62 -0
  9. data/app/controllers/spree/admin/stock_managements_controller.rb +63 -1
  10. data/app/controllers/spree/api/v2/platform/places_controller.rb +4 -1
  11. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +42 -0
  12. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +14 -31
  13. data/app/controllers/spree/api/v2/storefront/dynamic_fields_controller.rb +1 -1
  14. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +26 -18
  15. data/app/controllers/spree/api/v2/storefront/inventory_items_controller.rb +57 -0
  16. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  17. data/app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb +46 -0
  18. data/app/controllers/spree/api/v2/storefront/trip_places_controller.rb +29 -0
  19. data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +62 -0
  20. data/app/controllers/spree/api/v2/storefront/trips_controller.rb +24 -0
  21. data/app/finders/spree_cm_commissioner/accommodations/find.rb +37 -0
  22. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +32 -0
  23. data/app/interactors/spree_cm_commissioner/billing_address_creator.rb +33 -0
  24. data/app/interactors/spree_cm_commissioner/customer_notification_sender.rb +7 -2
  25. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +40 -0
  26. data/app/interactors/spree_cm_commissioner/fetch_facebook_user_data.rb +45 -0
  27. data/app/interactors/spree_cm_commissioner/google_places_fetcher.rb +1 -0
  28. data/app/interactors/spree_cm_commissioner/intercity_taxi_order_creator.rb +106 -0
  29. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +25 -0
  30. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +44 -0
  31. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +13 -0
  32. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +15 -0
  33. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +75 -0
  34. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +32 -0
  35. data/app/interactors/spree_cm_commissioner/taxi_trip_creator.rb +66 -0
  36. data/app/interactors/spree_cm_commissioner/transit/draft_order_creator.rb +136 -0
  37. data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +155 -0
  38. data/app/interactors/spree_cm_commissioner/user_fb_token_authenticator.rb +37 -0
  39. data/app/interactors/spree_cm_commissioner/user_fb_token_checker.rb +20 -0
  40. data/app/interactors/spree_cm_commissioner/user_identity_checker.rb +8 -8
  41. data/app/interactors/spree_cm_commissioner/user_registration_with_fb_token.rb +62 -0
  42. data/app/interactors/spree_cm_commissioner/variant_block_updater.rb +62 -0
  43. data/app/interactors/spree_cm_commissioner/vehicle_updater.rb +41 -0
  44. data/app/jobs/spree_cm_commissioner/application_job.rb +20 -0
  45. data/app/jobs/spree_cm_commissioner/application_unique_job.rb +20 -0
  46. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +7 -0
  47. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +7 -0
  48. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +11 -0
  49. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +11 -0
  50. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +9 -0
  51. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +18 -15
  52. data/app/models/concerns/spree_cm_commissioner/line_item_guests_concern.rb +29 -15
  53. data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +144 -0
  54. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +3 -12
  55. data/app/models/concerns/spree_cm_commissioner/order_seatable.rb +44 -0
  56. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +39 -0
  57. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +1 -3
  58. data/app/models/concerns/spree_cm_commissioner/product_type.rb +10 -0
  59. data/app/models/concerns/spree_cm_commissioner/route_type.rb +1 -1
  60. data/app/models/concerns/spree_cm_commissioner/user_identity.rb +12 -4
  61. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +1 -0
  62. data/app/models/concerns/spree_cm_commissioner/vehicle_type.rb +11 -0
  63. data/app/models/spree_cm_commissioner/address_decorator.rb +36 -2
  64. data/app/models/spree_cm_commissioner/block.rb +43 -0
  65. data/app/models/spree_cm_commissioner/customer_notification.rb +6 -1
  66. data/app/models/spree_cm_commissioner/dynamic_field.rb +2 -0
  67. data/app/models/spree_cm_commissioner/guest.rb +39 -0
  68. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +4 -0
  69. data/app/models/spree_cm_commissioner/inventory.rb +11 -0
  70. data/app/models/spree_cm_commissioner/inventory_item.rb +82 -0
  71. data/app/models/spree_cm_commissioner/line_item_decorator.rb +47 -78
  72. data/app/models/spree_cm_commissioner/notification_taxon.rb +1 -1
  73. data/app/models/spree_cm_commissioner/option_type_decorator.rb +1 -11
  74. data/app/models/spree_cm_commissioner/order/address_book_decorator.rb +30 -12
  75. data/app/models/spree_cm_commissioner/order_decorator.rb +38 -1
  76. data/app/models/spree_cm_commissioner/place.rb +3 -4
  77. data/app/models/spree_cm_commissioner/price_decorator.rb +9 -0
  78. data/app/models/spree_cm_commissioner/product_decorator.rb +13 -10
  79. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +41 -0
  80. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +126 -0
  81. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +36 -0
  82. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +25 -0
  83. data/app/models/spree_cm_commissioner/reserved_block.rb +31 -0
  84. data/app/models/spree_cm_commissioner/saved_guest.rb +21 -0
  85. data/app/models/spree_cm_commissioner/seat_layout.rb +20 -0
  86. data/app/models/spree_cm_commissioner/seat_section.rb +16 -0
  87. data/app/models/spree_cm_commissioner/seats/blocks_canceler.rb +30 -0
  88. data/app/models/spree_cm_commissioner/seats/blocks_holder.rb +53 -0
  89. data/app/models/spree_cm_commissioner/seats/blocks_reserver.rb +49 -0
  90. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_on_hold_by_other_guest.rb +4 -0
  91. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_other_guest.rb +4 -0
  92. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_same_guest.rb +4 -0
  93. data/app/models/spree_cm_commissioner/seats/errors/unable_to_save_reserved_block_record.rb +4 -0
  94. data/app/models/spree_cm_commissioner/service_calendar.rb +0 -2
  95. data/app/models/spree_cm_commissioner/state_decorator.rb +0 -1
  96. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +26 -25
  97. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +2 -1
  98. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  99. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +44 -0
  100. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +17 -0
  101. data/app/models/spree_cm_commissioner/taxon_decorator.rb +7 -1
  102. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +0 -6
  103. data/app/models/spree_cm_commissioner/trip.rb +19 -10
  104. data/app/models/spree_cm_commissioner/trip_connection.rb +5 -5
  105. data/app/models/spree_cm_commissioner/trip_stop.rb +10 -21
  106. data/app/models/spree_cm_commissioner/variant_block.rb +17 -0
  107. data/app/models/spree_cm_commissioner/variant_decorator.rb +37 -47
  108. data/app/models/spree_cm_commissioner/variant_options.rb +4 -23
  109. data/app/models/spree_cm_commissioner/vehicle.rb +12 -15
  110. data/app/models/spree_cm_commissioner/vendor_decorator.rb +14 -14
  111. data/app/models/spree_cm_commissioner/vendor_place.rb +13 -3
  112. data/app/overrides/spree/admin/shared/sub_menu/_configuration/locations_tab.html.erb.deface +5 -0
  113. data/app/queries/spree_cm_commissioner/trip_query.rb +110 -131
  114. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +3 -0
  115. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  116. data/app/request_schemas/spree_cm_commissioner/inventory_item_schema.rb +8 -0
  117. data/app/request_schemas/spree_cm_commissioner/trip_search_request_schema.rb +20 -0
  118. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +19 -0
  119. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +2 -0
  120. data/app/serializers/spree/v2/storefront/cart_serializer_decorator.rb +1 -0
  121. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +3 -1
  122. data/app/serializers/spree/v2/storefront/user_serializer_decorator.rb +1 -0
  123. data/app/serializers/spree_cm_commissioner/v2/storefront/amenity_serializer.rb +15 -0
  124. data/app/serializers/spree_cm_commissioner/v2/storefront/block_serializer.rb +11 -0
  125. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +5 -1
  126. data/app/serializers/spree_cm_commissioner/v2/storefront/inventory_item_serializer.rb +42 -0
  127. data/app/serializers/spree_cm_commissioner/v2/storefront/reserved_block_serializer.rb +9 -0
  128. data/app/serializers/spree_cm_commissioner/v2/storefront/saved_guest_serializer.rb +21 -0
  129. data/app/serializers/spree_cm_commissioner/v2/storefront/seat_layout_serializer.rb +12 -0
  130. data/app/serializers/spree_cm_commissioner/v2/storefront/seat_section_serializer.rb +11 -0
  131. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_place_serializer.rb +10 -0
  132. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_query_result_serializer.rb +14 -0
  133. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_result_serializer.rb +17 -0
  134. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_serializer.rb +20 -0
  135. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +11 -0
  136. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_variant_serializer.rb +12 -0
  137. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vehicle_serializer.rb +16 -0
  138. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vendor_serializer.rb +16 -0
  139. data/app/serializers/spree_cm_commissioner/v2/storefront/variant_block_serializer.rb +9 -0
  140. data/app/services/spree_cm_commissioner/transit/legs_builder_service.rb +46 -0
  141. data/app/services/spree_cm_commissioner/user_authenticator.rb +10 -1
  142. data/app/views/shared/_map.html.erb +5 -4
  143. data/app/views/spree/admin/customer_notifications/_form.html.erb +3 -3
  144. data/app/views/spree/admin/inventory_items/show.html.erb +72 -0
  145. data/app/views/spree/admin/locations/_form.html.erb +133 -0
  146. data/app/views/spree/admin/locations/edit.html.erb +11 -0
  147. data/app/views/spree/admin/locations/index.html.erb +67 -0
  148. data/app/views/spree/admin/locations/new.html.erb +11 -0
  149. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +7 -2
  150. data/app/views/spree/admin/stock_managements/calendar.html.erb +56 -0
  151. data/app/views/spree/admin/stock_managements/index.html.erb +55 -6
  152. data/config/initializers/paper_trail.rb +1 -0
  153. data/config/initializers/spree_permitted_attributes.rb +24 -4
  154. data/config/locales/en.yml +2 -0
  155. data/config/routes.rb +32 -2
  156. data/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb +3 -1
  157. data/db/migrate/20250122000000_move_route_type_from_vehicles_to_trips.rb +7 -0
  158. data/db/migrate/20250304293518_create_cm_inventory_items.rb +21 -0
  159. data/db/migrate/20250428092706_add_active_to_cm_customer_notifications.rb +7 -0
  160. data/db/migrate/20250428103550_remove_tenant_id_and_vendor_id_from_cm_customer_notifications.rb +9 -0
  161. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +5 -0
  162. data/db/migrate/20250502025848_add_index_to_spree_products.rb +5 -0
  163. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +5 -0
  164. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +5 -0
  165. data/db/migrate/20250603035256_add_inventory_item_to_spree_prices.rb +7 -0
  166. data/db/migrate/20250619073724_drop_table_cm_line_item_seats.rb +5 -0
  167. data/db/migrate/20250619073812_drop_table_cm_vehicle_seats.rb +5 -0
  168. data/db/migrate/20250619073844_drop_table_cm_vehicle_types.rb +9 -0
  169. data/db/migrate/20250619073957_drop_table_cm_option_value_vehicle_types.rb +5 -0
  170. data/db/migrate/20250619082354_remove_unnecessary_fields_from_cm_places.rb +9 -0
  171. data/db/migrate/20250619082736_remove_route_type_from_spree_products.rb +5 -0
  172. data/db/migrate/20250619083055_remove_unnecessary_fields_from_spree_taxons.rb +5 -0
  173. data/db/migrate/20250620083055_remove_variant_id_from_cm_trips.rb +5 -0
  174. data/db/migrate/20250620090000_update_cm_trip_connections_to_use_cm_trips.rb +6 -0
  175. data/db/migrate/20250620090001_create_cm_seat_layouts.rb +17 -0
  176. data/db/migrate/20250620090002_create_cm_seat_sections.rb +18 -0
  177. data/db/migrate/20250620090003_create_cm_blocks.rb +18 -0
  178. data/db/migrate/20250624091005_create_cm_reserved_blocks.rb +29 -0
  179. data/db/migrate/20250626083642_create_cm_variant_blocks.rb +24 -0
  180. data/db/migrate/20250627023314_add_block_id_to_cm_guests.rb +13 -0
  181. data/db/migrate/20250716022821_add_location_reference_to_cm_vendor_places.rb +5 -0
  182. data/db/migrate/20250716031743_drop_table_cm_vendor_stops.rb +5 -0
  183. data/db/migrate/20250717023824_add_vendor_reference_to_cm_trips.rb +5 -0
  184. data/db/migrate/20250717041414_add_location_place_reference_to_cm_trip_stops.rb +5 -0
  185. data/db/migrate/20250717042539_rename_cm_trip_stops_stop_id_column_to_stop_place_id.rb +7 -0
  186. data/db/migrate/20250717042707_rename_cm_trips_origin_and_destination_to_origin_place_and_destination_place.rb +11 -0
  187. data/db/migrate/20250721080738_add_vehicle_type_to_cm_vehicles.rb +7 -0
  188. data/db/migrate/20250725092713_add_block_type_to_cm_blocks.rb +5 -0
  189. data/db/migrate/20250731062816_add_departure_time_and_arrival_time_to_trip_stop.rb +6 -0
  190. data/db/migrate/20250807033035_create_spree_cm_commissioner_saved_guests.rb +23 -0
  191. data/db/migrate/20250807040000_generate_pickup_time_integer_option_values.rb +71 -0
  192. data/db/migrate/20250808054835_add_saved_guest_reference_to_cm_blocks.rb +5 -0
  193. data/docker-compose.yml +1 -1
  194. data/lib/cm_app_logger.rb +17 -1
  195. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +14 -11
  196. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_cm_commissioner → spree_dashboard/spree_cm_commissioner}/utilities.js +4 -0
  197. data/lib/spree_cm_commissioner/cached_inventory_item.rb +23 -0
  198. data/lib/spree_cm_commissioner/calendar_event.rb +11 -1
  199. data/lib/spree_cm_commissioner/test_helper/factories/block_factory.rb +16 -0
  200. data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +10 -0
  201. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  202. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +20 -0
  203. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  204. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +6 -30
  205. data/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb +0 -36
  206. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +18 -34
  207. data/lib/spree_cm_commissioner/test_helper/factories/reserved_block_factory.rb +27 -0
  208. data/lib/spree_cm_commissioner/test_helper/factories/saved_guest_factory.rb +8 -0
  209. data/lib/spree_cm_commissioner/test_helper/factories/seat_layout_factory.rb +11 -0
  210. data/lib/spree_cm_commissioner/test_helper/factories/seat_section_factory.rb +16 -0
  211. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  212. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +6 -0
  213. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +11 -3
  214. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +13 -0
  215. data/lib/spree_cm_commissioner/test_helper/factories/variant_block_factory.rb +7 -0
  216. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +41 -19
  217. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb +1 -1
  218. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +6 -1
  219. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +13 -1
  220. data/lib/spree_cm_commissioner/transit/leg.rb +37 -0
  221. data/lib/spree_cm_commissioner/transit/seat_selection.rb +27 -0
  222. data/lib/spree_cm_commissioner/trip_query_result.rb +50 -0
  223. data/lib/spree_cm_commissioner/trip_result.rb +19 -10
  224. data/lib/spree_cm_commissioner/version.rb +1 -1
  225. data/lib/spree_cm_commissioner.rb +37 -1
  226. data/lib/tasks/create_default_non_permanent_inventory_items.rake +16 -0
  227. data/lib/tasks/ensure_correct_product_type.rake +7 -0
  228. data/lib/tasks/generate_inventory_items.rake +7 -0
  229. data/spree_cm_commissioner.gemspec +7 -0
  230. metadata +195 -26
  231. data/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb +0 -44
  232. data/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb +0 -20
  233. data/app/models/spree_cm_commissioner/branch.rb +0 -12
  234. data/app/models/spree_cm_commissioner/line_item_seat.rb +0 -10
  235. data/app/models/spree_cm_commissioner/option_value_vehicle_type.rb +0 -8
  236. data/app/models/spree_cm_commissioner/stop.rb +0 -23
  237. data/app/models/spree_cm_commissioner/vehicle_seat.rb +0 -11
  238. data/app/models/spree_cm_commissioner/vehicle_type.rb +0 -76
  239. data/app/models/spree_cm_commissioner/vendor_stop.rb +0 -10
  240. data/app/queries/spree_cm_commissioner/trip_search_query.rb +0 -76
  241. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +0 -45
  242. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +0 -55
  243. data/app/queries/spree_cm_commissioner/vendor_stop_place_query.rb +0 -54
  244. data/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb +0 -11
  245. data/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb +0 -12
  246. data/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb +0 -8
  247. data/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb +0 -8
  248. data/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb +0 -7
  249. data/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb +0 -14
  250. data/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb +0 -8
  251. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb +0 -8
  252. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +0 -96
  253. data/lib/spree_cm_commissioner/trip_seat_layout_result.rb +0 -11
@@ -0,0 +1,62 @@
1
+ module SpreeCmCommissioner
2
+ class UserRegistrationWithFbToken < BaseInteractor
3
+ delegate :tenant_id, :provider, to: :context
4
+
5
+ def call
6
+ context.fail!(message: 'provider_missing') unless provider
7
+
8
+ ActiveRecord::Base.transaction do
9
+ register_user!(provider[:name], provider[:email])
10
+ link_user_account!(provider)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def register_user!(name, email)
17
+ user = Spree.user_class.new(
18
+ password: SecureRandom.base64(16),
19
+ email: email,
20
+ tenant_id: tenant_id,
21
+ **name_attributes(name)
22
+ )
23
+
24
+ # Flag as social network registration to skip presence validation for email, login, phone_number, and user_identity_providers
25
+ # Other validations (uniqueness, format) still run if present
26
+ user.registering_via_social_network = true
27
+
28
+ if user.save
29
+ context.user = user
30
+ else
31
+ context.fail!(message: user.errors.full_messages.join("\n"))
32
+ end
33
+ end
34
+
35
+ def name_attributes(name)
36
+ return {} if name.blank?
37
+
38
+ parts = name.strip.split
39
+ {
40
+ first_name: parts[0],
41
+ last_name: parts[1..].join(' ').presence
42
+ }.compact
43
+ end
44
+
45
+ def link_user_account!(provider)
46
+ user_identity_provider = SpreeCmCommissioner::UserIdentityProvider.where(
47
+ user_id: context.user.id,
48
+ identity_type: provider[:identity_type]
49
+ ).first_or_initialize
50
+
51
+ user_identity_provider.sub = provider[:sub]
52
+ user_identity_provider.email = provider[:email]
53
+ user_identity_provider.name = provider[:name]
54
+
55
+ if user_identity_provider.save
56
+ context.user_identity_provider = user_identity_provider
57
+ else
58
+ context.fail!(message: user_identity_provider.errors.full_messages.join("\n"))
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,62 @@
1
+ module SpreeCmCommissioner
2
+ class VariantBlockUpdater < BaseInteractor
3
+ delegate :variant, :data, :stock_location, :current_store, to: :context
4
+
5
+ def call
6
+ block_ids = data[:block_ids] || []
7
+
8
+ ActiveRecord::Base.transaction do
9
+ update_variant_block!(block_ids, variant)
10
+ update_stock_item!(variant)
11
+ end
12
+
13
+ context.message = 'Update variant blocks successfully'
14
+ rescue StandardError => e
15
+ context.fail!(message: "Update variant blocks Failed: #{e.message}")
16
+ end
17
+
18
+ private
19
+
20
+ def update_variant_block!(block_ids, variant)
21
+ variant.variant_blocks.where.not(block_id: block_ids).destroy_all
22
+
23
+ block_ids.each do |block_id|
24
+ next unless SpreeCmCommissioner::Block.exists?(id: block_id) # Skip invalid block_ids
25
+
26
+ block = SpreeCmCommissioner::VariantBlock.find_by(
27
+ block_id: block_id,
28
+ trip_id: variant.product.trip.id,
29
+ variant_id: variant.id
30
+ )
31
+
32
+ next unless block.nil?
33
+
34
+ SpreeCmCommissioner::VariantBlock.create!(
35
+ block_id: block_id,
36
+ trip_id: variant.product.trip.id,
37
+ variant_id: variant.id
38
+ )
39
+ end
40
+ end
41
+
42
+ def update_stock_item!(variant)
43
+ stock_items = variant.stock_items
44
+
45
+ context.fail!(message: 'Variant has more than one stock item. Please report to admin.') if stock_items.count > 1
46
+
47
+ stock_item = stock_items.first || variant.stock_items.create!(stock_location: stock_location)
48
+ quantity_difference = variant.variant_blocks.count - stock_item.count_on_hand
49
+
50
+ resource = SpreeCmCommissioner::Stock::StockMovementCreator.call(
51
+ variant_id: variant.id,
52
+ stock_location_id: stock_location.id,
53
+ current_store: current_store,
54
+ stock_movement_params: { quantity: quantity_difference }
55
+ )
56
+
57
+ return if resource.success?
58
+
59
+ context.fail!(message: "Stock update failed: #{resource.errors}")
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,41 @@
1
+ module SpreeCmCommissioner
2
+ class VehicleUpdater < BaseInteractor
3
+ def call
4
+ vehicle = context.vehicle
5
+ data = context.data
6
+
7
+ option_value_ids = data[:option_value_ids] || []
8
+ option_type_ids = data[:option_type_ids] || []
9
+
10
+ ActiveRecord::Base.transaction do
11
+ update_vehicle!(vehicle, data[:vehicle_attributes])
12
+ update_amenities!(vehicle, option_value_ids, option_type_ids)
13
+ end
14
+
15
+ context.message = I18n.t('views.transit.vehicles.update_success')
16
+ rescue StandardError => e
17
+ context.fail!(message: "Failed to update vehicle: #{e.message}")
18
+ end
19
+
20
+ private
21
+
22
+ def update_vehicle!(vehicle, attributes)
23
+ return if vehicle.update(attributes.slice(:code, :license_plate, :allow_seat_selection, :vehicle_type, :number_of_seats))
24
+
25
+ raise vehicle.errors.full_messages.to_sentence
26
+ end
27
+
28
+ def update_amenities!(vehicle, option_value_ids, option_type_ids)
29
+ vehicle.option_value_vehicles.where.not(option_value_id: option_value_ids).destroy_all
30
+ vehicle.vehicle_option_types.where.not(option_type_id: option_type_ids).destroy_all
31
+
32
+ option_value_ids.each do |option_value_id|
33
+ vehicle.option_value_vehicles.find_or_create_by(option_value_id: option_value_id)
34
+ end
35
+
36
+ option_type_ids.each do |option_type_id|
37
+ vehicle.vehicle_option_types.find_or_create_by(option_type_id: option_type_id)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,25 @@
1
1
  module SpreeCmCommissioner
2
2
  class ApplicationJob < ::ApplicationJob
3
3
  queue_as :default
4
+
5
+ around_perform :log_exceptions
6
+
7
+ private
8
+
9
+ def log_exceptions
10
+ yield
11
+ rescue StandardError => e
12
+ CmAppLogger.log(
13
+ label: "#{self.class.name} failed",
14
+ data: {
15
+ error_class: e.class.name,
16
+ error_message: e.message,
17
+ arguments: arguments.inspect,
18
+ backtrace: e.backtrace&.first(5)&.join("\n")
19
+ }
20
+ )
21
+
22
+ raise # Re-raise the exception to let Sidekiq handle retries or failure
23
+ end
4
24
  end
5
25
  end
@@ -2,5 +2,25 @@
2
2
  module SpreeCmCommissioner
3
3
  class ApplicationUniqueJob < ::ApplicationUniqueJob
4
4
  queue_as :default
5
+
6
+ around_perform :log_exceptions
7
+
8
+ private
9
+
10
+ def log_exceptions
11
+ yield
12
+ rescue StandardError => e
13
+ CmAppLogger.log(
14
+ label: "#{self.class.name} failed",
15
+ data: {
16
+ error_class: e.class.name,
17
+ error_message: e.message,
18
+ arguments: arguments.inspect,
19
+ backtrace: e.backtrace&.first(5)&.join("\n")
20
+ }
21
+ )
22
+
23
+ raise # Re-raise the exception to let Sidekiq handle retries or failure
24
+ end
5
25
  end
6
26
  end
@@ -0,0 +1,7 @@
1
+ module SpreeCmCommissioner
2
+ class EnsureCorrectProductTypeJob < ApplicationJob
3
+ def perform
4
+ SpreeCmCommissioner::EnsureCorrectProductType.call
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module SpreeCmCommissioner
2
+ class InventoryItemSyncerJob < ApplicationUniqueJob
3
+ def perform(inventory_id_and_quantities:)
4
+ InventoryItemSyncer.call(inventory_id_and_quantities:)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class InventoryItemsAdjusterJob < ApplicationUniqueJob
4
+ def perform(variant_id:, quantity:)
5
+ variant = Spree::Variant.find(variant_id)
6
+
7
+ SpreeCmCommissioner::Stock::InventoryItemsAdjuster.call(variant: variant, quantity: quantity)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class InventoryItemsGeneratorJob < ApplicationUniqueJob
4
+ def perform(variant_id:)
5
+ variant = Spree::Variant.find(variant_id)
6
+
7
+ SpreeCmCommissioner::Stock::InventoryItemsGenerator.call(variant: variant)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class PermanentInventoryItemsGeneratorJob < ApplicationUniqueJob
4
+ def perform
5
+ SpreeCmCommissioner::Stock::PermanentInventoryItemsGenerator.call
6
+ end
7
+ end
8
+ end
9
+ end
@@ -18,29 +18,32 @@ module SpreeCmCommissioner
18
18
  def date_unit
19
19
  return nil unless permanent_stock?
20
20
 
21
- date_range_excluding_checkout.size if accommodation?
21
+ date_range.size
22
22
  end
23
23
 
24
- def date_range_excluding_checkout
24
+ def date_range
25
25
  return [] unless date_present?
26
+ return [] unless permanent_stock?
26
27
 
27
- date_range = (from_date.to_date..to_date.to_date).to_a
28
- date_range.pop if date_range.size > 1
29
- date_range
30
- end
28
+ # For transit products (e.g., bus, train), only return the departure day,
29
+ # as the booking represents a single trip rather than a duration.
30
+ return [from_date.to_date] if transit?
31
31
 
32
- def date_range_including_checkout
33
- return [] unless date_present?
32
+ # For products with a stay or usage period (e.g., hotel, car rental, activities),
33
+ # return the full date range from start to end, inclusive.
34
+ return (from_date.to_date..to_date.to_date).to_a if service? || accommodation?
34
35
 
35
- (from_date.to_date..to_date.to_date).to_a
36
+ []
36
37
  end
37
38
 
38
- def date_range
39
- if accommodation?
40
- date_range_excluding_checkout
41
- else
42
- date_range_including_checkout
43
- end
39
+ def checkin_date
40
+ from_date&.to_datetime
41
+ end
42
+
43
+ def checkout_date
44
+ return to_date ? to_date.to_datetime + 1.day : nil if accommodation?
45
+
46
+ to_date&.to_datetime
44
47
  end
45
48
 
46
49
  private
@@ -1,13 +1,21 @@
1
+ # Number of guests are depend on the variant, but user is allowed to input custom number of guests
2
+ # which will be stored in public_metadata.
3
+ #
4
+ # metadata {
5
+ # 'number_of_adults': 2,
6
+ # 'number_of_kids': 1,
7
+ # }
1
8
  module SpreeCmCommissioner
2
9
  module LineItemGuestsConcern
3
10
  extend ActiveSupport::Concern
4
11
 
5
12
  included do
6
- validate :validate_total_adults, if: -> { public_metadata[:number_of_adults].present? }
7
- validate :validate_total_kids, if: -> { public_metadata[:number_of_kids].present? }
13
+ validate :validate_total_adults, if: -> { valid_integer_metadata?('number_of_adults') }
14
+ validate :validate_total_kids, if: -> { valid_integer_metadata?('number_of_kids') }
8
15
  end
9
16
 
10
- def remaining_total_guests = [number_of_guests - guests.size, 0].max
17
+ def number_of_adults = public_metadata['number_of_adults']&.to_i || (variant.number_of_adults * quantity)
18
+ def number_of_kids = public_metadata['number_of_kids']&.to_i || (variant.number_of_kids * quantity)
11
19
  def number_of_guests = number_of_adults + number_of_kids
12
20
 
13
21
  def allowed_extra_adults = variant.allowed_extra_adults * quantity
@@ -16,8 +24,7 @@ module SpreeCmCommissioner
16
24
  def allowed_total_adults = (variant.number_of_adults * quantity) + allowed_extra_adults
17
25
  def allowed_total_kids = (variant.number_of_kids * quantity) + allowed_extra_kids
18
26
 
19
- def number_of_adults = public_metadata[:number_of_adults] || (variant.number_of_adults * quantity)
20
- def number_of_kids = public_metadata[:number_of_kids] || (variant.number_of_kids * quantity)
27
+ def remaining_total_guests = [number_of_guests - guests.size, 0].max
21
28
 
22
29
  def generate_remaining_guests
23
30
  return if remaining_total_guests.zero?
@@ -28,15 +35,17 @@ module SpreeCmCommissioner
28
35
  end
29
36
 
30
37
  def extra_adults
38
+ return 0 unless valid_integer_metadata?('number_of_adults')
31
39
  return 0 unless extra_adults?
32
40
 
33
- public_metadata[:number_of_adults] - (variant.number_of_adults * quantity)
41
+ public_metadata['number_of_adults'].to_i - (variant.number_of_adults * quantity)
34
42
  end
35
43
 
36
44
  def extra_kids
45
+ return 0 unless valid_integer_metadata?('number_of_kids')
37
46
  return 0 unless extra_kids?
38
47
 
39
- public_metadata[:number_of_kids] - (variant.number_of_kids * quantity)
48
+ public_metadata['number_of_kids'].to_i - (variant.number_of_kids * quantity)
40
49
  end
41
50
 
42
51
  def guest_options
@@ -55,25 +64,30 @@ module SpreeCmCommissioner
55
64
  end
56
65
 
57
66
  def extra_adults?
58
- public_metadata[:number_of_adults].present? && public_metadata[:number_of_adults] > variant.number_of_adults * quantity
67
+ return false unless valid_integer_metadata?('number_of_adults')
68
+
69
+ public_metadata['number_of_adults'].to_i > variant.number_of_adults * quantity
59
70
  end
60
71
 
61
72
  def extra_kids?
62
- public_metadata[:number_of_kids].present? && public_metadata[:number_of_kids] > variant.number_of_kids * quantity
73
+ return false unless valid_integer_metadata?('number_of_kids')
74
+
75
+ public_metadata['number_of_kids'].to_i > variant.number_of_kids * quantity
63
76
  end
64
77
 
65
78
  private
66
79
 
67
- def validate_total_adults
68
- return if public_metadata[:number_of_adults] <= allowed_total_adults
80
+ def valid_integer_metadata?(key)
81
+ value = public_metadata[key.to_s]
82
+ value.present? && value.to_s.match?(/\A-?\d+\z/)
83
+ end
69
84
 
70
- errors.add(:quantity, 'exceed_total_adults')
85
+ def validate_total_adults
86
+ errors.add(:quantity, 'exceed_total_adults') if public_metadata['number_of_adults'].to_i > allowed_total_adults
71
87
  end
72
88
 
73
89
  def validate_total_kids
74
- return if public_metadata[:number_of_kids] <= allowed_total_kids
75
-
76
- errors.add(:quantity, 'exceed_total_kids')
90
+ errors.add(:quantity, 'exceed_total_kids') if public_metadata['number_of_kids'].to_i > allowed_total_kids
77
91
  end
78
92
  end
79
93
  end
@@ -0,0 +1,144 @@
1
+ # This concern allow line item to have trip-related metadata.
2
+ #
3
+ # Metadata structure:
4
+ # {
5
+ # 'direction': 'outbound' or 'inbound',
6
+ # 'trip_id': 123,
7
+ # 'boarding_trip_stop_id': 456,
8
+ # 'drop_off_trip_stop_id': 789
9
+ # }
10
+ #
11
+ # Direction:
12
+ # - "outbound": The trip is moving away from the origin (e.g., A → B → C → D).
13
+ # This term is more flexible than "departure" and applies to both direct and connected trips.
14
+ # - "inbound": The trip is returning toward the origin (e.g., D → C → B → A).
15
+ # This term is more flexible than "return" and applies to both direct and connected trips.
16
+ module SpreeCmCommissioner
17
+ module LineItemTransitable
18
+ extend ActiveSupport::Concern
19
+
20
+ DIRECTION = %w[outbound inbound].freeze
21
+
22
+ TripKey = Struct.new(:trip_id, :direction, :boarding_trip_stop_id, :drop_off_trip_stop_id)
23
+
24
+ included do
25
+ validates :direction, inclusion: { in: DIRECTION }, allow_nil: true
26
+ validates :trip_id, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
27
+ validates :boarding_trip_stop_id, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
28
+ validates :drop_off_trip_stop_id, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
29
+
30
+ validates :pickup_lat, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_nil: true
31
+ validates :pickup_lng, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_nil: true
32
+ validates :drop_off_lat, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_nil: true
33
+ validates :drop_off_lng, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_nil: true
34
+
35
+ validates :passenger_count, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
36
+
37
+ # Groups LineItems by trip fields. Some fields can be null,
38
+ # so use it with caution. Output:
39
+ # {
40
+ # $<TripKey:> => [<LineItem id:1>]
41
+ # $<TripKey:> => [<LineItem id:2>]
42
+ # $<TripKey:> => [<LineItem id:3>, <LineItem id:4>]
43
+ # }
44
+ def self.group_by_trip_key
45
+ all.group_by do |line_item|
46
+ TripKey.new(
47
+ line_item.trip_id,
48
+ line_item.direction,
49
+ line_item.boarding_trip_stop_id,
50
+ line_item.drop_off_trip_stop_id
51
+ )
52
+ end
53
+ end
54
+ end
55
+
56
+ def outbound? = direction == 'outbound'
57
+ def inbound? = direction == 'inbound'
58
+
59
+ def direction = private_metadata['direction']
60
+ def trip_id = private_metadata['trip_id']&.to_i
61
+ def boarding_trip_stop_id = private_metadata['boarding_trip_stop_id']&.to_i
62
+ def drop_off_trip_stop_id = private_metadata['drop_off_trip_stop_id']&.to_i
63
+
64
+ def pickup_place_name = public_metadata['pickup_place_name']
65
+ def drop_off_place_name = public_metadata['drop_off_place_name']
66
+ def pickup_lat = public_metadata['pickup_lat']&.to_f
67
+ def pickup_lng = public_metadata['pickup_lng']&.to_f
68
+ def drop_off_lat = public_metadata['drop_off_lat']&.to_f
69
+ def drop_off_lng = public_metadata['drop_off_lng']&.to_f
70
+ def passenger_count = public_metadata['passenger_count']&.to_i
71
+ def pickup_oob_confirmed = private_metadata['pickup_oob_confirmed']
72
+ def drop_off_oob_confirmed = private_metadata['drop_off_oob_confirmed']
73
+
74
+ def direction=(value)
75
+ set_private_metadata_value('direction', value)
76
+ end
77
+
78
+ def trip_id=(value)
79
+ set_private_metadata_value('trip_id', value)
80
+ end
81
+
82
+ def boarding_trip_stop_id=(value)
83
+ set_private_metadata_value('boarding_trip_stop_id', value)
84
+ end
85
+
86
+ def drop_off_trip_stop_id=(value)
87
+ set_private_metadata_value('drop_off_trip_stop_id', value)
88
+ end
89
+
90
+ def pickup_oob_confirmed=(value)
91
+ set_private_metadata_value('pickup_oob_confirmed', value)
92
+ end
93
+
94
+ def drop_off_oob_confirmed=(value)
95
+ set_private_metadata_value('drop_off_oob_confirmed', value)
96
+ end
97
+
98
+ def pickup_place_name=(value)
99
+ set_public_metadata_value('pickup_place_name', value)
100
+ end
101
+
102
+ def drop_off_place_name=(value)
103
+ set_public_metadata_value('drop_off_place_name', value)
104
+ end
105
+
106
+ def pickup_lat=(value)
107
+ set_public_metadata_value('pickup_lat', value)
108
+ end
109
+
110
+ def pickup_lng=(value)
111
+ set_public_metadata_value('pickup_lng', value)
112
+ end
113
+
114
+ def drop_off_lat=(value)
115
+ set_public_metadata_value('drop_off_lat', value)
116
+ end
117
+
118
+ def drop_off_lng=(value)
119
+ set_public_metadata_value('drop_off_lng', value)
120
+ end
121
+
122
+ def passenger_count=(value)
123
+ set_public_metadata_value('passenger_count', value)
124
+ end
125
+
126
+ private
127
+
128
+ def set_public_metadata_value(key, value)
129
+ if value.nil?
130
+ public_metadata.delete(key)
131
+ else
132
+ public_metadata[key] = value.to_s
133
+ end
134
+ end
135
+
136
+ def set_private_metadata_value(key, value)
137
+ if value.nil?
138
+ private_metadata.delete(key)
139
+ else
140
+ private_metadata[key] = value.to_s
141
+ end
142
+ end
143
+ end
144
+ end
@@ -14,13 +14,6 @@ module SpreeCmCommissioner
14
14
  state_selection
15
15
  payment_option
16
16
  delivery_option
17
- amenity
18
- departure_time
19
- duration
20
- vehicle_id
21
- origin
22
- destination
23
- place_id
24
17
  ].freeze
25
18
 
26
19
  RESERVED_OPTIONS = {
@@ -48,11 +41,9 @@ module SpreeCmCommissioner
48
41
  'bib-display-prefix' => 'boolean',
49
42
  'bib-pre-generation-on-create' => 'boolean',
50
43
  'seat-number-positions' => 'array',
51
- 'origin' => 'place_id',
52
- 'destination' => 'place_id',
53
- 'departure-time' => 'time',
54
- 'vehicle' => 'vehicle_id',
55
- 'allow-seat-selection' => 'boolean'
44
+ 'seat-type' => 'string',
45
+ 'intercity-taxi' => 'string',
46
+ 'pickup-time' => 'integer'
56
47
  }.freeze
57
48
 
58
49
  included do
@@ -0,0 +1,44 @@
1
+ module SpreeCmCommissioner
2
+ module OrderSeatable
3
+ def should_manage_blocks?
4
+ preload_block_ids.any?
5
+ end
6
+
7
+ def hold_blocks!
8
+ return unless should_manage_blocks?
9
+
10
+ CmAppLogger.log(label: "#{self.class.name}#hold_blocks!", data: { order_id: id }) do
11
+ SpreeCmCommissioner::Seats::BlocksHolder.new(line_item_ids: line_item_ids, hold_by: user).hold_blocks!
12
+ end
13
+ end
14
+
15
+ def cancel_blocks!
16
+ return unless should_manage_blocks?
17
+
18
+ CmAppLogger.log(label: "#{self.class.name}#cancel_blocks!", data: { order_id: id }) do
19
+ SpreeCmCommissioner::Seats::BlocksCanceler.new(order_id: id, cancel_by: user).cancel_blocks!
20
+ end
21
+ end
22
+
23
+ def reserve_blocks!
24
+ return unless should_manage_blocks?
25
+
26
+ CmAppLogger.log(label: "#{self.class.name}#reserve_blocks!", data: { order_id: id }) do
27
+ SpreeCmCommissioner::Seats::BlocksReserver.new(line_item_ids: line_item_ids, reserve_by: user).reserve_blocks!
28
+ end
29
+ end
30
+
31
+ # Calling `.block_ids` directly can cause many slow database queries (N+1 problem)
32
+ # every time `.should_manage_blocks?` or `.preload_block_ids` runs.
33
+ # To avoid this, we store a precomputed list of block IDs in `private_metadata`.
34
+ # This list is updated whenever a guest’s block is saved or destroy.
35
+ def preload_block_ids=(preload_block_ids = [])
36
+ self.private_metadata ||= {}
37
+ self.private_metadata['preload_block_ids'] = preload_block_ids
38
+ end
39
+
40
+ def preload_block_ids
41
+ self.private_metadata&.fetch('preload_block_ids', []) || []
42
+ end
43
+ end
44
+ end