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
@@ -6,7 +6,7 @@ module SpreeCmCommissioner
6
6
  base.include SpreeCmCommissioner::ParameterizeName
7
7
  base.include SpreeCmCommissioner::OptionTypeAttrType
8
8
 
9
- base.enum kind: %i[variant product vendor vehicle_type transit]
9
+ base.enum kind: %i[variant product vendor vehicle]
10
10
 
11
11
  base.validates :name, presence: true
12
12
 
@@ -19,16 +19,6 @@ module SpreeCmCommissioner
19
19
  base.scope :promoted, -> { where(promoted: true) }
20
20
  base.whitelisted_ransackable_attributes = %w[kind]
21
21
 
22
- def base.amenities
23
- Spree::OptionType.where(kind: 'vehicle_type', name: 'amenities', presentation: 'Amenities', attr_type: 'amenity').first_or_create
24
- end
25
-
26
- def base.vehicle
27
- Spree::OptionType.where(presentation: 'vehicle', attr_type: 'vehicle_id', kind: 'variant',
28
- name: 'vehicle'
29
- ).first_or_create
30
- end
31
-
32
22
  def base.rules_option_type
33
23
  Spree::OptionType.find_by(name: RULES_OPTION_TYPE_NAME)
34
24
  end
@@ -5,30 +5,48 @@
5
5
  module SpreeCmCommissioner
6
6
  module Order
7
7
  module AddressBookDecorator
8
- def guest_order? = user.blank?
9
-
10
8
  # override
11
9
  def bill_address_attributes=(attributes)
12
- attributes[:id] = bill_address&.id if attributes[:id].blank?
13
- return if guest_order? && !basic_info_included?(attributes)
10
+ attributes[:id] = bill_address_id if attributes[:id].blank?
11
+ attributes[:phone] = intel_phone_number if intel_phone_number.present?
14
12
 
15
- super
13
+ self.bill_address = assign_address_attributes(attributes)
14
+ user.bill_address = bill_address if user && user.bill_address.nil?
16
15
  end
17
16
 
18
17
  # override
19
18
  def ship_address_attributes=(attributes)
20
- attributes[:id] = bill_address&.id if attributes[:id].blank?
21
- return if guest_order? && !basic_info_included?(attributes)
19
+ attributes[:id] = ship_address_id if attributes[:id].blank?
20
+ attributes[:phone] = intel_phone_number if intel_phone_number.present?
22
21
 
23
- super
22
+ self.ship_address = assign_address_attributes(attributes)
23
+ user.ship_address = ship_address if user && user.ship_address.nil?
24
24
  end
25
25
 
26
- private
26
+ def assign_address_attributes(attributes = {})
27
+ return if attributes.blank?
28
+
29
+ attributes.transform_values! { |v| v == '' ? nil : v }
30
+ attributes = attributes.to_h.symbolize_keys
27
31
 
28
- def basic_info_included?(attributes)
29
- return true if attributes[:id].present?
32
+ existing_address = attributes[:id].present? ? find_existing_address(attributes[:id]) : nil
33
+
34
+ if existing_address&.editable?
35
+ existing_address.assign_attributes(attributes)
36
+ existing_address
37
+ else
38
+ ::Spree::Address.new(attributes.except(:id, :updated_at, :created_at))
39
+ end
40
+ end
30
41
 
31
- %i[firstname lastname phone].all? { |key| attributes[key].present? }
42
+ def find_existing_address(id)
43
+ if user.present?
44
+ ::Spree::Address.where('(user_id = ? OR user_id IS NULL) AND deleted_at IS NULL', user.id)
45
+ .find_by(id: id)
46
+ else
47
+ ::Spree::Address.where(user_id: nil, deleted_at: nil)
48
+ .find_by(id: id)
49
+ end
32
50
  end
33
51
  end
34
52
  end
@@ -2,6 +2,7 @@ module SpreeCmCommissioner
2
2
  module OrderDecorator
3
3
  def self.prepended(base) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
4
4
  base.include SpreeCmCommissioner::PhoneNumberSanitizer
5
+ base.include SpreeCmCommissioner::OrderSeatable
5
6
  base.include SpreeCmCommissioner::OrderStateMachine
6
7
 
7
8
  base.scope :subscription, -> { where.not(subscription_id: nil) }
@@ -42,6 +43,14 @@ module SpreeCmCommissioner
42
43
  base.has_many :vendors, through: :products, class_name: 'Spree::Vendor'
43
44
  base.has_many :taxons, through: :products, class_name: 'Spree::Taxon'
44
45
  base.has_many :guests, through: :line_items, class_name: 'SpreeCmCommissioner::Guest'
46
+
47
+ base.has_many :saved_guests,
48
+ through: :guests,
49
+ source: :saved_guest,
50
+ class_name: 'SpreeCmCommissioner::SavedGuest'
51
+
52
+ base.has_many :blocks, through: :guests, class_name: 'SpreeCmCommissioner::Block', source: :block
53
+ base.has_many :reserved_blocks, through: :guests, class_name: 'SpreeCmCommissioner::ReservedBlock'
45
54
  base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variants
46
55
 
47
56
  base.delegate :customer, to: :user, allow_nil: true
@@ -49,6 +58,8 @@ module SpreeCmCommissioner
49
58
  base.whitelisted_ransackable_associations |= %w[customer taxon payments guests invoice]
50
59
  base.whitelisted_ransackable_attributes |= %w[intel_phone_number phone_number email number state]
51
60
 
61
+ base.accepts_nested_attributes_for :saved_guests, allow_destroy: true
62
+
52
63
  def base.search_by_qr_data!(data)
53
64
  token = data.match(/^R\d{9,}-([A-Za-z0-9_\-]+)$/)&.captures
54
65
 
@@ -58,6 +69,13 @@ module SpreeCmCommissioner
58
69
  end
59
70
  end
60
71
 
72
+ # override
73
+ # spree use this method to check stock availability & consider whether :order can continue to next state.
74
+ def insufficient_stock_lines
75
+ checker = SpreeCmCommissioner::Stock::OrderAvailabilityChecker.new(self)
76
+ checker.insufficient_stock_lines
77
+ end
78
+
61
79
  def ticket_seller_user?
62
80
  return false if user.nil?
63
81
 
@@ -106,7 +124,14 @@ module SpreeCmCommissioner
106
124
  end
107
125
 
108
126
  def mark_as_archive
109
- update(archived_at: Time.current)
127
+ blocks_canceled = begin
128
+ cancel_blocks!
129
+ true
130
+ rescue StandardError
131
+ false
132
+ end
133
+
134
+ update(archived_at: Time.current) if blocks_canceled
110
135
  end
111
136
 
112
137
  # overrided
@@ -199,6 +224,18 @@ module SpreeCmCommissioner
199
224
 
200
225
  private
201
226
 
227
+ def unstock_inventory_in_redis!
228
+ CmAppLogger.log(label: "#{self.class.name}#unstock_inventory_in_redis!", data: { order_id: id }) do
229
+ SpreeCmCommissioner::RedisStock::InventoryUpdater.new(line_item_ids).unstock!
230
+ end
231
+ end
232
+
233
+ def restock_inventory_in_redis!
234
+ CmAppLogger.log(label: "#{self.class.name}#restock_inventory_in_redis!", data: { order_id: id }) do
235
+ SpreeCmCommissioner::RedisStock::InventoryUpdater.new(line_item_ids).restock!
236
+ end
237
+ end
238
+
202
239
  # override :spree_api
203
240
  def webhook_payload_body
204
241
  resource_serializer.new(
@@ -1,7 +1,5 @@
1
- require_dependency 'spree_cm_commissioner'
2
-
3
1
  module SpreeCmCommissioner
4
- class Place < ApplicationRecord
2
+ class Place < Base
5
3
  acts_as_nested_set
6
4
 
7
5
  validates :reference, presence: true, if: :validate_reference?
@@ -16,8 +14,9 @@ module SpreeCmCommissioner
16
14
  has_many :product_places, class_name: 'SpreeCmCommissioner::ProductPlace', dependent: :destroy
17
15
  has_many :products, through: :product_places
18
16
 
17
+ has_many :location_vendor_places, -> { where(place_type: :location) }, class_name: 'SpreeCmCommissioner::VendorPlace'
18
+
19
19
  has_many :children, -> { order(:lft) }, class_name: 'SpreeCmCommissioner::Place', foreign_key: :parent_id, dependent: :destroy
20
- has_many :vendor_stops, class_name: 'SpreeCmCommissioner::VendorStop', dependent: :destroy, foreign_key: :stop_id
21
20
 
22
21
  has_many :taxon_places, class_name: 'SpreeCmCommissioner::TaxonPlace', dependent: :destroy
23
22
  has_many :taxons, through: :taxon_places
@@ -0,0 +1,9 @@
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)
@@ -6,7 +6,6 @@ module SpreeCmCommissioner
6
6
  base.include SpreeCmCommissioner::KycBitwise
7
7
  base.include SpreeCmCommissioner::Metafield
8
8
  base.include SpreeCmCommissioner::TenantUpdatable
9
- base.include SpreeCmCommissioner::RouteType
10
9
 
11
10
  base.has_many :variant_kind_option_types, -> { where(kind: :variant).order(:position) },
12
11
  through: :product_option_types, source: :option_type
@@ -33,6 +32,7 @@ module SpreeCmCommissioner
33
32
  base.has_one :google_wallet, class_name: 'SpreeCmCommissioner::GoogleWallet', dependent: :destroy
34
33
 
35
34
  base.has_many :complete_line_items, through: :classifications, source: :line_items
35
+ base.has_many :inventory_items, through: :variants
36
36
  base.has_many :guests, through: :line_items
37
37
 
38
38
  base.has_many :product_places, class_name: 'SpreeCmCommissioner::ProductPlace', dependent: :destroy
@@ -50,7 +50,6 @@ module SpreeCmCommissioner
50
50
 
51
51
  base.has_many :industry_classifications, -> { joins(:taxon).where(spree_taxons: { kind: :industry }) }, class_name: 'Spree::Classification'
52
52
  base.has_many :industry_taxons, through: :industry_classifications, source: :taxon
53
-
54
53
  base.scope :min_price, lambda { |vendor|
55
54
  joins(:prices_including_master)
56
55
  .where(vendor_id: vendor.id, product_type: vendor.primary_product_type)
@@ -67,12 +66,11 @@ module SpreeCmCommissioner
67
66
  base.before_validation :set_event_id
68
67
 
69
68
  base.validate :validate_event_taxons, if: -> { taxons.event.present? }
70
-
71
69
  base.validate :validate_product_date, if: -> { available_on.present? && discontinue_on.present? }
72
-
70
+ base.validate :product_type_unchanged, on: :update
73
71
  base.validates :commission_rate, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true
74
72
 
75
- base.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status vendor_id short_name route_type]
73
+ base.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status vendor_id short_name]
76
74
 
77
75
  base.after_update :update_variants_vendor_id, if: :saved_change_to_vendor_id?
78
76
  base.after_update :sync_event_id_to_children, if: :saved_change_to_event_id?
@@ -89,8 +87,6 @@ module SpreeCmCommissioner
89
87
  request_to_book: 8
90
88
  }
91
89
 
92
- base.accepts_nested_attributes_for :trip, allow_destroy: true
93
-
94
90
  base.multi_tenant :tenant, class_name: 'SpreeCmCommissioner::Tenant'
95
91
  base.before_save :set_tenant
96
92
  end
@@ -117,9 +113,9 @@ module SpreeCmCommissioner
117
113
 
118
114
  def dynamic_fields_by_collection_phase
119
115
  {
120
- pre_registration: dynamic_fields.pre_registration.order(:position),
121
- post_registration: dynamic_fields.post_registration.order(:position),
122
- during_check_in: dynamic_fields.during_check_in.order(:position)
116
+ pre_registration: dynamic_fields.pre_registration.includes(:dynamic_field_options).order(:position),
117
+ post_registration: dynamic_fields.post_registration.includes(:dynamic_field_options).order(:position),
118
+ during_check_in: dynamic_fields.during_check_in.includes(:dynamic_field_options).order(:position)
123
119
  }
124
120
  end
125
121
 
@@ -165,6 +161,13 @@ module SpreeCmCommissioner
165
161
 
166
162
  errors.add(:discontinue_on, 'must be after the available on date')
167
163
  end
164
+
165
+ def product_type_unchanged
166
+ return if product_type_was.nil?
167
+ return unless product_type_changed?
168
+
169
+ errors.add(:product_type, 'cannot be changed once set')
170
+ end
168
171
  end
169
172
  end
170
173
 
@@ -0,0 +1,41 @@
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
@@ -0,0 +1,126 @@
1
+ module SpreeCmCommissioner
2
+ module RedisStock
3
+ class InventoryUpdater
4
+ class UnableToRestock < StandardError; end
5
+ class UnableToUnstock < StandardError; end
6
+
7
+ def initialize(line_item_ids)
8
+ @line_item_ids = line_item_ids
9
+ end
10
+
11
+ def unstock!
12
+ keys, quantities, inventory_ids = extract_inventory_data
13
+
14
+ raise UnableToUnstock, Spree.t(:insufficient_stock_lines_present) unless unstock(keys, quantities)
15
+
16
+ inventory_id_and_quantities = inventory_ids.map.with_index do |inventory_id, i|
17
+ { inventory_id: inventory_id, quantity: -quantities[i] }
18
+ end
19
+
20
+ schedule_sync_inventory(inventory_id_and_quantities)
21
+ end
22
+
23
+ def restock!
24
+ keys, quantities, inventory_ids = extract_inventory_data
25
+
26
+ raise UnableToRestock unless restock(keys, quantities)
27
+
28
+ inventory_id_and_quantities = inventory_ids.map.with_index do |inventory_id, i|
29
+ { inventory_id: inventory_id, quantity: quantities[i] }
30
+ end
31
+
32
+ schedule_sync_inventory(inventory_id_and_quantities)
33
+ end
34
+
35
+ private
36
+
37
+ # only unstock or restock when variants is available, track inventory, not backorderable & not need_confirmation.
38
+ # otherwise, we can ignore them.
39
+ def line_items
40
+ return @line_items if defined?(@line_items)
41
+
42
+ @line_items = Spree::LineItem.where(id: @line_item_ids).includes(variant: :stock_items)
43
+ @line_items = @line_items.map do |line_item|
44
+ next unless line_item.variant.available?
45
+ next unless line_item.variant.should_track_inventory?
46
+ next if line_item.variant.backorderable?
47
+ next if line_item.variant.need_confirmation?
48
+
49
+ line_item
50
+ end.compact
51
+ end
52
+
53
+ def unstock(keys, quantities)
54
+ SpreeCmCommissioner.redis_pool.with do |redis|
55
+ redis.eval(unstock_redis_script, keys: keys, argv: quantities)
56
+ end.positive?
57
+ end
58
+
59
+ def restock(keys, quantities)
60
+ SpreeCmCommissioner.redis_pool.with do |redis|
61
+ redis.eval(restock_redis_script, keys: keys, argv: quantities)
62
+ end.positive?
63
+ end
64
+
65
+ # Return: [CachedInventoryItem(...), CachedInventoryItem(...)]
66
+ def cached_inventory_items
67
+ @cached_inventory_items ||= SpreeCmCommissioner::RedisStock::LineItemsCachedInventoryItemsBuilder.new(line_item_ids: line_items.pluck(:id))
68
+ .call.values.flatten
69
+ end
70
+
71
+ def extract_inventory_data
72
+ keys = []
73
+ quantities = []
74
+ inventory_ids = []
75
+
76
+ cached_inventory_items.each do |cached_inventory_item|
77
+ keys << cached_inventory_item.inventory_key
78
+ quantities << line_items.find { |item| item.variant_id == cached_inventory_item.variant_id }.quantity
79
+ inventory_ids << cached_inventory_item.inventory_item_id
80
+ end
81
+
82
+ [keys, quantities, inventory_ids]
83
+ end
84
+
85
+ def unstock_redis_script
86
+ <<~LUA
87
+ local keys = KEYS
88
+ local quantities = ARGV
89
+
90
+ -- Check availability first
91
+ for i, key in ipairs(keys) do
92
+ local current = tonumber(redis.call('GET', key) or 0)
93
+ if current - tonumber(quantities[i]) < 0 then
94
+ return 0
95
+ end
96
+ end
97
+
98
+ -- Apply updates
99
+ for i, key in ipairs(keys) do
100
+ redis.call('DECRBY', key, tonumber(quantities[i]))
101
+ end
102
+
103
+ return 1
104
+ LUA
105
+ end
106
+
107
+ def restock_redis_script
108
+ <<~LUA
109
+ local keys = KEYS
110
+ local quantities = ARGV
111
+
112
+ -- Apply restock updates
113
+ for i, key in ipairs(keys) do
114
+ redis.call('INCRBY', key, tonumber(quantities[i]))
115
+ end
116
+
117
+ return 1
118
+ LUA
119
+ end
120
+
121
+ def schedule_sync_inventory(inventory_id_and_quantities)
122
+ SpreeCmCommissioner::InventoryItemSyncerJob.perform_later(inventory_id_and_quantities:)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,36 @@
1
+ module SpreeCmCommissioner
2
+ module RedisStock
3
+ class LineItemsCachedInventoryItemsBuilder
4
+ attr_reader :line_item_ids
5
+
6
+ def initialize(line_item_ids:)
7
+ @line_item_ids = line_item_ids
8
+ end
9
+
10
+ # return list of inventory items group by :line_item_id:
11
+ # {
12
+ # 1: [ CachedInventoryItem(...), CachedInventoryItem(...) ],
13
+ # 2: [ CachedInventoryItem(...), CachedInventoryItem(...) ],
14
+ # }
15
+ def call
16
+ cached_inventory_items.group_by do |cached_inventory_item|
17
+ line_item = line_items.find { |item| item.variant_id == cached_inventory_item.variant_id }
18
+ line_item.id
19
+ end
20
+ end
21
+
22
+ def cached_inventory_items
23
+ @cached_inventory_items ||= SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(inventory_items)
24
+ .call
25
+ end
26
+
27
+ def inventory_items
28
+ @inventory_items ||= line_items.flat_map(&:inventory_items)
29
+ end
30
+
31
+ def line_items
32
+ @line_items ||= Spree::LineItem.where(id: line_item_ids).includes(:inventory_items)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ module SpreeCmCommissioner
2
+ module RedisStock
3
+ class VariantCachedInventoryItemsBuilder
4
+ attr_reader :variant_id, :dates
5
+
6
+ def initialize(variant_id:, dates: [])
7
+ @variant_id = variant_id
8
+ @dates = dates
9
+ end
10
+
11
+ # output: [ CachedInventoryItem(...), CachedInventoryItem(...) ]
12
+ def call
13
+ ::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(inventory_items).call
14
+ end
15
+
16
+ def inventory_items
17
+ variant = Spree::Variant.find(variant_id)
18
+
19
+ inventory_items = variant.inventory_items
20
+ inventory_items = inventory_items.where(inventory_date: dates) if variant.permanent_stock?
21
+ inventory_items
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ module SpreeCmCommissioner
2
+ class ReservedBlock < Base
3
+ has_paper_trail
4
+
5
+ belongs_to :updated_by, optional: true, class_name: 'Spree::User'
6
+ belongs_to :guest, optional: true, class_name: 'SpreeCmCommissioner::Guest'
7
+ belongs_to :inventory_item, class_name: 'SpreeCmCommissioner::InventoryItem'
8
+ belongs_to :block, class_name: 'SpreeCmCommissioner::Block'
9
+
10
+ enum status: {
11
+ reserved: 0, # confirmed reservation
12
+ on_hold: 1, # temporary hold
13
+ canceled: 2
14
+ }
15
+
16
+ validates :status, presence: true
17
+ validates :expired_at, presence: true, if: -> { on_hold? }
18
+
19
+ scope :reserved_or_on_hold, -> { where(status: %i[reserved on_hold]).where('expired_at IS NULL OR expired_at > ?', Time.current) }
20
+ scope :active_on_hold, -> { on_hold.where('expired_at IS NULL OR expired_at > ?', Time.current) }
21
+ scope :active, -> { where('expired_at IS NULL OR expired_at > ?', Time.current) }
22
+
23
+ def expired?
24
+ expired_at.present? && expired_at <= Time.current
25
+ end
26
+
27
+ def active_on_hold?
28
+ on_hold? && !expired?
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # Reusable guest profile, can be linked to a user or be nil for anonymous checkouts.
2
+ # Orders associate to saved_guest through guests. If an order later associates to a user, the saved_guest is assigned to that user.
3
+ module SpreeCmCommissioner
4
+ class SavedGuest < Base
5
+ include SpreeCmCommissioner::PhoneNumberSanitizer
6
+
7
+ enum gender: { :other => 0, :male => 1, :female => 2 }
8
+
9
+ belongs_to :user, class_name: 'Spree::User', optional: true
10
+ belongs_to :occupation, class_name: 'Spree::Taxon', optional: true
11
+ belongs_to :nationality, class_name: 'Spree::Taxon', optional: true
12
+
13
+ has_many :guests, class_name: 'SpreeCmCommissioner::Guest', dependent: :nullify
14
+
15
+ accepts_nested_attributes_for :guests, allow_destroy: true
16
+
17
+ def full_name
18
+ [first_name, last_name].compact_blank.join(' ')
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module SpreeCmCommissioner
2
+ class SeatLayout < Base
3
+ has_many :seat_sections, class_name: 'SpreeCmCommissioner::SeatSection', dependent: :destroy
4
+
5
+ has_many :top_level_blocks, -> { where(seat_section_id: nil) }, class_name: 'SpreeCmCommissioner::Block', dependent: :destroy
6
+ has_many :section_blocks, -> { where.not(seat_section_id: nil) }, class_name: 'SpreeCmCommissioner::Block'
7
+
8
+ belongs_to :layoutable, polymorphic: true
9
+
10
+ enum status: {
11
+ inactive: 0,
12
+ active: 1,
13
+ archived: 2
14
+ }
15
+
16
+ validates :name, presence: true
17
+
18
+ accepts_nested_attributes_for :seat_sections, :top_level_blocks
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module SpreeCmCommissioner
2
+ class SeatSection < Base
3
+ belongs_to :seat_layout, class_name: 'SpreeCmCommissioner::SeatLayout', optional: false
4
+
5
+ has_many :blocks, class_name: 'SpreeCmCommissioner::Block', dependent: :destroy
6
+
7
+ validates :name, presence: true
8
+ validates :width, presence: true, numericality: { greater_than: 0 }
9
+ validates :height, presence: true, numericality: { greater_than: 0 }
10
+ validates :x, presence: true, numericality: true
11
+ validates :y, presence: true, numericality: true
12
+ validates :rotation, presence: true, numericality: { greater_than_or_equal_to: -360, less_than_or_equal_to: 360 }
13
+
14
+ accepts_nested_attributes_for :blocks
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ module SpreeCmCommissioner
2
+ module Seats
3
+ class BlocksCanceler
4
+ def initialize(order_id:, cancel_by: nil)
5
+ @order_id = order_id
6
+ @cancel_by = cancel_by
7
+ end
8
+
9
+ # Cancels all reserved blocks associated with a given order.
10
+ #
11
+ # Typically used when:
12
+ # - The order is canceled.
13
+ # - The user abandons the order before completing checkout.
14
+ def cancel_blocks!
15
+ order = Spree::Order.find(@order_id)
16
+
17
+ ActiveRecord::Base.transaction do
18
+ order.reserved_blocks.each do |reserved_block|
19
+ reserved_block.assign_attributes(
20
+ status: :canceled,
21
+ expired_at: nil,
22
+ updated_by: @cancel_by
23
+ )
24
+ raise Errors::UnableToSaveReservedBlockRecord unless reserved_block.save
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,53 @@
1
+ module SpreeCmCommissioner
2
+ module Seats
3
+ class BlocksHolder
4
+ HOLD_DURATION = 8.minutes
5
+
6
+ def initialize(line_item_ids:, hold_by: nil)
7
+ @line_item_ids = line_item_ids
8
+ @hold_by = hold_by
9
+ end
10
+
11
+ # Hold blocks for the given line items, preventing dublicate block selection.
12
+ #
13
+ # It ensures:
14
+ # - Raises an error if already reserved.
15
+ # - Raises an error if currently on_hold by someone else.
16
+ # - Allows re-locking if already on_hold by the same guest.
17
+ # - Otherwise, proceeds to lock the given blocks.
18
+ def hold_blocks!
19
+ line_items = Spree::LineItem.where(id: @line_item_ids).includes(:inventory_items, guests_with_blocks: :block)
20
+ guests_with_blocks = line_items.flat_map(&:guests_with_blocks).compact
21
+ inventory_items = line_items.flat_map(&:inventory_items).compact
22
+
23
+ ActiveRecord::Base.transaction do
24
+ guests_with_blocks.each do |guest|
25
+ inventory_items.each do |inventory_item|
26
+ hold_specific_block!(inventory_item, guest)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def hold_specific_block!(inventory_item, guest)
35
+ reserved_block = SpreeCmCommissioner::ReservedBlock.find_or_initialize_by(inventory_item: inventory_item, block: guest.block)
36
+
37
+ raise Errors::BlocksAreReservedByOtherGuest if reserved_block.reserved? && reserved_block.guest_id != guest.id
38
+ raise Errors::BlocksAreReservedBySameGuest if reserved_block.reserved? && reserved_block.guest_id == guest.id
39
+ raise Errors::BlocksAreOnHoldByOtherGuest if reserved_block.active_on_hold? && reserved_block.guest_id != guest.id
40
+
41
+ reserved_block.assign_attributes(
42
+ status: :on_hold,
43
+ guest: guest,
44
+ expired_at: HOLD_DURATION.from_now,
45
+ inventory_date: inventory_item.inventory_date,
46
+ updated_by: @hold_by
47
+ )
48
+
49
+ raise Errors::UnableToSaveReservedBlockRecord unless reserved_block.save
50
+ end
51
+ end
52
+ end
53
+ end