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,44 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class InventoryItemResetter < BaseInteractor
4
+ delegate :inventory_item, to: :context
5
+
6
+ def call
7
+ max_capacity = variant_total_inventory
8
+ total_purchases = variant_total_purchases
9
+ quantity_available = [max_capacity - total_purchases, 0].max
10
+
11
+ updated = inventory_item.update(max_capacity: max_capacity, quantity_available: quantity_available)
12
+ return context.fail!(message: 'Failed to update inventory item', errors: inventory_item.errors.full_messages) unless updated
13
+
14
+ clear_inventory_cache
15
+ end
16
+
17
+ def variant_total_inventory
18
+ # for shipment, total_on_hand is not orignal stock. shipment does subtract the stock.
19
+ # to get desire result, we need to add to total_purchase.
20
+ if inventory_item.variant.delivery_required?
21
+ inventory_item.variant.total_on_hand.to_i + variant_total_purchases
22
+ else
23
+ inventory_item.variant.total_on_hand.to_i
24
+ end
25
+ end
26
+
27
+ def variant_total_purchases
28
+ scope = inventory_item.variant.complete_line_items
29
+
30
+ if inventory_item.permanent_stock?
31
+ scope.where('? BETWEEN from_date AND to_date', inventory_item.inventory_date).sum(:quantity).to_i
32
+ else
33
+ scope.sum(:quantity).to_i
34
+ end
35
+ end
36
+
37
+ def clear_inventory_cache
38
+ SpreeCmCommissioner.redis_pool.with do |redis|
39
+ redis.del(inventory_item.redis_key)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class InventoryItemsAdjuster < BaseInteractor
4
+ delegate :variant, :quantity, to: :context
5
+
6
+ def call
7
+ variant.inventory_items.active.find_each do |inventory_item|
8
+ inventory_item.adjust_quantity!(quantity)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class InventoryItemsGenerator < BaseInteractor
4
+ delegate :variant, to: :context
5
+
6
+ def call
7
+ if variant.permanent_stock?
8
+ SpreeCmCommissioner::Stock::PermanentInventoryItemsGenerator.call(variant_ids: [variant.id])
9
+ else
10
+ variant.create_default_non_permanent_inventory_item! unless variant.default_inventory_item_exist?
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,75 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class PermanentInventoryItemsGenerator < BaseInteractor
4
+ delegate :variant_ids, to: :context
5
+
6
+ def variants_per_batch = 1000
7
+
8
+ def pre_inventory_days_for(variant)
9
+ context.pre_inventory_days || variant.pre_inventory_days
10
+ end
11
+
12
+ def call
13
+ variants.in_batches(of: variants_per_batch) do |batch|
14
+ generate_inventory_items_for_batch(batch)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def generate_inventory_items_for_batch(batch)
21
+ total_on_hand_by_variant = total_on_hand_for(batch)
22
+ batch.each do |variant|
23
+ count_on_hand = total_on_hand_by_variant[variant.id] || 0
24
+ generate_inventory_items_for_variant(variant, count_on_hand)
25
+ end
26
+ end
27
+
28
+ def generate_inventory_items_for_variant(variant, count_on_hand)
29
+ inventory_dates_for(variant).each do |inventory_date|
30
+ next if inventory_exist?(variant, inventory_date)
31
+
32
+ create_inventory_item(variant, inventory_date, count_on_hand)
33
+ end
34
+ end
35
+
36
+ def inventory_dates_for(variant)
37
+ start_date = Time.zone.tomorrow
38
+ end_date = Time.zone.today + pre_inventory_days_for(variant)
39
+
40
+ (start_date..end_date)
41
+ end
42
+
43
+ def inventory_exist?(variant, inventory_date)
44
+ variant.inventory_items.exists?(inventory_date: inventory_date)
45
+ end
46
+
47
+ def create_inventory_item(variant, inventory_date, count_on_hand)
48
+ variant.inventory_items.create!(
49
+ inventory_date: inventory_date,
50
+ quantity_available: count_on_hand,
51
+ max_capacity: count_on_hand,
52
+ product_type: variant.product_type
53
+ )
54
+ end
55
+
56
+ # Returns a hash: { variant_id => total_on_hand, ... }
57
+ def total_on_hand_for(variants)
58
+ variant_ids = variants.pluck(:id)
59
+
60
+ Spree::StockItem
61
+ .joins(:stock_location)
62
+ .where(deleted_at: nil, variant_id: variant_ids)
63
+ .where(spree_stock_locations: { active: true })
64
+ .group(:variant_id)
65
+ .sum(:count_on_hand)
66
+ end
67
+
68
+ def variants
69
+ scope = Spree::Variant.active.with_permanent_stock.where(is_master: false)
70
+ scope = scope.where(id: variant_ids) if variant_ids.present?
71
+ scope
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,32 @@
1
+ module SpreeCmCommissioner
2
+ module Stock
3
+ class StockMovementCreator < BaseInteractor
4
+ delegate :variant_id, :stock_location_id, :current_store, :stock_movement_params, to: :context
5
+
6
+ def call
7
+ variant = current_store.variants.find(variant_id)
8
+
9
+ return context.fail!(message: Spree.t(:doesnt_track_inventory)) unless variant.track_inventory?
10
+
11
+ stock_location = Spree::StockLocation.find(stock_location_id)
12
+ stock_movement = stock_location.stock_movements.build(stock_movement_params)
13
+ stock_movement.stock_item = stock_location.set_up_stock_item(variant)
14
+
15
+ if stock_movement.save
16
+ adjust_inventory_items_async(variant_id, stock_movement.quantity)
17
+ context.stock_movement = stock_movement
18
+ else
19
+ context.fail!(message: stock_movement.errors.full_messages.join(', '))
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def adjust_inventory_items_async(variant_id, quantity)
26
+ CmAppLogger.log(label: "#{self.class.name}#adjust_inventory_items_async", data: { variant_id: variant_id, quantity: quantity }) do
27
+ SpreeCmCommissioner::Stock::InventoryItemsAdjusterJob.perform_later(variant_id: variant_id, quantity: quantity)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,66 @@
1
+ module SpreeCmCommissioner
2
+ class TaxiTripCreator < BaseInteractor
3
+ delegate :trip_params, to: :context
4
+
5
+ def call
6
+ ActiveRecord::Base.transaction do
7
+ create_trip_with_product
8
+ create_variant
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def create_trip_with_product
15
+ trip = SpreeCmCommissioner::Trip.new(trip_params)
16
+
17
+ if trip.save
18
+ context.trip = trip
19
+ context.product = trip.product
20
+ assign_option_type_to_product
21
+ context.product.save
22
+ else
23
+ context.fail!(error: "Failed to create taxi trip: #{trip.errors.full_messages.join(', ')}")
24
+ end
25
+ end
26
+
27
+ def create_variant
28
+ variant = context.product.variants.new(
29
+ price: trip_params['product_attributes']['price'],
30
+ sku: "TRIP-#{context.formatted_name}-#{Time.current.to_i}",
31
+ option_value_ids: option_value_ids
32
+ )
33
+
34
+ if variant.save
35
+ context.variant = variant
36
+ create_stock_item_for_variant(variant)
37
+ else
38
+ context.fail!(error: "Failed to create taxi trip variant: #{variant.errors.full_messages.join(', ')}")
39
+ end
40
+ end
41
+
42
+ def create_stock_item_for_variant(variant)
43
+ stock_location = context.product.vendor.stock_location
44
+ if stock_location.present?
45
+ Spree::StockItem.find_or_create_by!(variant: variant, stock_location: stock_location) do |item|
46
+ item.count_on_hand = 1
47
+ end
48
+ else
49
+ context.fail!(error: 'Vendor does not have an active stock location.')
50
+ end
51
+ end
52
+
53
+ def assign_option_type_to_product
54
+ option_type = Spree::OptionType.find_by(name: 'intercity-taxi')
55
+ return unless option_type
56
+
57
+ context.product.option_types << option_type
58
+ end
59
+
60
+ def option_value_ids
61
+ context.product.option_types.flat_map do |option_type|
62
+ option_type.option_values.pluck(:id)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,136 @@
1
+ # DraftOrderCreator creates a new order for a entire journey, including outbound and (if present) inbound directions.
2
+ #
3
+ # Attributes:
4
+ # - outbound_date: [Date] The date of outbound transit
5
+ # - inbound_date: [Date] The date of inbound transit
6
+ # - outbound_legs: [Array<SpreeCmCommissioner::Transit::Leg>] Outbound transit legs (one for direct, multiple for connecting)
7
+ # - inbound_legs: [Array<SpreeCmCommissioner::Transit::Leg>] Inbound transit legs (one for direct, multiple for connecting)
8
+ # - user: [User] The user for whom the order is being created
9
+ #
10
+ # Each line item is built with precise from_date and to_date based on legs and trip stop times.
11
+ # Each line item has guests built corresponding to seat selection blocks.
12
+ #
13
+ # Note: inbound_legs and inbound_date are optional; the order may represent a one-way (outbound only) journey.
14
+ module SpreeCmCommissioner
15
+ module Transit
16
+ class DraftOrderCreator < BaseInteractor
17
+ delegate :outbound_date,
18
+ :inbound_date,
19
+ :outbound_legs,
20
+ :inbound_legs,
21
+ :user, to: :context
22
+
23
+ def call
24
+ return context.fail!(message: 'Outbound legs are missing') if outbound_legs.blank?
25
+
26
+ begin
27
+ context.order = create_order!
28
+ rescue StandardError => e
29
+ context.fail!(message: e.message)
30
+ end
31
+ end
32
+
33
+ def create_order!
34
+ order = Spree::Order.new(state: 'cart', use_billing: true, user: user)
35
+
36
+ outbound_line_items = build_line_items_for_legs!(order: order, legs: outbound_legs, initial_date: outbound_date)
37
+ inbound_line_items = inbound_legs.blank? ? [] : build_line_items_for_legs!(order: order, legs: inbound_legs, initial_date: inbound_date)
38
+
39
+ outbound_qty = outbound_line_items.sum(&:quantity)
40
+ inbound_qty = inbound_line_items.sum(&:quantity)
41
+
42
+ raise StandardError, 'Outbound & inbound line item quantities do not match' if inbound_line_items.any? && outbound_qty != inbound_qty
43
+ raise StandardError, order.errors.full_messages.to_sentence unless order.save
44
+
45
+ order.update_with_updater!
46
+ order
47
+ end
48
+
49
+ # Build line items for each leg, using the previous leg's to_date as the next leg's date,
50
+ # or the initial date for the first leg.
51
+ def build_line_items_for_legs!(order:, legs:, initial_date:)
52
+ all_line_items = []
53
+ current_leg_date = initial_date
54
+
55
+ legs.each do |leg|
56
+ leg_line_items = build_line_items_for!(leg, order, current_leg_date)
57
+ leg_line_items = insert_saved_guests_per_line_items_leg(leg_line_items)
58
+
59
+ all_line_items.concat(leg_line_items)
60
+ current_leg_date = leg_line_items.last.to_date.to_date if leg_line_items.any?
61
+ end
62
+
63
+ all_line_items
64
+ end
65
+
66
+ def build_line_items_for!(leg, order, date)
67
+ trip = SpreeCmCommissioner::Trip.find(leg.trip_id)
68
+ trip_stops = trip.trip_stops.where(id: [leg.boarding_trip_stop_id, leg.drop_off_trip_stop_id]).index_by(&:id)
69
+
70
+ from_date, to_date = calculate_line_item_duration!(
71
+ date: date,
72
+ departure_time: trip_stops[leg.boarding_trip_stop_id].departure_time,
73
+ arrival_time: trip_stops[leg.drop_off_trip_stop_id].arrival_time
74
+ )
75
+
76
+ leg.seat_selections.group_by(&:variant_id).map do |variant_id, seat_selections|
77
+ line_item = order.line_items.new(
78
+ product_type: :transit,
79
+ from_date: from_date,
80
+ to_date: to_date,
81
+ variant_id: variant_id,
82
+ quantity: seat_selections.sum(&:quantity),
83
+ private_metadata: {
84
+ direction: leg.direction,
85
+ trip_id: leg.trip_id.to_s,
86
+ boarding_trip_stop_id: leg.boarding_trip_stop_id.to_s,
87
+ drop_off_trip_stop_id: leg.drop_off_trip_stop_id.to_s
88
+ }
89
+ )
90
+
91
+ build_guests_for!(line_item, seat_selections)
92
+ end
93
+ end
94
+
95
+ def build_guests_for!(line_item, seat_selections)
96
+ block_ids = seat_selections.flat_map(&:block_ids)
97
+
98
+ if block_ids.any? && block_ids.size != line_item.quantity
99
+ raise StandardError, "Number of blocks (#{block_ids.size}) does not match quantity (#{line_item.quantity})"
100
+ end
101
+
102
+ Array.new(line_item.quantity) do |index|
103
+ line_item.guests.new(block_id: block_ids[index])
104
+ end
105
+
106
+ line_item
107
+ end
108
+
109
+ def insert_saved_guests_per_line_items_leg(line_items)
110
+ line_items.flat_map(&:guests).each_with_index do |guest, index|
111
+ context.saved_guests ||= []
112
+ context.saved_guests << SpreeCmCommissioner::SavedGuest.new if context.saved_guests[index].blank?
113
+
114
+ guest.saved_guest = context.saved_guests[index]
115
+ end
116
+
117
+ line_items
118
+ end
119
+
120
+ def calculate_line_item_duration!(date:, departure_time:, arrival_time:)
121
+ raise StandardError, 'Departure or arrival time in trip stop is missing' if departure_time.blank? || arrival_time.blank?
122
+ raise StandardError, 'Arrival time cannot be before departure time' if arrival_time < departure_time
123
+
124
+ trip_duration_in_seconds = arrival_time - departure_time
125
+
126
+ # The input `date` is in local time (e.g., 10-10-2022).
127
+ # Combine it with the departure_time in Rails default timezone so that
128
+ # the resulting from_date stays on the intended local calendar day when stored in UTC.
129
+ from_date = Time.zone.local(date.year, date.month, date.day, departure_time.hour, departure_time.min, departure_time.sec)
130
+ to_date = from_date + trip_duration_in_seconds.seconds
131
+
132
+ [from_date, to_date]
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,155 @@
1
+ module SpreeCmCommissioner
2
+ class TripCloneCreator < BaseInteractor
3
+ delegate :vendor, :params, :original_trip, :places, :current_store, :current_vendor, to: :context
4
+
5
+ def call
6
+ ActiveRecord::Base.transaction do
7
+ cloned_product = create_cloned_product(original_trip.product)
8
+ cloned_trip = create_cloned_trip(original_trip, cloned_product)
9
+
10
+ departure_time_from_stops(cloned_trip)
11
+ calculate_trip_duration(cloned_trip)
12
+
13
+ cloned_trip.save!
14
+ context.cloned_trip = cloned_trip
15
+ clone_variants(original_trip, cloned_trip)
16
+ end
17
+ rescue StandardError => e
18
+ context.fail!(message: e.message)
19
+ end
20
+
21
+ private
22
+
23
+ # Product cloning
24
+ def create_cloned_product(original_product)
25
+ return nil unless original_product
26
+
27
+ cloned_product = duplicate_product(original_product)
28
+ assign_vendor_attributes(cloned_product)
29
+
30
+ cloned_product.save!
31
+ cloned_product
32
+ end
33
+
34
+ def duplicate_product(original_product)
35
+ cloned = original_product.dup
36
+ %i[variants product_properties classifications option_types].each do |assoc|
37
+ cloned.public_send("#{assoc}=", [])
38
+ end
39
+ cloned
40
+ end
41
+
42
+ def assign_vendor_attributes(cloned_product)
43
+ cloned_product.vendor_id = vendor.id
44
+ cloned_product.store_ids = [current_store.id]
45
+ cloned_product.product_type = :transit
46
+ cloned_product.status = :active
47
+ cloned_product.price = original_trip.product.price if original_trip.product.price.present?
48
+ cloned_product.option_types = original_trip.product.option_types if original_trip.product.option_types.any?
49
+
50
+ return unless params[:product_attributes]
51
+
52
+ product_attributes = params[:product_attributes].dup
53
+ product_attributes[:name] ||= original_trip.product.name.to_s
54
+ cloned_product.assign_attributes(product_attributes)
55
+ end
56
+
57
+ # Trip cloning
58
+ def create_cloned_trip(original_trip, cloned_product)
59
+ cloned_trip = build_cloned_trip(original_trip, cloned_product)
60
+ cloned_trip.trip_stops = clone_trip_stops(original_trip)
61
+ cloned_trip
62
+ end
63
+
64
+ def build_cloned_trip(original_trip, cloned_product)
65
+ cloned = original_trip.dup
66
+ cloned.product = cloned_product
67
+ cloned.vendor_id = vendor.id
68
+ cloned.assign_attributes(params.except(:product_attributes, :trip_stops_attributes))
69
+ cloned
70
+ end
71
+
72
+ def clone_trip_stops(original_trip)
73
+ original_trip.trip_stops.map.with_index do |stop, index|
74
+ clone_trip_stop(stop, index)
75
+ end
76
+ end
77
+
78
+ def clone_trip_stop(stop, index)
79
+ cloned_stop = stop.dup
80
+ cloned_stop.id = nil
81
+ cloned_stop.trip_id = nil
82
+
83
+ apply_trip_stop_params(cloned_stop, index)
84
+ validate_trip_stop!(cloned_stop)
85
+
86
+ cloned_stop
87
+ end
88
+
89
+ def apply_trip_stop_params(cloned_stop, index)
90
+ stop_params = params.dig(:trip_stops_attributes, index.to_s)
91
+ return unless stop_params
92
+
93
+ cloned_stop.departure_time = parse_time(stop_params[:departure_time])
94
+ cloned_stop.arrival_time = parse_time(stop_params[:arrival_time])
95
+ cloned_stop.stop_type = stop_params[:stop_type].presence
96
+ cloned_stop.location_place_id = stop_params[:location_place_id].presence
97
+ end
98
+
99
+ def parse_time(value)
100
+ Time.zone.parse(value) if value.present?
101
+ end
102
+
103
+ def validate_trip_stop!(cloned_stop)
104
+ return if cloned_stop.departure_time.present?
105
+
106
+ raise StandardError, "Departure time can't be blank for stop #{cloned_stop.stop_name}"
107
+ end
108
+
109
+ # Clone Variants
110
+ def clone_variants(original_trip, cloned_trip)
111
+ original_trip.product.variants.each do |variant|
112
+ new_variant = cloned_trip.product.variants.create!(
113
+ price: variant.price,
114
+ option_value_ids: variant.option_value_ids,
115
+ sku: ''
116
+ )
117
+ clone_variant_blocks(variant, new_variant)
118
+ end
119
+ rescue StandardError => e
120
+ raise StandardError, "Failed to clone variants: #{e.message}"
121
+ end
122
+
123
+ # clone variant_block (seat)
124
+ def clone_variant_blocks(origin_variant, new_variant)
125
+ block_data = {
126
+ block_ids: origin_variant.block_ids || []
127
+ }
128
+
129
+ context = SpreeCmCommissioner::VariantBlockUpdater.call(
130
+ variant: new_variant,
131
+ current_store: current_store,
132
+ data: block_data,
133
+ stock_location: current_vendor.stock_locations.first
134
+ )
135
+
136
+ raise StandardError, "Failed to update variant blocks: #{context.message}" unless context.success?
137
+ rescue StandardError => e
138
+ raise StandardError, "Failed to clone variant blocks: #{e.message}"
139
+ end
140
+
141
+ # Helpers
142
+ def departure_time_from_stops(trip)
143
+ first_departure = trip.trip_stops.first&.departure_time
144
+ trip.departure_time = first_departure if first_departure
145
+ end
146
+
147
+ def calculate_trip_duration(trip)
148
+ departures = trip.trip_stops.filter_map(&:departure_time)
149
+ arrivals = trip.trip_stops.filter_map(&:arrival_time)
150
+ return if departures.empty? || arrivals.empty?
151
+
152
+ trip.duration = (arrivals.max - departures.min).to_i
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,37 @@
1
+ module SpreeCmCommissioner
2
+ class UserFbTokenAuthenticator < BaseInteractor
3
+ delegate :fb_access_token, :tenant_id, to: :context
4
+
5
+ # :fb_access_token, :tenant_id (optional)
6
+ def call
7
+ context.fail!(message: 'fb_access_token_missing') unless fb_access_token
8
+
9
+ context.user = if checker.user.nil?
10
+ register_user
11
+ else
12
+ checker.user
13
+ end
14
+
15
+ context.fail!(message: 'account_temporarily_deleted') if context.user.soft_deleted?
16
+ end
17
+
18
+ private
19
+
20
+ def checker
21
+ @checker ||= SpreeCmCommissioner::UserFbTokenChecker.call(
22
+ fb_access_token: fb_access_token,
23
+ tenant_id: tenant_id
24
+ )
25
+ @checker
26
+ end
27
+
28
+ def register_user
29
+ register_context = SpreeCmCommissioner::UserRegistrationWithFbToken.call(
30
+ provider: checker.provider,
31
+ tenant_id: tenant_id
32
+ )
33
+
34
+ register_context.user
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ module SpreeCmCommissioner
2
+ class UserFbTokenChecker < BaseInteractor
3
+ delegate :tenant_id, :fb_access_token, to: :context
4
+
5
+ def call
6
+ fb_data = SpreeCmCommissioner::FetchFacebookUserData.call(fb_access_token: fb_access_token)
7
+ context.fail!(message: fb_data.message) if fb_data.failure?
8
+
9
+ context.provider = fb_data.provider
10
+
11
+ identity_checker = SpreeCmCommissioner::UserIdentityChecker.call(
12
+ identity_type: context.provider[:identity_type],
13
+ sub: context.provider[:sub],
14
+ tenant_id: tenant_id
15
+ )
16
+
17
+ context.user = identity_checker.success? ? identity_checker.user : nil
18
+ end
19
+ end
20
+ end
@@ -1,28 +1,28 @@
1
1
  module SpreeCmCommissioner
2
2
  class UserIdentityChecker < BaseInteractor
3
+ delegate :identity_type, :sub, :tenant_id, to: :context
4
+
3
5
  # expects:
4
6
  # context.identity_type
5
7
  # context.sub
6
8
  # context.tenant_id (optional)
7
-
8
9
  def call
9
10
  load_user
10
11
 
11
12
  return unless context.user.nil?
12
13
 
13
- error_message = I18n.t('user_identity_provider.not_found', identity_type: context.identity_type)
14
+ error_message = I18n.t('user_identity_provider.not_found', identity_type: identity_type)
14
15
  context.fail!(message: error_message)
15
16
  end
16
17
 
17
18
  def load_user
18
- return if context.sub.blank? || context.identity_type.blank?
19
+ return if sub.blank? || identity_type.blank?
19
20
 
20
- user_identity_provider = UserIdentityProvider.joins(:user)
21
- .where(identity_type: context.identity_type, sub: context.sub)
22
- .where(spree_users: { tenant_id: context.tenant_id })
23
- .first
21
+ user_identity_providers = UserIdentityProvider.joins(:user)
22
+ .where(identity_type: identity_type, sub: sub)
23
+ .where(spree_users: { tenant_id: tenant_id })
24
24
 
25
- context.user = user_identity_provider&.user
25
+ context.user = user_identity_providers.first&.user
26
26
  end
27
27
  end
28
28
  end