spree_cm_commissioner 2.0.0 → 2.0.1.pre.pre

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 +16 -2
  3. data/.gitignore +2 -1
  4. data/Gemfile.lock +32 -1
  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/stock_managements_controller.rb +63 -1
  9. data/app/controllers/spree/api/v2/organizer/invite_guests_controller.rb +1 -1
  10. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +42 -0
  11. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +14 -31
  12. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +13 -13
  13. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  14. data/app/factory/spree_cm_commissioner/invite_guest_claimed_telegram_message_factory.rb +81 -23
  15. data/app/finders/spree_cm_commissioner/accommodations/find.rb +37 -0
  16. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +32 -0
  17. data/app/interactors/spree_cm_commissioner/create_ticket.rb +5 -6
  18. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +40 -0
  19. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +25 -0
  20. data/app/interactors/spree_cm_commissioner/pin_code_sender.rb +1 -0
  21. data/app/interactors/spree_cm_commissioner/sms.rb +1 -1
  22. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +44 -0
  23. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +13 -0
  24. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +15 -0
  25. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +75 -0
  26. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +32 -0
  27. data/app/interactors/spree_cm_commissioner/user_id_token_authenticator.rb +3 -7
  28. data/app/interactors/spree_cm_commissioner/user_id_token_checker.rb +3 -11
  29. data/app/interactors/spree_cm_commissioner/user_identity_checker.rb +6 -12
  30. data/app/interactors/spree_cm_commissioner/user_registration_with_id_token.rb +1 -7
  31. data/app/jobs/spree_cm_commissioner/application_job.rb +20 -0
  32. data/app/jobs/spree_cm_commissioner/application_unique_job.rb +20 -0
  33. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +7 -0
  34. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +7 -0
  35. data/app/jobs/spree_cm_commissioner/sms_pin_code_job.rb +1 -1
  36. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +11 -0
  37. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +11 -0
  38. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +9 -0
  39. data/app/mailers/spree/order_mailer_decorator.rb +3 -18
  40. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +9 -15
  41. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +1 -12
  42. data/app/models/concerns/spree_cm_commissioner/order_seatable.rb +44 -0
  43. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +39 -0
  44. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +1 -3
  45. data/app/models/concerns/spree_cm_commissioner/product_type.rb +10 -0
  46. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +1 -1
  47. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +0 -1
  48. data/app/models/spree_cm_commissioner/block.rb +23 -0
  49. data/app/models/spree_cm_commissioner/dynamic_field.rb +0 -20
  50. data/app/models/spree_cm_commissioner/guest.rb +18 -56
  51. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +1 -41
  52. data/app/models/spree_cm_commissioner/inventory.rb +11 -0
  53. data/app/models/spree_cm_commissioner/inventory_item.rb +69 -0
  54. data/app/models/spree_cm_commissioner/line_item_decorator.rb +46 -78
  55. data/app/models/spree_cm_commissioner/notification_taxon.rb +1 -1
  56. data/app/models/spree_cm_commissioner/option_type_decorator.rb +1 -11
  57. data/app/models/spree_cm_commissioner/order_decorator.rb +30 -1
  58. data/app/models/spree_cm_commissioner/place.rb +1 -4
  59. data/app/models/spree_cm_commissioner/price_decorator.rb +9 -0
  60. data/app/models/spree_cm_commissioner/product_decorator.rb +10 -28
  61. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +41 -0
  62. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +126 -0
  63. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +36 -0
  64. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +25 -0
  65. data/app/models/spree_cm_commissioner/reserved_block.rb +30 -0
  66. data/app/models/spree_cm_commissioner/seat_layout.rb +20 -0
  67. data/app/models/spree_cm_commissioner/seat_section.rb +16 -0
  68. data/app/models/spree_cm_commissioner/seats/blocks_canceler.rb +30 -0
  69. data/app/models/spree_cm_commissioner/seats/blocks_holder.rb +53 -0
  70. data/app/models/spree_cm_commissioner/seats/blocks_reserver.rb +49 -0
  71. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_on_hold_by_other_guest.rb +4 -0
  72. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_other_guest.rb +4 -0
  73. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_same_guest.rb +4 -0
  74. data/app/models/spree_cm_commissioner/seats/errors/unable_to_save_reserved_block_record.rb +4 -0
  75. data/app/models/spree_cm_commissioner/service_calendar.rb +0 -2
  76. data/app/models/spree_cm_commissioner/state_decorator.rb +0 -1
  77. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +26 -25
  78. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +2 -1
  79. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  80. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +44 -0
  81. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +17 -0
  82. data/app/models/spree_cm_commissioner/taxon_decorator.rb +0 -1
  83. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +0 -6
  84. data/app/models/spree_cm_commissioner/trip.rb +8 -10
  85. data/app/models/spree_cm_commissioner/trip_connection.rb +5 -5
  86. data/app/models/spree_cm_commissioner/trip_stop.rb +6 -25
  87. data/app/models/spree_cm_commissioner/user_identity_provider.rb +4 -26
  88. data/app/models/spree_cm_commissioner/variant_block.rb +9 -0
  89. data/app/models/spree_cm_commissioner/variant_decorator.rb +37 -47
  90. data/app/models/spree_cm_commissioner/variant_options.rb +0 -23
  91. data/app/models/spree_cm_commissioner/vehicle.rb +9 -14
  92. data/app/models/spree_cm_commissioner/vendor_decorator.rb +11 -17
  93. data/app/models/spree_cm_commissioner/vendor_place.rb +9 -3
  94. data/app/queries/spree_cm_commissioner/trip_query.rb +70 -44
  95. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +3 -0
  96. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  97. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +19 -0
  98. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +2 -0
  99. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +1 -0
  100. data/app/serializers/spree/v2/tenant/user_serializer.rb +0 -1
  101. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +1 -1
  102. data/app/services/spree_cm_commissioner/user_authenticator.rb +1 -1
  103. data/app/views/spree/admin/inventory_items/show.html.erb +72 -0
  104. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +7 -2
  105. data/app/views/spree/admin/stock_managements/calendar.html.erb +56 -0
  106. data/app/views/spree/admin/stock_managements/index.html.erb +55 -6
  107. data/app/views/spree/admin/tenant_vendors/index.html.erb +2 -9
  108. data/app/views/spree/admin/tenants/_form.html.erb +0 -9
  109. data/app/views/spree/admin/tenants/edit.html.erb +1 -2
  110. data/app/views/spree/admin/tenants/index.html.erb +1 -7
  111. data/app/views/spree/admin/vendors/_form.html.erb +0 -14
  112. data/app/views/spree/order_mailer/confirm_email.html.erb +16 -27
  113. data/app/views/spree_cm_commissioner/layouts/order_mailer.html.erb +1 -5
  114. data/app/views/spree_cm_commissioner/order_mailer/_mailer_stylesheets.html.erb +4 -41
  115. data/config/initializers/paper_trail.rb +1 -0
  116. data/config/initializers/spree_permitted_attributes.rb +5 -0
  117. data/config/locales/en.yml +1 -5
  118. data/config/routes.rb +21 -2
  119. data/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb +3 -1
  120. data/db/migrate/20250304293518_create_cm_inventory_items.rb +21 -0
  121. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +5 -0
  122. data/db/migrate/20250502025848_add_index_to_spree_products.rb +5 -0
  123. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +5 -0
  124. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +5 -0
  125. data/db/migrate/20250603035256_add_inventory_item_to_spree_prices.rb +7 -0
  126. data/db/migrate/20250619073724_drop_table_cm_line_item_seats.rb +5 -0
  127. data/db/migrate/20250619073812_drop_table_cm_vehicle_seats.rb +5 -0
  128. data/db/migrate/20250619073844_drop_table_cm_vehicle_types.rb +9 -0
  129. data/db/migrate/20250619073957_drop_table_cm_option_value_vehicle_types.rb +5 -0
  130. data/db/migrate/20250619082354_remove_unnecessary_fields_from_cm_places.rb +9 -0
  131. data/db/migrate/20250619082736_remove_route_type_from_spree_products.rb +5 -0
  132. data/db/migrate/20250619083055_remove_unnecessary_fields_from_spree_taxons.rb +5 -0
  133. data/db/migrate/20250620083055_remove_variant_id_from_cm_trips.rb +5 -0
  134. data/db/migrate/20250620090000_update_cm_trip_connections_to_use_cm_trips.rb +6 -0
  135. data/db/migrate/20250620090001_create_cm_seat_layouts.rb +17 -0
  136. data/db/migrate/20250620090002_create_cm_seat_sections.rb +18 -0
  137. data/db/migrate/20250620090003_create_cm_blocks.rb +18 -0
  138. data/db/migrate/20250624091005_create_cm_reserved_blocks.rb +29 -0
  139. data/db/migrate/20250626083642_create_cm_variant_blocks.rb +24 -0
  140. data/db/migrate/20250627023314_add_block_id_to_cm_guests.rb +13 -0
  141. data/db/migrate/20250716022821_add_location_reference_to_cm_vendor_places.rb +5 -0
  142. data/db/migrate/20250716031743_drop_table_cm_vendor_stops.rb +5 -0
  143. data/db/migrate/20250717023824_add_vendor_reference_to_cm_trips.rb +5 -0
  144. data/db/migrate/20250717041414_add_location_place_reference_to_cm_trip_stops.rb +5 -0
  145. data/db/migrate/20250717042539_rename_cm_trip_stops_stop_id_column_to_stop_place_id.rb +7 -0
  146. data/db/migrate/20250717042707_rename_cm_trips_origin_and_destination_to_origin_place_and_destination_place.rb +11 -0
  147. data/docker-compose.yml +1 -1
  148. data/lib/cm_app_logger.rb +11 -4
  149. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +14 -11
  150. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_cm_commissioner → spree_dashboard/spree_cm_commissioner}/utilities.js +4 -0
  151. data/lib/spree_cm_commissioner/cached_inventory_item.rb +23 -0
  152. data/lib/spree_cm_commissioner/calendar_event.rb +11 -1
  153. data/lib/spree_cm_commissioner/test_helper/factories/block_factory.rb +15 -0
  154. data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +10 -0
  155. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  156. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +9 -0
  157. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  158. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +6 -30
  159. data/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb +0 -36
  160. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +18 -34
  161. data/lib/spree_cm_commissioner/test_helper/factories/reserved_block_factory.rb +27 -0
  162. data/lib/spree_cm_commissioner/test_helper/factories/seat_layout_factory.rb +11 -0
  163. data/lib/spree_cm_commissioner/test_helper/factories/seat_section_factory.rb +16 -0
  164. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  165. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +6 -0
  166. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +11 -3
  167. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +10 -0
  168. data/lib/spree_cm_commissioner/test_helper/factories/user_identity_provider_factory.rb +1 -1
  169. data/lib/spree_cm_commissioner/test_helper/factories/variant_block_factory.rb +7 -0
  170. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +41 -19
  171. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb +1 -1
  172. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +6 -1
  173. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +13 -1
  174. data/lib/spree_cm_commissioner/trip_query_result.rb +8 -0
  175. data/lib/spree_cm_commissioner/trip_result.rb +5 -3
  176. data/lib/spree_cm_commissioner/version.rb +1 -1
  177. data/lib/spree_cm_commissioner.rb +35 -1
  178. data/lib/tasks/create_default_non_permanent_inventory_items.rake +16 -0
  179. data/lib/tasks/ensure_correct_product_type.rake +7 -0
  180. data/lib/tasks/generate_inventory_items.rake +7 -0
  181. data/spree_cm_commissioner.gemspec +7 -0
  182. metadata +141 -38
  183. data/app/assets/images/mailer/mail.png +0 -0
  184. data/app/assets/images/mailer/tenant_phone.png +0 -0
  185. data/app/assets/images/mailer/tenant_user.png +0 -0
  186. data/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb +0 -44
  187. data/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb +0 -20
  188. data/app/models/spree_cm_commissioner/branch.rb +0 -12
  189. data/app/models/spree_cm_commissioner/line_item_seat.rb +0 -10
  190. data/app/models/spree_cm_commissioner/option_value_vehicle_type.rb +0 -8
  191. data/app/models/spree_cm_commissioner/stop.rb +0 -23
  192. data/app/models/spree_cm_commissioner/vehicle_seat.rb +0 -11
  193. data/app/models/spree_cm_commissioner/vehicle_type.rb +0 -76
  194. data/app/models/spree_cm_commissioner/vendor_stop.rb +0 -10
  195. data/app/queries/spree_cm_commissioner/trip_search_query.rb +0 -76
  196. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +0 -45
  197. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +0 -55
  198. data/app/queries/spree_cm_commissioner/vendor_stop_place_query.rb +0 -54
  199. data/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb +0 -11
  200. data/app/views/spree_cm_commissioner/order_mailer/tenant/_customer_info.html.erb +0 -42
  201. data/app/views/spree_cm_commissioner/order_mailer/tenant/_footer.html.erb +0 -25
  202. data/app/views/spree_cm_commissioner/order_mailer/tenant/_greeting.html.erb +0 -19
  203. data/app/views/spree_cm_commissioner/order_mailer/tenant/_support_contact.html.erb +0 -33
  204. data/db/migrate/20250709073455_add_email_fields_to_spree_vendors.rb +0 -6
  205. data/db/migrate/20250715103333_remove_indexes_from_cm_user_identity_providers.rb +0 -13
  206. data/db/migrate/20250718071620_add_data_fill_stage_to_dynamic_fields.rb +0 -5
  207. data/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb +0 -12
  208. data/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb +0 -8
  209. data/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb +0 -8
  210. data/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb +0 -7
  211. data/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb +0 -14
  212. data/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb +0 -8
  213. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb +0 -8
  214. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +0 -96
  215. data/lib/spree_cm_commissioner/trip_seat_layout_result.rb +0 -11
@@ -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', dependent: :destroy
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
@@ -0,0 +1,49 @@
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
@@ -0,0 +1,4 @@
1
+ module SpreeCmCommissioner
2
+ class Seats::Errors::BlocksAreOnHoldByOtherGuest < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module SpreeCmCommissioner
2
+ class Seats::Errors::BlocksAreReservedByOtherGuest < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module SpreeCmCommissioner
2
+ class Seats::Errors::BlocksAreReservedBySameGuest < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module SpreeCmCommissioner
2
+ class Seats::Errors::UnableToSaveReservedBlockRecord < StandardError
3
+ end
4
+ end
@@ -1,5 +1,3 @@
1
- require_dependency 'spree_cm_commissioner'
2
-
3
1
  module SpreeCmCommissioner
4
2
  class ServiceCalendar < SpreeCmCommissioner::Base
5
3
  # exception_rules = [
@@ -3,7 +3,6 @@ module SpreeCmCommissioner
3
3
  def self.prepended(base)
4
4
  base.whitelisted_ransackable_attributes |= %w[name abbr country_id]
5
5
  base.has_many :vendors, foreign_key: 'default_state_id', class_name: 'Spree::Vendor', inverse_of: :default_state, dependent: :nullify
6
- base.has_many :stops, class_name: 'SpreeCmCommissioner::Place'
7
6
 
8
7
  def update_total_inventory
9
8
  update(total_inventory: vendors.pluck(:total_inventory).compact.sum)
@@ -1,47 +1,48 @@
1
1
  module SpreeCmCommissioner
2
2
  module Stock
3
3
  class AvailabilityChecker
4
- attr_reader :variant, :error_message
4
+ attr_reader :variant, :options, :error_message
5
5
 
6
- def initialize(variant)
6
+ def initialize(variant, options = {})
7
7
  @variant = variant
8
+ @options = options
8
9
  @error_message = nil
9
10
  end
10
11
 
11
- def can_supply?(quantity = 1, options = {})
12
+ def can_supply?(quantity = 1)
12
13
  return false unless variant.available?
13
14
  return true unless variant.should_track_inventory?
14
15
  return true if variant.backorderable?
15
16
  return true if variant.need_confirmation?
16
17
 
17
- # when delivery required, shipment will dynamically add / remove unit from stock item.
18
- # so we can directly check can_supply with stock items directly.
19
- return variant.stock_items.sum(:count_on_hand) >= quantity if variant.delivery_required?
18
+ variant_available?(quantity)
19
+ end
20
20
 
21
- if variant.permanent_stock?
22
- permanent_stock_variant_available?(quantity, options)
23
- else
24
- variant_available?(quantity, options)
21
+ def variant_available?(quantity = 1)
22
+ return false if cached_inventory_items.empty?
23
+
24
+ cached_inventory_items.all? do |cached_inventory_item|
25
+ cached_inventory_item.active? && cached_inventory_item.quantity_available >= quantity
25
26
  end
26
27
  end
27
28
 
28
- def variant_available?(quantity = 1, options = {})
29
- query = SpreeCmCommissioner::VariantAvailability::NonPermanentStockQuery.new(
30
- variant: variant,
31
- except_line_item_id: options[:except_line_item_id]
32
- )
33
- result = query.available?(quantity)
34
- @error_message = query.error_message unless result
35
- result
29
+ def cached_inventory_items
30
+ return @cached_inventory_items if defined?(@cached_inventory_items)
31
+
32
+ if variant.permanent_stock?
33
+ return [] if options[:from_date].blank? || options[:to_date].blank?
34
+
35
+ dates = options[:from_date].to_date..options[:to_date].to_date
36
+ @cached_inventory_items = builder_klass.new(variant_id: variant.id, dates: dates).call
37
+ @cached_inventory_items = [] if @cached_inventory_items.size != dates.count
38
+ @cached_inventory_items
39
+ else
40
+ @cached_inventory_items = builder_klass.new(variant_id: variant.id).call
41
+ end
36
42
  end
37
43
 
38
- def permanent_stock_variant_available?(quantity = 1, options = {})
39
- SpreeCmCommissioner::VariantAvailability::PermanentStockQuery.new(
40
- variant: variant,
41
- from_date: options[:from_date].to_date,
42
- to_date: options[:to_date].to_date,
43
- except_line_item_id: options[:except_line_item_id]
44
- ).available?(quantity)
44
+ def builder_klass
45
+ ::SpreeCmCommissioner::RedisStock::VariantCachedInventoryItemsBuilder
45
46
  end
46
47
  end
47
48
  end
@@ -3,7 +3,8 @@ module SpreeCmCommissioner
3
3
  module AvailabilityValidatorDecorator
4
4
  # override
5
5
  def item_available?(line_item, quantity)
6
- SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(line_item).can_supply?(quantity)
6
+ SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(line_item)
7
+ .can_supply?(quantity)
7
8
  end
8
9
  end
9
10
  end
@@ -8,14 +8,14 @@ module SpreeCmCommissioner
8
8
  end
9
9
 
10
10
  def can_supply?(quantity)
11
- AvailabilityChecker.new(line_item.variant).can_supply?(quantity, options)
11
+ ::SpreeCmCommissioner::Stock::AvailabilityChecker.new(line_item.variant, options)
12
+ .can_supply?(quantity)
12
13
  end
13
14
 
14
15
  def options
15
16
  {
16
17
  from_date: line_item.from_date,
17
- to_date: line_item.to_date,
18
- except_line_item_id: line_item.id
18
+ to_date: line_item.to_date
19
19
  }
20
20
  end
21
21
  end
@@ -0,0 +1,44 @@
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
@@ -3,11 +3,28 @@ module SpreeCmCommissioner
3
3
  def self.prepended(base)
4
4
  base.has_one :vendor, through: :variant
5
5
  base.after_save :update_vendor_total_inventory, if: :saved_change_to_count_on_hand?
6
+
7
+ base.after_commit :create_inventory_items, on: :create
8
+ base.after_commit :adjust_inventory_items_async, on: :destroy
6
9
  end
7
10
 
8
11
  def update_vendor_total_inventory
9
12
  SpreeCmCommissioner::VendorJob.perform_later(vendor.id) if vendor.present?
10
13
  end
14
+
15
+ private
16
+
17
+ def create_inventory_items
18
+ SpreeCmCommissioner::Stock::InventoryItemsGeneratorJob.perform_later(variant_id: variant.id)
19
+ end
20
+
21
+ # When admin delete stock item, it will deduct stock from inventory item
22
+ def adjust_inventory_items_async
23
+ params = { variant_id: variant.id, quantity: -count_on_hand }
24
+ CmAppLogger.log(label: "#{self.class.name}#adjust_inventory_items_async", data: params) do
25
+ SpreeCmCommissioner::Stock::InventoryItemsAdjusterJob.perform_later(**params)
26
+ end
27
+ end
11
28
  end
12
29
  end
13
30
 
@@ -2,7 +2,6 @@ module SpreeCmCommissioner
2
2
  module TaxonDecorator
3
3
  def self.prepended(base) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
4
4
  base.include SpreeCmCommissioner::TaxonKind
5
- base.include SpreeCmCommissioner::Transit::TaxonBitwise
6
5
  base.include SpreeCmCommissioner::ParticipationTypeBitwise
7
6
  base.include SpreeCmCommissioner::EventCheckInFlowable
8
7
 
@@ -8,12 +8,6 @@ module SpreeCmCommissioner
8
8
  Spree::Taxonomy.find_or_create_by(name: 'Businesses', kind: 'category', store: Spree::Store.default)
9
9
  end
10
10
  end
11
-
12
- def base.place
13
- ActiveRecord::Base.connected_to(role: :writing) do
14
- Spree::Taxonomy.find_or_create_by(name: 'Place', kind: 'transit', store: Spree::Store.default)
15
- end
16
- end
17
11
  end
18
12
  end
19
13
  end
@@ -1,14 +1,15 @@
1
- require_dependency 'spree_cm_commissioner'
2
1
  module SpreeCmCommissioner
3
- class Trip < SpreeCmCommissioner::Base
2
+ class Trip < Base
4
3
  attr_accessor :hours, :minutes, :seconds
5
4
 
6
5
  before_validation :convert_duration_to_seconds
7
6
 
7
+ belongs_to :vendor, class_name: 'Spree::Vendor', inverse_of: :trips, optional: false
8
8
  belongs_to :product, class_name: 'Spree::Product', inverse_of: :trip, optional: false
9
9
  belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle', optional: false
10
- belongs_to :origin, class_name: 'SpreeCmCommissioner::Place', optional: true
11
- belongs_to :destination, class_name: 'SpreeCmCommissioner::Place', optional: true
10
+
11
+ belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place', optional: false
12
+ belongs_to :destination_place, class_name: 'SpreeCmCommissioner::Place', optional: false
12
13
 
13
14
  has_many :trip_blazer_queries, as: :queryable, class_name: 'SpreeCmCommissioner::BlazerQueryable'
14
15
  has_many :blazer_queries, through: :trip_blazer_queries, source: :blazer_query, class_name: 'Blazer::Query'
@@ -16,16 +17,13 @@ module SpreeCmCommissioner
16
17
  has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy
17
18
  has_many :variants, through: :product
18
19
  has_many :inventory_items, through: :variants
19
- belongs_to :variant, class_name: 'Spree::Variant'
20
-
21
- belongs_to :route, class_name: 'Spree::Product'
22
- belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle'
23
- validates :departure_time, presence: true
24
20
 
25
21
  validates :departure_time, presence: true
26
22
  validates :duration, numericality: { greater_than: 0 }
27
23
  validate :origin_and_destination_cannot_be_the_same
28
24
 
25
+ accepts_nested_attributes_for :trip_stops, :product, allow_destroy: true
26
+
29
27
  def convert_duration_to_seconds
30
28
  return if hours.blank? && minutes.blank? && seconds.blank?
31
29
 
@@ -50,7 +48,7 @@ module SpreeCmCommissioner
50
48
  private
51
49
 
52
50
  def origin_and_destination_cannot_be_the_same
53
- return unless origin_id == destination_id
51
+ return unless origin_place_id == destination_place_id
54
52
 
55
53
  errors.add(:base, 'Origin and destination cannot be the same')
56
54
  end
@@ -1,7 +1,7 @@
1
1
  module SpreeCmCommissioner
2
- class TripConnection < ApplicationRecord
3
- belongs_to :from_trip, class_name: 'Spree::Variant'
4
- belongs_to :to_trip, class_name: 'Spree::Variant'
2
+ class TripConnection < Base
3
+ belongs_to :from_trip, class_name: 'SpreeCmCommissioner::Trip'
4
+ belongs_to :to_trip, class_name: 'SpreeCmCommissioner::Trip'
5
5
 
6
6
  validate :both_trip_cannot_be_the_same
7
7
  before_validation :calculate_connection_time_minutes
@@ -12,8 +12,8 @@ module SpreeCmCommissioner
12
12
  def calculate_connection_time_minutes
13
13
  return if from_trip.nil? || to_trip.nil?
14
14
 
15
- arrival_seconds = from_trip.trip.arrival_time.seconds_since_midnight
16
- departure_seconds = to_trip.trip.departure_time.seconds_since_midnight
15
+ arrival_seconds = from_trip.arrival_time.seconds_since_midnight
16
+ departure_seconds = to_trip.departure_time.seconds_since_midnight
17
17
 
18
18
  layover_seconds = departure_seconds - arrival_seconds
19
19
  layover_seconds += 86_400 if layover_seconds.negative?
@@ -1,36 +1,17 @@
1
- require_dependency 'spree_cm_commissioner'
2
1
  module SpreeCmCommissioner
3
- class TripStop < SpreeCmCommissioner::Base
2
+ class TripStop < Base
4
3
  acts_as_list column: :sequence, scope: :trip_id
5
4
  enum :stop_type, { boarding: 0, drop_off: 1 }
6
5
 
7
- belongs_to :trip, class_name: 'Spree::Variant'
8
- belongs_to :stop, class_name: 'SpreeCmCommissioner::Place'
6
+ belongs_to :trip, class_name: 'SpreeCmCommissioner::Trip', optional: false
7
+ belongs_to :stop_place, class_name: 'SpreeCmCommissioner::Place', optional: false
8
+ belongs_to :location_place, class_name: 'SpreeCmCommissioner::Place', optional: false
9
9
 
10
10
  before_validation :set_stop_name
11
- after_destroy :decrement_trip_count
12
- after_commit :create_vendor_stop
13
- validates :stop_id, uniqueness: { scope: :trip_id }
11
+ validates :stop_place_id, uniqueness: { scope: :trip_id }
14
12
 
15
13
  def set_stop_name
16
- self.stop_name = stop.name if stop.present?
17
- end
18
-
19
- def create_vendor_stop
20
- vendor_stop = vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).first_or_create
21
- vendor_stop.update(trip_count: vendor_stop.trip_count.to_i + 1)
22
- end
23
-
24
- private
25
-
26
- def decrement_trip_count
27
- vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).find_each do |vendor_stop|
28
- vendor_stop.update(trip_count: [vendor_stop.trip_count - 1, 0].max)
29
- end
30
- end
31
-
32
- def vendor
33
- Spree::Product.find(trip&.product_id).vendor
14
+ self.stop_name = stop_place.name if stop_place.present?
34
15
  end
35
16
  end
36
17
  end
@@ -3,17 +3,15 @@ module SpreeCmCommissioner
3
3
  enum identity_type: { :google => 0, :apple => 1, :facebook => 2, :telegram => 3, :vattanac_bank => 4 }
4
4
 
5
5
  belongs_to :user, class_name: Spree.user_class.to_s, optional: false
6
- has_one :tenant, through: :user
7
-
8
- has_many :user_identity_provider_telegram_bots
9
- has_many :telegram_bots, through: :user_identity_provider_telegram_bots
10
6
 
11
7
  validates :sub, presence: true
8
+ validates :sub, uniqueness: { scope: :identity_type }
9
+
12
10
  validates :identity_type, presence: true
13
11
  validates :identity_type, uniqueness: { scope: :user_id }
14
12
 
15
- # custom validation to ensure that the sub is unique per identity_type and spree_users.tenant
16
- validate :unique_sub_per_identity_type_and_tenant
13
+ has_many :user_identity_provider_telegram_bots
14
+ has_many :telegram_bots, through: :user_identity_provider_telegram_bots
17
15
 
18
16
  # sub is a telegram uid, which telegram considered a chatID if
19
17
  # user have /started with bot.
@@ -22,25 +20,5 @@ module SpreeCmCommissioner
22
20
 
23
21
  nil
24
22
  end
25
-
26
- private
27
-
28
- def unique_sub_per_identity_type_and_tenant
29
- return if user.nil? || sub.blank? || identity_type.blank?
30
-
31
- tenant_id = user.tenant_id
32
-
33
- # Check for duplicates in the same tenant
34
- scope = self.class
35
- .joins(:user)
36
- .where(sub: sub, identity_type: identity_type)
37
- .where(spree_users: { tenant_id: tenant_id })
38
-
39
- scope = scope.where.not(id: id) if persisted?
40
-
41
- return unless scope.exists?
42
-
43
- errors.add(:sub, 'must be unique per identity_type and tenant')
44
- end
45
23
  end
46
24
  end
@@ -0,0 +1,9 @@
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