spree_cm_commissioner 2.0.3.pre.pre5 → 2.0.3.pre.pre.pre7

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 (215) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +2 -16
  3. data/.gitignore +1 -2
  4. data/Gemfile.lock +4 -39
  5. data/Rakefile +4 -33
  6. data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +11 -8
  7. data/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb +44 -0
  8. data/app/controllers/spree/admin/stock_managements_controller.rb +1 -63
  9. data/app/controllers/spree/api/v2/operator/event_charts_controller.rb +2 -8
  10. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +31 -14
  11. data/app/controllers/spree/api/v2/storefront/dynamic_fields_controller.rb +42 -0
  12. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +2 -0
  13. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  14. data/app/controllers/spree_cm_commissioner/admin/products_controller_decorator.rb +14 -0
  15. data/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb +20 -0
  16. data/app/interactors/spree_cm_commissioner/create_vendor.rb +11 -1
  17. data/app/jobs/spree_cm_commissioner/application_job.rb +0 -20
  18. data/app/jobs/spree_cm_commissioner/application_unique_job.rb +0 -20
  19. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +15 -18
  20. data/app/models/concerns/spree_cm_commissioner/line_item_guests_concern.rb +15 -29
  21. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +12 -2
  22. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +0 -39
  23. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +3 -1
  24. data/app/models/concerns/spree_cm_commissioner/product_type.rb +0 -10
  25. data/app/models/concerns/spree_cm_commissioner/route_type.rb +1 -1
  26. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +1 -1
  27. data/app/models/spree_cm_commissioner/address_decorator.rb +2 -22
  28. data/app/models/spree_cm_commissioner/branch.rb +12 -0
  29. data/app/models/spree_cm_commissioner/guest.rb +85 -71
  30. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +37 -27
  31. data/app/models/spree_cm_commissioner/line_item_decorator.rb +78 -47
  32. data/app/models/spree_cm_commissioner/line_item_seat.rb +10 -0
  33. data/app/models/spree_cm_commissioner/notification_taxon.rb +1 -1
  34. data/app/models/spree_cm_commissioner/option_type_decorator.rb +11 -1
  35. data/app/models/spree_cm_commissioner/option_value_vehicle_type.rb +8 -0
  36. data/app/models/spree_cm_commissioner/order_decorator.rb +1 -31
  37. data/app/models/spree_cm_commissioner/place.rb +4 -1
  38. data/app/models/spree_cm_commissioner/product_decorator.rb +35 -10
  39. data/app/models/spree_cm_commissioner/service_calendar.rb +2 -0
  40. data/app/models/spree_cm_commissioner/state_decorator.rb +1 -0
  41. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +25 -26
  42. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +1 -2
  43. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  44. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +0 -17
  45. data/app/models/spree_cm_commissioner/stop.rb +23 -0
  46. data/app/models/spree_cm_commissioner/taxon_decorator.rb +1 -0
  47. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +6 -0
  48. data/app/models/spree_cm_commissioner/trip.rb +10 -17
  49. data/app/models/spree_cm_commissioner/trip_connection.rb +5 -5
  50. data/app/models/spree_cm_commissioner/trip_stop.rb +21 -10
  51. data/app/models/spree_cm_commissioner/user_decorator.rb +1 -1
  52. data/app/models/spree_cm_commissioner/variant_decorator.rb +47 -37
  53. data/app/models/spree_cm_commissioner/variant_options.rb +23 -0
  54. data/app/models/spree_cm_commissioner/vehicle.rb +16 -11
  55. data/app/models/spree_cm_commissioner/vehicle_seat.rb +11 -0
  56. data/app/models/spree_cm_commissioner/vehicle_type.rb +76 -0
  57. data/app/models/spree_cm_commissioner/vendor_decorator.rb +14 -11
  58. data/app/models/spree_cm_commissioner/vendor_place.rb +3 -13
  59. data/app/models/spree_cm_commissioner/vendor_stop.rb +10 -0
  60. data/app/overrides/spree/admin/products/_form/industry_taxons.html.erb.deface +26 -0
  61. data/app/overrides/spree/admin/products/_form/taxons.html.erb.deface +26 -0
  62. data/app/queries/spree_cm_commissioner/trip_query.rb +131 -110
  63. data/app/queries/spree_cm_commissioner/trip_search_query.rb +76 -0
  64. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +45 -0
  65. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +55 -0
  66. data/app/queries/spree_cm_commissioner/vendor_stop_place_query.rb +54 -0
  67. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +0 -3
  68. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  69. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +0 -2
  70. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +0 -1
  71. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +2 -0
  72. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +1 -3
  73. data/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb +11 -0
  74. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +2 -7
  75. data/app/views/spree/admin/stock_managements/index.html.erb +6 -55
  76. data/config/initializers/spree_permitted_attributes.rb +0 -5
  77. data/config/routes.rb +3 -24
  78. data/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb +1 -3
  79. data/db/migrate/20250812015522_remove_unique_indexes_from_cm_user_identity_providers.rb +8 -0
  80. data/db/migrate/20250812121745_add_data_fill_stage_phase_to_cm_guests.rb +5 -0
  81. data/db/migrate/20250814073854_add_call_to_action_to_spree_products.rb +5 -0
  82. data/docker-compose.yml +1 -1
  83. data/lib/cm_app_logger.rb +4 -11
  84. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +11 -14
  85. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_dashboard/spree_cm_commissioner → spree_cm_commissioner}/utilities.js +0 -4
  86. data/lib/spree_cm_commissioner/calendar_event.rb +1 -11
  87. data/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb +12 -0
  88. data/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb +8 -0
  89. data/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb +8 -0
  90. data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +0 -10
  91. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  92. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  93. data/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb +7 -0
  94. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +30 -6
  95. data/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb +36 -0
  96. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +34 -18
  97. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  98. data/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb +14 -0
  99. data/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb +8 -0
  100. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +3 -11
  101. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +19 -41
  102. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb +1 -1
  103. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb +8 -0
  104. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +96 -0
  105. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -6
  106. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +1 -13
  107. data/lib/spree_cm_commissioner/trip_query_result.rb +0 -16
  108. data/lib/spree_cm_commissioner/trip_result.rb +10 -12
  109. data/lib/spree_cm_commissioner/trip_seat_layout_result.rb +11 -0
  110. data/lib/spree_cm_commissioner/version.rb +1 -1
  111. data/lib/spree_cm_commissioner.rb +1 -37
  112. data/spree_cm_commissioner.gemspec +0 -7
  113. metadata +32 -161
  114. data/app/controllers/spree/admin/inventory_items_controller.rb +0 -83
  115. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +0 -42
  116. data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +0 -60
  117. data/app/finders/spree_cm_commissioner/accommodations/find.rb +0 -37
  118. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +0 -32
  119. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +0 -40
  120. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +0 -25
  121. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +0 -44
  122. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +0 -13
  123. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +0 -15
  124. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +0 -75
  125. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +0 -32
  126. data/app/interactors/spree_cm_commissioner/taxi_trip_creator.rb +0 -66
  127. data/app/interactors/spree_cm_commissioner/transit/draft_order_creator.rb +0 -119
  128. data/app/interactors/spree_cm_commissioner/variant_block_updater.rb +0 -62
  129. data/app/interactors/spree_cm_commissioner/vehicle_updater.rb +0 -41
  130. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +0 -7
  131. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +0 -7
  132. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +0 -11
  133. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +0 -11
  134. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +0 -9
  135. data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +0 -89
  136. data/app/models/concerns/spree_cm_commissioner/order_seatable.rb +0 -44
  137. data/app/models/concerns/spree_cm_commissioner/vehicle_type.rb +0 -11
  138. data/app/models/spree_cm_commissioner/block.rb +0 -43
  139. data/app/models/spree_cm_commissioner/inventory.rb +0 -11
  140. data/app/models/spree_cm_commissioner/inventory_item.rb +0 -69
  141. data/app/models/spree_cm_commissioner/price_decorator.rb +0 -9
  142. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +0 -41
  143. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +0 -126
  144. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +0 -36
  145. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +0 -25
  146. data/app/models/spree_cm_commissioner/reserved_block.rb +0 -30
  147. data/app/models/spree_cm_commissioner/saved_guest.rb +0 -13
  148. data/app/models/spree_cm_commissioner/seat_layout.rb +0 -20
  149. data/app/models/spree_cm_commissioner/seat_section.rb +0 -16
  150. data/app/models/spree_cm_commissioner/seats/blocks_canceler.rb +0 -30
  151. data/app/models/spree_cm_commissioner/seats/blocks_holder.rb +0 -53
  152. data/app/models/spree_cm_commissioner/seats/blocks_reserver.rb +0 -49
  153. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_on_hold_by_other_guest.rb +0 -4
  154. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_other_guest.rb +0 -4
  155. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_same_guest.rb +0 -4
  156. data/app/models/spree_cm_commissioner/seats/errors/unable_to_save_reserved_block_record.rb +0 -4
  157. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +0 -44
  158. data/app/models/spree_cm_commissioner/variant_block.rb +0 -17
  159. data/app/request_schemas/spree_cm_commissioner/trip_search_request_schema.rb +0 -20
  160. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +0 -19
  161. data/app/serializers/spree/v2/storefront/amenity_serializer.rb +0 -15
  162. data/app/serializers/spree/v2/storefront/trip_query_result_serializer.rb +0 -14
  163. data/app/serializers/spree/v2/storefront/trip_result_serializer.rb +0 -16
  164. data/app/serializers/spree/v2/storefront/trip_vehicle_serializer.rb +0 -11
  165. data/app/serializers/spree/v2/storefront/trip_vendor_serializer.rb +0 -13
  166. data/app/views/spree/admin/inventory_items/show.html.erb +0 -72
  167. data/app/views/spree/admin/stock_managements/calendar.html.erb +0 -56
  168. data/config/initializers/paper_trail.rb +0 -1
  169. data/db/migrate/20250122000000_move_route_type_from_vehicles_to_trips.rb +0 -7
  170. data/db/migrate/20250304293518_create_cm_inventory_items.rb +0 -21
  171. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +0 -5
  172. data/db/migrate/20250502025848_add_index_to_spree_products.rb +0 -5
  173. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +0 -5
  174. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +0 -5
  175. data/db/migrate/20250603035256_add_inventory_item_to_spree_prices.rb +0 -7
  176. data/db/migrate/20250619073724_drop_table_cm_line_item_seats.rb +0 -5
  177. data/db/migrate/20250619073812_drop_table_cm_vehicle_seats.rb +0 -5
  178. data/db/migrate/20250619073844_drop_table_cm_vehicle_types.rb +0 -9
  179. data/db/migrate/20250619073957_drop_table_cm_option_value_vehicle_types.rb +0 -5
  180. data/db/migrate/20250619082354_remove_unnecessary_fields_from_cm_places.rb +0 -9
  181. data/db/migrate/20250619082736_remove_route_type_from_spree_products.rb +0 -5
  182. data/db/migrate/20250619083055_remove_unnecessary_fields_from_spree_taxons.rb +0 -5
  183. data/db/migrate/20250620083055_remove_variant_id_from_cm_trips.rb +0 -5
  184. data/db/migrate/20250620090000_update_cm_trip_connections_to_use_cm_trips.rb +0 -6
  185. data/db/migrate/20250620090001_create_cm_seat_layouts.rb +0 -17
  186. data/db/migrate/20250620090002_create_cm_seat_sections.rb +0 -18
  187. data/db/migrate/20250620090003_create_cm_blocks.rb +0 -18
  188. data/db/migrate/20250624091005_create_cm_reserved_blocks.rb +0 -29
  189. data/db/migrate/20250626083642_create_cm_variant_blocks.rb +0 -24
  190. data/db/migrate/20250627023314_add_block_id_to_cm_guests.rb +0 -13
  191. data/db/migrate/20250716022821_add_location_reference_to_cm_vendor_places.rb +0 -5
  192. data/db/migrate/20250716031743_drop_table_cm_vendor_stops.rb +0 -5
  193. data/db/migrate/20250717023824_add_vendor_reference_to_cm_trips.rb +0 -5
  194. data/db/migrate/20250717041414_add_location_place_reference_to_cm_trip_stops.rb +0 -5
  195. data/db/migrate/20250717042539_rename_cm_trip_stops_stop_id_column_to_stop_place_id.rb +0 -7
  196. data/db/migrate/20250717042707_rename_cm_trips_origin_and_destination_to_origin_place_and_destination_place.rb +0 -11
  197. data/db/migrate/20250721080738_add_vehicle_type_to_cm_vehicles.rb +0 -7
  198. data/db/migrate/20250725092713_add_block_type_to_cm_blocks.rb +0 -5
  199. data/db/migrate/20250731062816_add_departure_time_and_arrival_time_to_trip_stop.rb +0 -6
  200. data/db/migrate/20250807033035_create_spree_cm_commissioner_saved_guests.rb +0 -23
  201. data/db/migrate/20250808054835_add_saved_guest_reference_to_cm_blocks.rb +0 -5
  202. data/lib/spree_cm_commissioner/cached_inventory_item.rb +0 -23
  203. data/lib/spree_cm_commissioner/test_helper/factories/block_factory.rb +0 -16
  204. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +0 -9
  205. data/lib/spree_cm_commissioner/test_helper/factories/reserved_block_factory.rb +0 -27
  206. data/lib/spree_cm_commissioner/test_helper/factories/seat_layout_factory.rb +0 -11
  207. data/lib/spree_cm_commissioner/test_helper/factories/seat_section_factory.rb +0 -16
  208. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +0 -6
  209. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +0 -13
  210. data/lib/spree_cm_commissioner/test_helper/factories/variant_block_factory.rb +0 -7
  211. data/lib/spree_cm_commissioner/transit/leg.rb +0 -23
  212. data/lib/spree_cm_commissioner/transit/seat_selection.rb +0 -19
  213. data/lib/tasks/create_default_non_permanent_inventory_items.rake +0 -16
  214. data/lib/tasks/ensure_correct_product_type.rake +0 -7
  215. data/lib/tasks/generate_inventory_items.rake +0 -7
@@ -1,119 +0,0 @@
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
46
- end
47
-
48
- # Build line items for each leg, using the previous leg's to_date as the next leg's date,
49
- # or the initial date for the first leg.
50
- def build_line_items_for_legs!(order:, legs:, initial_date:)
51
- all_line_items = []
52
- current_leg_date = initial_date
53
-
54
- legs.each do |leg|
55
- leg_line_items = build_line_items_for!(leg, order, current_leg_date)
56
- all_line_items.concat(leg_line_items)
57
- current_leg_date = leg_line_items.last.to_date.to_date if leg_line_items.any?
58
- end
59
-
60
- all_line_items
61
- end
62
-
63
- def build_line_items_for!(leg, order, date)
64
- trip = SpreeCmCommissioner::Trip.find(leg.trip_id)
65
- trip_stops = trip.trip_stops.where(id: [leg.boarding_trip_stop_id, leg.drop_off_trip_stop_id]).index_by(&:id)
66
-
67
- from_date, to_date = calculate_line_item_duration!(
68
- date: date,
69
- departure_time: trip_stops[leg.boarding_trip_stop_id].departure_time,
70
- arrival_time: trip_stops[leg.drop_off_trip_stop_id].arrival_time
71
- )
72
-
73
- leg.seat_selections.group_by(&:variant_id).map do |variant_id, seat_selections|
74
- line_item = order.line_items.new(
75
- product_type: :transit,
76
- from_date: from_date,
77
- to_date: to_date,
78
- variant_id: variant_id,
79
- quantity: seat_selections.sum(&:quantity),
80
- private_metadata: {
81
- direction: leg.direction,
82
- trip_id: leg.trip_id.to_s,
83
- boarding_trip_stop_id: leg.boarding_trip_stop_id.to_s,
84
- drop_off_trip_stop_id: leg.drop_off_trip_stop_id.to_s
85
- }
86
- )
87
-
88
- build_guests!(line_item, seat_selections)
89
-
90
- line_item
91
- end
92
- end
93
-
94
- def build_guests!(line_item, seat_selections)
95
- block_ids = seat_selections.flat_map(&:block_ids)
96
-
97
- if block_ids.any? && block_ids.size != line_item.quantity
98
- raise StandardError, "Number of blocks (#{block_ids.size}) does not match quantity (#{line_item.quantity})"
99
- end
100
-
101
- Array.new(line_item.quantity) do |index|
102
- line_item.guests.new(block_id: block_ids[index])
103
- end
104
- end
105
-
106
- def calculate_line_item_duration!(date:, departure_time:, arrival_time:)
107
- raise StandardError, 'Departure or arrival time in trip stop is missing' if departure_time.blank? || arrival_time.blank?
108
- raise StandardError, 'Arrival time cannot be before departure time' if arrival_time < departure_time
109
-
110
- trip_duration_in_seconds = arrival_time - departure_time
111
-
112
- from_date = date.to_datetime.change(hour: departure_time.hour, min: departure_time.min, sec: departure_time.sec)
113
- to_date = from_date + trip_duration_in_seconds.seconds
114
-
115
- [from_date, to_date]
116
- end
117
- end
118
- end
119
- end
@@ -1,62 +0,0 @@
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
@@ -1,41 +0,0 @@
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,7 +0,0 @@
1
- module SpreeCmCommissioner
2
- class EnsureCorrectProductTypeJob < ApplicationJob
3
- def perform
4
- SpreeCmCommissioner::EnsureCorrectProductType.call
5
- end
6
- end
7
- end
@@ -1,7 +0,0 @@
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
@@ -1,11 +0,0 @@
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
@@ -1,11 +0,0 @@
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
@@ -1,9 +0,0 @@
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
@@ -1,89 +0,0 @@
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
- # Groups LineItems by trip fields. Some fields can be null,
31
- # so use it with caution. Output:
32
- # {
33
- # $<TripKey:> => [<LineItem id:1>]
34
- # $<TripKey:> => [<LineItem id:2>]
35
- # $<TripKey:> => [<LineItem id:3>, <LineItem id:4>]
36
- # }
37
- def self.group_by_trip_key
38
- all.group_by do |line_item|
39
- TripKey.new(
40
- line_item.trip_id,
41
- line_item.direction,
42
- line_item.boarding_trip_stop_id,
43
- line_item.drop_off_trip_stop_id
44
- )
45
- end
46
- end
47
- end
48
-
49
- def outbound? = direction == 'outbound'
50
- def inbound? = direction == 'inbound'
51
-
52
- def direction = private_metadata['direction']
53
- def trip_id = private_metadata['trip_id']&.to_i
54
- def boarding_trip_stop_id = private_metadata['boarding_trip_stop_id']&.to_i
55
- def drop_off_trip_stop_id = private_metadata['drop_off_trip_stop_id']&.to_i
56
-
57
- def direction=(value)
58
- if value.nil?
59
- private_metadata.delete('direction')
60
- else
61
- private_metadata['direction'] = value.to_s
62
- end
63
- end
64
-
65
- def trip_id=(value)
66
- if value.nil?
67
- private_metadata.delete('trip_id')
68
- else
69
- private_metadata['trip_id'] = value.to_s
70
- end
71
- end
72
-
73
- def boarding_trip_stop_id=(value)
74
- if value.nil?
75
- private_metadata.delete('boarding_trip_stop_id')
76
- else
77
- private_metadata['boarding_trip_stop_id'] = value.to_s
78
- end
79
- end
80
-
81
- def drop_off_trip_stop_id=(value)
82
- if value.nil?
83
- private_metadata.delete('drop_off_trip_stop_id')
84
- else
85
- private_metadata['drop_off_trip_stop_id'] = value.to_s
86
- end
87
- end
88
- end
89
- end
@@ -1,44 +0,0 @@
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
@@ -1,11 +0,0 @@
1
- module SpreeCmCommissioner
2
- module VehicleType
3
- extend ActiveSupport::Concern
4
-
5
- VEHICLE_TYPES = %i[suv sedan minivan van sleeping_bus luxury_van air_bus].freeze
6
-
7
- included do
8
- enum vehicle_type: VEHICLE_TYPES if table_exists? && column_names.include?('vehicle_type')
9
- end
10
- end
11
- end
@@ -1,43 +0,0 @@
1
- module SpreeCmCommissioner
2
- class Block < Base
3
- enum block_type: {
4
- seat: 1,
5
- sleeping_seat: 2,
6
- driver: 3,
7
- bathroom: 4,
8
- other: 0
9
- }
10
-
11
- belongs_to :seat_section, class_name: 'SpreeCmCommissioner::SeatSection', optional: true
12
- belongs_to :seat_layout, class_name: 'SpreeCmCommissioner::SeatLayout', optional: false
13
-
14
- validates :label, presence: true, if: :label_required?
15
-
16
- has_many :variant_blocks, class_name: 'SpreeCmCommissioner::VariantBlock', dependent: :destroy
17
- has_many :variants, class_name: 'Spree::Variant', through: :variant_blocks
18
-
19
- validates :width, presence: true, numericality: { greater_than: 0 }
20
- validates :height, presence: true, numericality: { greater_than: 0 }
21
- validates :x, presence: true, numericality: true
22
- validates :y, presence: true, numericality: true
23
- validates :rotation, presence: true, numericality: { greater_than_or_equal_to: -360, less_than_or_equal_to: 360 }
24
-
25
- before_validation :assign_layout_from_section, if: -> { seat_layout.nil? && seat_section.present? }
26
-
27
- def label_required?
28
- seatable? || block_type.to_s == 'other'
29
- end
30
-
31
- def self.seatable?(block_type)
32
- block_type.to_s.in?(%w[sleeping_seat seat])
33
- end
34
-
35
- def seatable?
36
- self.class.seatable?(block_type)
37
- end
38
-
39
- def assign_layout_from_section
40
- self.seat_layout = seat_section.seat_layout
41
- end
42
- end
43
- end
@@ -1,11 +0,0 @@
1
- module SpreeCmCommissioner
2
- class Inventory
3
- include ActiveModel::Model
4
-
5
- attr_accessor :variant_id, :inventory_date, :quantity_available, :max_capacity, :product_type
6
-
7
- validates :variant_id, presence: true
8
- validates :quantity_available, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true
9
- validates :max_capacity, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true
10
- end
11
- end
@@ -1,69 +0,0 @@
1
- module SpreeCmCommissioner
2
- class InventoryItem < Base
3
- include SpreeCmCommissioner::ProductType
4
-
5
- # Association
6
- belongs_to :variant, class_name: 'Spree::Variant', optional: false
7
- has_many :prices, class_name: 'Spree::Price', inverse_of: :inventory_item
8
-
9
- # Validation
10
- validates :quantity_available, numericality: { greater_than_or_equal_to: 0 }
11
- validates :max_capacity, numericality: { greater_than_or_equal_to: 0 } # Originally inventory of each variant.
12
- validates :inventory_date, presence: true, if: :permanent_stock?
13
- validates :variant_id, uniqueness: { scope: :inventory_date, message: -> (object, _data) { "The variant is taken on #{object.inventory_date}" } }
14
-
15
- # Scope
16
- scope :active, -> { where(inventory_date: nil).or(where('inventory_date >= ?', Time.zone.today)) }
17
-
18
- before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
19
-
20
- # This method is only used when admin update stock
21
- def adjust_quantity!(quantity)
22
- with_lock do
23
- self.max_capacity = [max_capacity + quantity, 0].max
24
- self.quantity_available = [quantity_available + quantity, 0].max
25
- save!
26
-
27
- # When user has been searched or booked a product, it has cached the quantity in redis,
28
- # So we need to update redis cache if inventory key has been created in redis
29
- adjust_quantity_in_redis(quantity)
30
- end
31
- end
32
-
33
- def redis_key
34
- "inventory:#{id}"
35
- end
36
-
37
- def adjust_quantity_in_redis(quantity)
38
- SpreeCmCommissioner.redis_pool.with do |redis|
39
- cached_quantity_available = redis.get(redis_key)
40
- # ignore if redis doesn't exist
41
- return if cached_quantity_available.nil? # rubocop:disable Lint/NonLocalExitFromIterator
42
-
43
- script = <<~LUA
44
- local key = KEYS[1]
45
- local increment = tonumber(ARGV[1])
46
- local current = tonumber(redis.call('GET', key) or 0)
47
- local new_value = current + increment
48
- if new_value < 0 then
49
- new_value = 0
50
- end
51
- redis.call('SET', key, new_value)
52
- return new_value
53
- LUA
54
-
55
- redis.eval(script, keys: [redis_key], argv: [quantity])
56
- end
57
- end
58
-
59
- def active?
60
- inventory_date.nil? || inventory_date >= Time.zone.today
61
- end
62
-
63
- def redis_expired_in
64
- expired_in = 31_536_000 # 1 year for normal stock
65
- expired_in = Time.parse(inventory_date.to_s).end_of_day.to_i - Time.zone.now.to_i if inventory_date.present?
66
- [expired_in, 0].max
67
- end
68
- end
69
- end
@@ -1,9 +0,0 @@
1
- module SpreeCmCommissioner
2
- module PriceDecorator
3
- def self.prepended(base)
4
- base.belongs_to :inventory_item, class_name: 'SpreeCmCommissioner::InventoryItem', inverse_of: :prices, optional: true
5
- end
6
- end
7
- end
8
-
9
- Spree::Price.prepend(SpreeCmCommissioner::PriceDecorator) unless Spree::Price.included_modules.include?(SpreeCmCommissioner::PriceDecorator)
@@ -1,41 +0,0 @@
1
- module SpreeCmCommissioner
2
- module RedisStock
3
- class CachedInventoryItemsBuilder
4
- attr_reader :inventory_items
5
-
6
- def initialize(inventory_items)
7
- @inventory_items = inventory_items
8
- end
9
-
10
- # output: [ CachedInventoryItem(...), CachedInventoryItem(...) ]
11
- def call
12
- keys = inventory_items.map { |item| "inventory:#{item.id}" }
13
- return [] unless keys.any?
14
-
15
- counts = SpreeCmCommissioner.redis_pool.with { |redis| redis.mget(*keys) }
16
- inventory_items.map.with_index do |inventory_item, i|
17
- ::SpreeCmCommissioner::CachedInventoryItem.new(
18
- inventory_key: keys[i],
19
- active: inventory_item.active?,
20
- quantity_available: cache_inventory(keys[i], inventory_item, counts[i]),
21
- inventory_item_id: inventory_item.id,
22
- variant_id: inventory_item.variant_id
23
- )
24
- end
25
- end
26
-
27
- private
28
-
29
- def cache_inventory(key, inventory_item, count_in_redis)
30
- return count_in_redis.to_i if count_in_redis.present?
31
- return inventory_item.quantity_available unless inventory_item.active?
32
-
33
- SpreeCmCommissioner.redis_pool.with do |redis|
34
- redis.set(key, inventory_item.quantity_available, ex: inventory_item.redis_expired_in)
35
- end
36
-
37
- inventory_item.quantity_available
38
- end
39
- end
40
- end
41
- end