spree_cm_commissioner 2.0.2.pre.pre → 2.0.3.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 (236) 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 +1 -28
  5. data/Rakefile +4 -33
  6. data/app/assets/images/mailer/mail.png +0 -0
  7. data/app/assets/images/mailer/tenant_phone.png +0 -0
  8. data/app/assets/images/mailer/tenant_user.png +0 -0
  9. data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +11 -8
  10. data/app/controllers/blazer/queries_controller_decorator.rb +11 -0
  11. data/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb +44 -0
  12. data/app/controllers/spree/admin/stock_managements_controller.rb +1 -63
  13. data/app/controllers/spree/api/v2/operator/event_charts_controller.rb +59 -0
  14. data/app/controllers/spree/api/v2/organizer/invite_guests_controller.rb +1 -1
  15. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +31 -14
  16. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +13 -13
  17. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  18. data/app/factory/spree_cm_commissioner/invite_guest_claimed_telegram_message_factory.rb +24 -81
  19. data/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb +20 -0
  20. data/app/interactors/spree_cm_commissioner/create_event.rb +5 -0
  21. data/app/interactors/spree_cm_commissioner/create_ticket.rb +6 -5
  22. data/app/interactors/spree_cm_commissioner/create_vendor.rb +44 -0
  23. data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +0 -1
  24. data/app/interactors/spree_cm_commissioner/reports_assigner.rb +17 -0
  25. data/app/interactors/spree_cm_commissioner/sms.rb +1 -1
  26. data/app/interactors/spree_cm_commissioner/user_id_token_authenticator.rb +7 -3
  27. data/app/interactors/spree_cm_commissioner/user_id_token_checker.rb +11 -3
  28. data/app/interactors/spree_cm_commissioner/user_identity_checker.rb +12 -6
  29. data/app/interactors/spree_cm_commissioner/user_registration_with_id_token.rb +7 -1
  30. data/app/jobs/spree_cm_commissioner/application_job.rb +0 -20
  31. data/app/jobs/spree_cm_commissioner/application_unique_job.rb +0 -20
  32. data/app/jobs/spree_cm_commissioner/reports_assigner_job.rb +7 -0
  33. data/app/jobs/spree_cm_commissioner/sms_pin_code_job.rb +1 -1
  34. data/app/mailers/spree/order_mailer_decorator.rb +18 -3
  35. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +33 -9
  36. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +12 -1
  37. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +0 -39
  38. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +3 -1
  39. data/app/models/concerns/spree_cm_commissioner/product_type.rb +0 -10
  40. data/app/models/concerns/spree_cm_commissioner/route_type.rb +1 -1
  41. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +1 -1
  42. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +1 -0
  43. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +4 -2
  44. data/app/models/spree_cm_commissioner/branch.rb +12 -0
  45. data/app/models/spree_cm_commissioner/dynamic_field.rb +20 -0
  46. data/app/models/spree_cm_commissioner/guest.rb +56 -18
  47. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +41 -1
  48. data/app/models/spree_cm_commissioner/line_item_decorator.rb +78 -46
  49. data/app/models/spree_cm_commissioner/line_item_seat.rb +10 -0
  50. data/app/models/spree_cm_commissioner/notification_taxon.rb +1 -1
  51. data/app/models/spree_cm_commissioner/option_type_decorator.rb +11 -1
  52. data/app/models/spree_cm_commissioner/option_value_vehicle_type.rb +8 -0
  53. data/app/models/spree_cm_commissioner/order_decorator.rb +1 -30
  54. data/app/models/spree_cm_commissioner/place.rb +4 -1
  55. data/app/models/spree_cm_commissioner/product_decorator.rb +28 -10
  56. data/app/models/spree_cm_commissioner/service_calendar.rb +2 -0
  57. data/app/models/spree_cm_commissioner/state_decorator.rb +1 -0
  58. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +25 -26
  59. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +1 -2
  60. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  61. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +0 -17
  62. data/app/models/spree_cm_commissioner/stop.rb +23 -0
  63. data/app/models/spree_cm_commissioner/taxon_decorator.rb +1 -0
  64. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +6 -0
  65. data/app/models/spree_cm_commissioner/trip.rb +10 -10
  66. data/app/models/spree_cm_commissioner/trip_connection.rb +5 -5
  67. data/app/models/spree_cm_commissioner/trip_stop.rb +25 -6
  68. data/app/models/spree_cm_commissioner/user_decorator.rb +6 -0
  69. data/app/models/spree_cm_commissioner/user_identity_provider.rb +26 -4
  70. data/app/models/spree_cm_commissioner/variant_decorator.rb +47 -37
  71. data/app/models/spree_cm_commissioner/variant_options.rb +23 -0
  72. data/app/models/spree_cm_commissioner/vehicle.rb +15 -10
  73. data/app/models/spree_cm_commissioner/vehicle_seat.rb +11 -0
  74. data/app/models/spree_cm_commissioner/vehicle_type.rb +76 -0
  75. data/app/models/spree_cm_commissioner/vendor_decorator.rb +17 -11
  76. data/app/models/spree_cm_commissioner/vendor_place.rb +3 -9
  77. data/app/models/spree_cm_commissioner/vendor_stop.rb +10 -0
  78. data/app/queries/spree_cm_commissioner/event_chart_queries.rb +179 -0
  79. data/app/queries/spree_cm_commissioner/trip_query.rb +44 -70
  80. data/app/queries/spree_cm_commissioner/trip_search_query.rb +76 -0
  81. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +45 -0
  82. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +55 -0
  83. data/app/queries/spree_cm_commissioner/vendor_stop_place_query.rb +54 -0
  84. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +0 -3
  85. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  86. data/app/serializables/spree_cm_commissioner/event_chart.rb +11 -0
  87. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +0 -2
  88. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +0 -1
  89. data/app/serializers/spree/v2/tenant/user_serializer.rb +1 -0
  90. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +1 -1
  91. data/app/services/spree_cm_commissioner/user_authenticator.rb +1 -1
  92. data/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb +11 -0
  93. data/app/views/blazer/queries/_content.html.erb +2 -0
  94. data/app/views/blazer/queries/_preset.html.erb +11 -0
  95. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +2 -7
  96. data/app/views/spree/admin/stock_managements/index.html.erb +6 -55
  97. data/app/views/spree/admin/tenant_vendors/index.html.erb +9 -2
  98. data/app/views/spree/admin/tenants/_form.html.erb +9 -0
  99. data/app/views/spree/admin/tenants/edit.html.erb +2 -1
  100. data/app/views/spree/admin/tenants/index.html.erb +7 -1
  101. data/app/views/spree/admin/vendors/_form.html.erb +14 -0
  102. data/app/views/spree/order_mailer/confirm_email.html.erb +27 -16
  103. data/app/views/spree_cm_commissioner/layouts/order_mailer.html.erb +5 -1
  104. data/app/views/spree_cm_commissioner/order_mailer/_mailer_stylesheets.html.erb +41 -4
  105. data/app/views/spree_cm_commissioner/order_mailer/tenant/_customer_info.html.erb +42 -0
  106. data/app/views/spree_cm_commissioner/order_mailer/tenant/_footer.html.erb +25 -0
  107. data/app/views/spree_cm_commissioner/order_mailer/tenant/_greeting.html.erb +19 -0
  108. data/app/views/spree_cm_commissioner/order_mailer/tenant/_support_contact.html.erb +33 -0
  109. data/config/initializers/spree_permitted_attributes.rb +0 -5
  110. data/config/locales/en.yml +5 -1
  111. data/config/routes.rb +3 -21
  112. data/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb +1 -3
  113. data/db/migrate/20250709073455_add_email_fields_to_spree_vendors.rb +6 -0
  114. data/db/migrate/20250710082132_add_preset_to_blazer_queries.rb +5 -0
  115. data/db/migrate/20250715103333_remove_indexes_from_cm_user_identity_providers.rb +13 -0
  116. data/db/migrate/20250718071620_add_data_fill_stage_to_dynamic_fields.rb +5 -0
  117. data/docker-compose.yml +1 -1
  118. data/lib/cm_app_logger.rb +4 -11
  119. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +11 -14
  120. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_dashboard/spree_cm_commissioner → spree_cm_commissioner}/utilities.js +0 -4
  121. data/lib/spree_cm_commissioner/calendar_event.rb +1 -11
  122. data/lib/spree_cm_commissioner/test_helper/factories/blazer_query_factory.rb +17 -0
  123. data/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb +12 -0
  124. data/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb +8 -0
  125. data/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb +8 -0
  126. data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +0 -10
  127. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  128. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  129. data/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb +7 -0
  130. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +30 -6
  131. data/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb +36 -0
  132. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +34 -18
  133. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  134. data/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb +14 -0
  135. data/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb +8 -0
  136. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +3 -11
  137. data/lib/spree_cm_commissioner/test_helper/factories/user_identity_provider_factory.rb +1 -1
  138. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +19 -41
  139. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb +1 -1
  140. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb +8 -0
  141. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +96 -0
  142. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -6
  143. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +1 -13
  144. data/lib/spree_cm_commissioner/trip_query_result.rb +0 -8
  145. data/lib/spree_cm_commissioner/trip_result.rb +3 -5
  146. data/lib/spree_cm_commissioner/trip_seat_layout_result.rb +11 -0
  147. data/lib/spree_cm_commissioner/version.rb +1 -1
  148. data/lib/spree_cm_commissioner.rb +1 -35
  149. data/spree_cm_commissioner.gemspec +0 -7
  150. metadata +46 -145
  151. data/app/controllers/spree/admin/inventory_items_controller.rb +0 -83
  152. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +0 -42
  153. data/app/finders/spree_cm_commissioner/accommodations/find.rb +0 -37
  154. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +0 -32
  155. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +0 -40
  156. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +0 -25
  157. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +0 -44
  158. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +0 -13
  159. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +0 -15
  160. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +0 -75
  161. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +0 -32
  162. data/app/interactors/spree_cm_commissioner/variant_block_updater.rb +0 -62
  163. data/app/interactors/spree_cm_commissioner/vehicle_updater.rb +0 -41
  164. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +0 -7
  165. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +0 -7
  166. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +0 -11
  167. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +0 -11
  168. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +0 -9
  169. data/app/models/concerns/spree_cm_commissioner/order_seatable.rb +0 -44
  170. data/app/models/concerns/spree_cm_commissioner/vehicle_type.rb +0 -11
  171. data/app/models/spree_cm_commissioner/block.rb +0 -39
  172. data/app/models/spree_cm_commissioner/inventory.rb +0 -11
  173. data/app/models/spree_cm_commissioner/inventory_item.rb +0 -69
  174. data/app/models/spree_cm_commissioner/price_decorator.rb +0 -9
  175. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +0 -41
  176. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +0 -126
  177. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +0 -36
  178. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +0 -25
  179. data/app/models/spree_cm_commissioner/reserved_block.rb +0 -30
  180. data/app/models/spree_cm_commissioner/seat_layout.rb +0 -20
  181. data/app/models/spree_cm_commissioner/seat_section.rb +0 -16
  182. data/app/models/spree_cm_commissioner/seats/blocks_canceler.rb +0 -30
  183. data/app/models/spree_cm_commissioner/seats/blocks_holder.rb +0 -53
  184. data/app/models/spree_cm_commissioner/seats/blocks_reserver.rb +0 -49
  185. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_on_hold_by_other_guest.rb +0 -4
  186. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_other_guest.rb +0 -4
  187. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_same_guest.rb +0 -4
  188. data/app/models/spree_cm_commissioner/seats/errors/unable_to_save_reserved_block_record.rb +0 -4
  189. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +0 -44
  190. data/app/models/spree_cm_commissioner/variant_block.rb +0 -9
  191. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +0 -19
  192. data/app/views/spree/admin/inventory_items/show.html.erb +0 -72
  193. data/app/views/spree/admin/stock_managements/calendar.html.erb +0 -56
  194. data/config/initializers/paper_trail.rb +0 -1
  195. data/db/migrate/20250122000000_move_route_type_from_vehicles_to_trips.rb +0 -7
  196. data/db/migrate/20250304293518_create_cm_inventory_items.rb +0 -21
  197. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +0 -5
  198. data/db/migrate/20250502025848_add_index_to_spree_products.rb +0 -5
  199. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +0 -5
  200. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +0 -5
  201. data/db/migrate/20250603035256_add_inventory_item_to_spree_prices.rb +0 -7
  202. data/db/migrate/20250619073724_drop_table_cm_line_item_seats.rb +0 -5
  203. data/db/migrate/20250619073812_drop_table_cm_vehicle_seats.rb +0 -5
  204. data/db/migrate/20250619073844_drop_table_cm_vehicle_types.rb +0 -9
  205. data/db/migrate/20250619073957_drop_table_cm_option_value_vehicle_types.rb +0 -5
  206. data/db/migrate/20250619082354_remove_unnecessary_fields_from_cm_places.rb +0 -9
  207. data/db/migrate/20250619082736_remove_route_type_from_spree_products.rb +0 -5
  208. data/db/migrate/20250619083055_remove_unnecessary_fields_from_spree_taxons.rb +0 -5
  209. data/db/migrate/20250620083055_remove_variant_id_from_cm_trips.rb +0 -5
  210. data/db/migrate/20250620090000_update_cm_trip_connections_to_use_cm_trips.rb +0 -6
  211. data/db/migrate/20250620090001_create_cm_seat_layouts.rb +0 -17
  212. data/db/migrate/20250620090002_create_cm_seat_sections.rb +0 -18
  213. data/db/migrate/20250620090003_create_cm_blocks.rb +0 -18
  214. data/db/migrate/20250624091005_create_cm_reserved_blocks.rb +0 -29
  215. data/db/migrate/20250626083642_create_cm_variant_blocks.rb +0 -24
  216. data/db/migrate/20250627023314_add_block_id_to_cm_guests.rb +0 -13
  217. data/db/migrate/20250716022821_add_location_reference_to_cm_vendor_places.rb +0 -5
  218. data/db/migrate/20250716031743_drop_table_cm_vendor_stops.rb +0 -5
  219. data/db/migrate/20250717023824_add_vendor_reference_to_cm_trips.rb +0 -5
  220. data/db/migrate/20250717041414_add_location_place_reference_to_cm_trip_stops.rb +0 -5
  221. data/db/migrate/20250717042539_rename_cm_trip_stops_stop_id_column_to_stop_place_id.rb +0 -7
  222. data/db/migrate/20250717042707_rename_cm_trips_origin_and_destination_to_origin_place_and_destination_place.rb +0 -11
  223. data/db/migrate/20250721080738_add_vehicle_type_to_cm_vehicles.rb +0 -7
  224. data/db/migrate/20250725092713_add_block_type_to_cm_blocks.rb +0 -5
  225. data/lib/spree_cm_commissioner/cached_inventory_item.rb +0 -23
  226. data/lib/spree_cm_commissioner/test_helper/factories/block_factory.rb +0 -15
  227. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +0 -9
  228. data/lib/spree_cm_commissioner/test_helper/factories/reserved_block_factory.rb +0 -27
  229. data/lib/spree_cm_commissioner/test_helper/factories/seat_layout_factory.rb +0 -11
  230. data/lib/spree_cm_commissioner/test_helper/factories/seat_section_factory.rb +0 -16
  231. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +0 -6
  232. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +0 -10
  233. data/lib/spree_cm_commissioner/test_helper/factories/variant_block_factory.rb +0 -7
  234. data/lib/tasks/create_default_non_permanent_inventory_items.rake +0 -16
  235. data/lib/tasks/ensure_correct_product_type.rake +0 -7
  236. data/lib/tasks/generate_inventory_items.rake +0 -7
@@ -1,39 +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: -> { seatable? && other? }
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 self.seatable?(block_type)
28
- block_type.in?(%w[sleeping_seat seat])
29
- end
30
-
31
- def seatable?
32
- self.class.seatable?(block_type)
33
- end
34
-
35
- def assign_layout_from_section
36
- self.seat_layout = seat_section.seat_layout
37
- end
38
- end
39
- 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
@@ -1,126 +0,0 @@
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
@@ -1,36 +0,0 @@
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
@@ -1,25 +0,0 @@
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
@@ -1,30 +0,0 @@
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 :active_on_hold, -> { on_hold.where('expired_at IS NULL OR expired_at > ?', Time.current) }
20
- scope :active, -> { where('expired_at IS NULL OR expired_at > ?', Time.current) }
21
-
22
- def expired?
23
- expired_at.present? && expired_at <= Time.current
24
- end
25
-
26
- def active_on_hold?
27
- on_hold? && !expired?
28
- end
29
- end
30
- end
@@ -1,20 +0,0 @@
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
@@ -1,16 +0,0 @@
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
@@ -1,30 +0,0 @@
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
@@ -1,53 +0,0 @@
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
@@ -1,49 +0,0 @@
1
- module SpreeCmCommissioner
2
- module Seats
3
- class BlocksReserver
4
- def initialize(line_item_ids:, reserve_by: nil)
5
- @line_item_ids = line_item_ids
6
- @reserve_by = reserve_by
7
- end
8
-
9
- # Mark blocks as reserved for the given line items.
10
- #
11
- # It ensures:
12
- # - Raises an error if already reserved by same guest or other guest.
13
- # - Raises an error if currently on_hold by someone else.
14
- # - Otherwise, proceeds to reserve the given blocks.
15
- def reserve_blocks!
16
- line_items = Spree::LineItem.where(id: @line_item_ids).includes(:inventory_items, guests_with_blocks: :block)
17
- guests_with_blocks = line_items.flat_map(&:guests_with_blocks).compact
18
- inventory_items = line_items.flat_map(&:inventory_items).compact
19
-
20
- ActiveRecord::Base.transaction do
21
- guests_with_blocks.each do |guest|
22
- inventory_items.each do |inventory_item|
23
- reserve_specific_block!(inventory_item, guest)
24
- end
25
- end
26
- end
27
- end
28
-
29
- def reserve_specific_block!(inventory_item, guest)
30
- reserved_block = SpreeCmCommissioner::ReservedBlock.find_or_initialize_by(inventory_item_id: inventory_item.id, block_id: guest.block_id)
31
-
32
- raise Errors::BlocksAreReservedByOtherGuest if reserved_block.reserved? && reserved_block.guest_id != guest.id
33
- raise Errors::BlocksAreReservedBySameGuest if reserved_block.reserved? && reserved_block.guest_id == guest.id
34
- raise Errors::BlocksAreOnHoldByOtherGuest if reserved_block.active_on_hold? && reserved_block.guest_id != guest.id
35
-
36
- # mark the block as reserved if not on_hold or lock by anyone but already expired
37
- reserved_block.assign_attributes(
38
- guest: guest,
39
- status: :reserved,
40
- expired_at: nil,
41
- inventory_date: inventory_item.inventory_date,
42
- updated_by: @reserve_by
43
- )
44
-
45
- raise Errors::UnableToSaveReservedBlockRecord unless reserved_block.save
46
- end
47
- end
48
- end
49
- end
@@ -1,4 +0,0 @@
1
- module SpreeCmCommissioner
2
- class Seats::Errors::BlocksAreOnHoldByOtherGuest < StandardError
3
- end
4
- end
@@ -1,4 +0,0 @@
1
- module SpreeCmCommissioner
2
- class Seats::Errors::BlocksAreReservedByOtherGuest < StandardError
3
- end
4
- end
@@ -1,4 +0,0 @@
1
- module SpreeCmCommissioner
2
- class Seats::Errors::BlocksAreReservedBySameGuest < StandardError
3
- end
4
- end
@@ -1,4 +0,0 @@
1
- module SpreeCmCommissioner
2
- class Seats::Errors::UnableToSaveReservedBlockRecord < StandardError
3
- end
4
- end
@@ -1,44 +0,0 @@
1
- # On top of SpreeCmCommissioner::Stock::AvailabilityChecker, this class does the same thing but in bulk check with redis.
2
- # Which mean, it check basic condition with db first, once pass, it check with redis.
3
- module SpreeCmCommissioner
4
- module Stock
5
- class OrderAvailabilityChecker
6
- attr_reader :order
7
-
8
- def initialize(order)
9
- @order = order
10
- end
11
-
12
- def can_supply_all?
13
- insufficient_stock_lines.empty?
14
- end
15
-
16
- def insufficient_stock_lines
17
- cached_inventory_items_group_by_line_item_id.map do |line_item_id, cached_inventory_items|
18
- line_item = order.line_items.find { |item| item.id == line_item_id }
19
- line_item unless sufficient_stock_for?(line_item, cached_inventory_items)
20
- end.compact
21
- end
22
-
23
- # {
24
- # 1: [ {inventory_key: "inventory:1", active: true, quantity_available: 5, inventory_item_id: 1, variant_id: 1}],
25
- # 2: [ {inventory_key: "inventory:2", active: true, quantity_available: 5, inventory_item_id: 2, variant_id: 2}],
26
- # }
27
- def cached_inventory_items_group_by_line_item_id
28
- @cached_inventory_items_group_by_line_item_id ||=
29
- ::SpreeCmCommissioner::RedisStock::LineItemsCachedInventoryItemsBuilder.new(
30
- line_item_ids: order.line_items.pluck(:id)
31
- ).call
32
- end
33
-
34
- def sufficient_stock_for?(line_item, cached_inventory_items)
35
- return false unless line_item.variant.available?
36
- return true unless line_item.variant.should_track_inventory?
37
- return true if line_item.variant.backorderable?
38
- return true if line_item.variant.need_confirmation?
39
-
40
- cached_inventory_items.all? { |item| item.active? && item.quantity_available >= line_item.quantity }
41
- end
42
- end
43
- end
44
- end
@@ -1,9 +0,0 @@
1
- module SpreeCmCommissioner
2
- class VariantBlock < Base
3
- belongs_to :variant, class_name: 'Spree::Variant', optional: false
4
- belongs_to :trip, class_name: 'SpreeCmCommissioner::Trip', optional: false
5
- belongs_to :block, class_name: 'SpreeCmCommissioner::Block', optional: false
6
-
7
- validates :block_id, uniqueness: { scope: %i[variant_id trip_id] }
8
- end
9
- end
@@ -1,19 +0,0 @@
1
- module SpreeCmCommissioner
2
- class VariantRequestSchema < ApplicationRequestSchema
3
- params do
4
- required(:from_date).value(:date)
5
- required(:to_date).value(:date)
6
- required(:adult).value(:integer)
7
- required(:children).value(:integer)
8
- end
9
-
10
- rule(:from_date, :to_date) do
11
- from_date = values[:from_date]
12
- to_date = values[:to_date]
13
-
14
- key.failure(:must_be_in_future) if from_date < Time.zone.today
15
- key.failure(:must_be_later_than_start_date) if from_date > to_date
16
- key.failure(:stay_is_too_long) if (to_date - from_date).to_i > ENV.fetch('ACCOMMODATION_MAX_STAY_DAYS', 10).to_i
17
- end
18
- end
19
- end