spree_cm_commissioner 1.18.1 → 2.0.0.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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +0 -3
  3. data/.github/workflows/test_and_build_gem.yml +16 -2
  4. data/.gitignore +2 -1
  5. data/Gemfile.lock +28 -1
  6. data/Rakefile +33 -4
  7. data/app/assets/javascripts/spree_cm_commissioner/backend.js +0 -1
  8. data/app/assets/stylesheets/spree_cm_commissioner/backend/calendar.scss +8 -11
  9. data/app/assets/stylesheets/spree_cm_commissioner/backend/commissioner_admin.css.scss +0 -1
  10. data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +1 -2
  11. data/app/controllers/spree/admin/inventory_items_controller.rb +83 -0
  12. data/app/controllers/spree/admin/stock_managements_controller.rb +63 -1
  13. data/app/controllers/spree/admin/tenants_controller.rb +0 -8
  14. data/app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb +42 -0
  15. data/app/controllers/spree/api/v2/storefront/accommodations_controller.rb +14 -31
  16. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +5 -31
  17. data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +2 -2
  18. data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +1 -1
  19. data/app/controllers/spree_cm_commissioner/well_known_controller.rb +18 -20
  20. data/app/finders/spree_cm_commissioner/accommodations/find.rb +37 -0
  21. data/app/finders/spree_cm_commissioner/accommodations/find_variant.rb +32 -0
  22. data/app/interactors/spree_cm_commissioner/ensure_correct_product_type.rb +40 -0
  23. data/app/interactors/spree_cm_commissioner/inventory_item_syncer.rb +25 -0
  24. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +44 -0
  25. data/app/interactors/spree_cm_commissioner/stock/inventory_items_adjuster.rb +13 -0
  26. data/app/interactors/spree_cm_commissioner/stock/inventory_items_generator.rb +15 -0
  27. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +75 -0
  28. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +32 -0
  29. data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +1 -5
  30. data/app/jobs/spree_cm_commissioner/application_job.rb +20 -0
  31. data/app/jobs/spree_cm_commissioner/application_unique_job.rb +20 -0
  32. data/app/jobs/spree_cm_commissioner/ensure_correct_product_type_job.rb +7 -0
  33. data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +7 -0
  34. data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +11 -0
  35. data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +11 -0
  36. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +9 -0
  37. data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +9 -15
  38. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +1 -13
  39. data/app/models/concerns/spree_cm_commissioner/order_seatable.rb +44 -0
  40. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +39 -0
  41. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +1 -3
  42. data/app/models/concerns/spree_cm_commissioner/product_type.rb +10 -0
  43. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +1 -1
  44. data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +0 -4
  45. data/app/models/spree_cm_commissioner/block.rb +23 -0
  46. data/app/models/spree_cm_commissioner/dynamic_field.rb +3 -15
  47. data/app/models/spree_cm_commissioner/dynamic_field_option.rb +1 -5
  48. data/app/models/spree_cm_commissioner/guest.rb +19 -1
  49. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +3 -21
  50. data/app/models/spree_cm_commissioner/inventory.rb +11 -0
  51. data/app/models/spree_cm_commissioner/inventory_item.rb +69 -0
  52. data/app/models/spree_cm_commissioner/invite_team.rb +1 -3
  53. data/app/models/spree_cm_commissioner/line_item_decorator.rb +46 -78
  54. data/app/models/spree_cm_commissioner/notification_taxon.rb +1 -1
  55. data/app/models/spree_cm_commissioner/option_type_decorator.rb +1 -11
  56. data/app/models/spree_cm_commissioner/order_decorator.rb +30 -1
  57. data/app/models/spree_cm_commissioner/place.rb +1 -3
  58. data/app/models/spree_cm_commissioner/price_decorator.rb +9 -0
  59. data/app/models/spree_cm_commissioner/product_decorator.rb +10 -4
  60. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +41 -0
  61. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +126 -0
  62. data/app/models/spree_cm_commissioner/redis_stock/line_items_cached_inventory_items_builder.rb +36 -0
  63. data/app/models/spree_cm_commissioner/redis_stock/variant_cached_inventory_items_builder.rb +25 -0
  64. data/app/models/spree_cm_commissioner/reserved_block.rb +30 -0
  65. data/app/models/spree_cm_commissioner/seat_layout.rb +20 -0
  66. data/app/models/spree_cm_commissioner/seat_section.rb +16 -0
  67. data/app/models/spree_cm_commissioner/seats/blocks_canceler.rb +30 -0
  68. data/app/models/spree_cm_commissioner/seats/blocks_holder.rb +53 -0
  69. data/app/models/spree_cm_commissioner/seats/blocks_reserver.rb +49 -0
  70. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_on_hold_by_other_guest.rb +4 -0
  71. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_other_guest.rb +4 -0
  72. data/app/models/spree_cm_commissioner/seats/errors/blocks_are_reserved_by_same_guest.rb +4 -0
  73. data/app/models/spree_cm_commissioner/seats/errors/unable_to_save_reserved_block_record.rb +4 -0
  74. data/app/models/spree_cm_commissioner/service_calendar.rb +0 -2
  75. data/app/models/spree_cm_commissioner/state_decorator.rb +0 -1
  76. data/app/models/spree_cm_commissioner/stock/availability_checker.rb +26 -25
  77. data/app/models/spree_cm_commissioner/stock/availability_validator_decorator.rb +2 -1
  78. data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +3 -3
  79. data/app/models/spree_cm_commissioner/stock/order_availability_checker.rb +44 -0
  80. data/app/models/spree_cm_commissioner/stock_item_decorator.rb +17 -0
  81. data/app/models/spree_cm_commissioner/taxon_decorator.rb +0 -2
  82. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +0 -6
  83. data/app/models/spree_cm_commissioner/trip.rb +20 -6
  84. data/app/models/spree_cm_commissioner/trip_connection.rb +5 -5
  85. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -3
  86. data/app/models/spree_cm_commissioner/user_taxon.rb +0 -1
  87. data/app/models/spree_cm_commissioner/variant_block.rb +9 -0
  88. data/app/models/spree_cm_commissioner/variant_decorator.rb +36 -47
  89. data/app/models/spree_cm_commissioner/variant_options.rb +0 -23
  90. data/app/models/spree_cm_commissioner/vehicle.rb +9 -14
  91. data/app/models/spree_cm_commissioner/vendor_decorator.rb +7 -9
  92. data/app/models/spree_cm_commissioner/vendor_place.rb +6 -5
  93. data/app/models/spree_cm_commissioner/vendor_stop.rb +1 -2
  94. data/app/overrides/spree/admin/variants/_form/kyc_field.html.erb.deface +2 -2
  95. data/app/queries/spree_cm_commissioner/guest_searcher_query.rb +3 -45
  96. data/app/queries/spree_cm_commissioner/trip_query.rb +23 -26
  97. data/app/request_schemas/spree_cm_commissioner/accommodation_request_schema.rb +3 -0
  98. data/app/request_schemas/spree_cm_commissioner/application_request_schema.rb +1 -1
  99. data/app/request_schemas/spree_cm_commissioner/variant_request_schema.rb +19 -0
  100. data/app/serializers/spree/v2/storefront/accommodation_serializer.rb +2 -0
  101. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +1 -0
  102. data/app/serializers/spree_cm_commissioner/v2/operator/dashboard_crew_event_serializer.rb +1 -4
  103. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_option_serializer.rb +3 -1
  104. data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +3 -1
  105. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_dynamic_field_serializer.rb +1 -2
  106. data/app/services/spree_cm_commissioner/organizer/export_guest_csv_service.rb +15 -23
  107. data/app/views/spree/admin/inventory_items/show.html.erb +72 -0
  108. data/app/views/spree/admin/stock_managements/_variant_stock_items.html.erb +7 -2
  109. data/app/views/spree/admin/stock_managements/calendar.html.erb +56 -0
  110. data/app/views/spree/admin/stock_managements/index.html.erb +55 -6
  111. data/app/views/spree/admin/tenants/_form.html.erb +42 -109
  112. data/config/initializers/paper_trail.rb +1 -0
  113. data/config/initializers/spree_permitted_attributes.rb +5 -2
  114. data/config/locales/en.yml +0 -4
  115. data/config/routes.rb +22 -4
  116. data/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb +3 -1
  117. data/db/migrate/20250304293518_create_cm_inventory_items.rb +21 -0
  118. data/db/migrate/20250429094228_add_lock_version_to_cm_inventory_items.rb +5 -0
  119. data/db/migrate/20250502025848_add_index_to_spree_products.rb +5 -0
  120. data/db/migrate/20250502030001_add_product_type_to_spree_variants.rb +5 -0
  121. data/db/migrate/20250502030002_add_product_type_to_spree_line_items.rb +5 -0
  122. data/db/migrate/20250603035256_add_inventory_item_to_spree_prices.rb +7 -0
  123. data/db/migrate/20250619073724_drop_table_cm_line_item_seats.rb +5 -0
  124. data/db/migrate/20250619073812_drop_table_cm_vehicle_seats.rb +5 -0
  125. data/db/migrate/20250619073844_drop_table_cm_vehicle_types.rb +9 -0
  126. data/db/migrate/20250619073957_drop_table_cm_option_value_vehicle_types.rb +5 -0
  127. data/db/migrate/20250619082354_remove_unnecessary_fields_from_cm_places.rb +9 -0
  128. data/db/migrate/20250619082736_remove_route_type_from_spree_products.rb +5 -0
  129. data/db/migrate/20250619083055_remove_unnecessary_fields_from_spree_taxons.rb +5 -0
  130. data/db/migrate/20250620083055_remove_variant_id_from_cm_trips.rb +5 -0
  131. data/db/migrate/20250620090000_update_cm_trip_connections_to_use_cm_trips.rb +6 -0
  132. data/db/migrate/20250620090001_create_cm_seat_layouts.rb +17 -0
  133. data/db/migrate/20250620090002_create_cm_seat_sections.rb +18 -0
  134. data/db/migrate/20250620090003_create_cm_blocks.rb +18 -0
  135. data/db/migrate/20250624091005_create_cm_reserved_blocks.rb +29 -0
  136. data/db/migrate/20250626083642_create_cm_variant_blocks.rb +24 -0
  137. data/db/migrate/20250627023314_add_block_id_to_cm_guests.rb +13 -0
  138. data/docker-compose.yml +1 -1
  139. data/lib/cm_app_logger.rb +11 -4
  140. data/lib/generators/spree_cm_commissioner/install/install_generator.rb +14 -11
  141. data/lib/generators/spree_cm_commissioner/install/templates/app/javascript/{spree_cm_commissioner → spree_dashboard/spree_cm_commissioner}/utilities.js +4 -0
  142. data/lib/spree_cm_commissioner/cached_inventory_item.rb +23 -0
  143. data/lib/spree_cm_commissioner/calendar_event.rb +11 -1
  144. data/lib/spree_cm_commissioner/test_helper/factories/block_factory.rb +9 -0
  145. data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +10 -0
  146. data/lib/spree_cm_commissioner/test_helper/factories/homepage_section_relatable_factory.rb +1 -1
  147. data/lib/spree_cm_commissioner/test_helper/factories/inventory_item_factory.rb +9 -0
  148. data/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +1 -1
  149. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +6 -30
  150. data/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb +0 -36
  151. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +18 -34
  152. data/lib/spree_cm_commissioner/test_helper/factories/reserved_block_factory.rb +27 -0
  153. data/lib/spree_cm_commissioner/test_helper/factories/seat_layout_factory.rb +8 -0
  154. data/lib/spree_cm_commissioner/test_helper/factories/seat_section_factory.rb +8 -0
  155. data/lib/spree_cm_commissioner/test_helper/factories/stock_location_factory.rb +2 -2
  156. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +6 -0
  157. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +10 -3
  158. data/lib/spree_cm_commissioner/test_helper/factories/variant_block_factory.rb +7 -0
  159. data/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +41 -19
  160. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb +1 -1
  161. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -1
  162. data/lib/spree_cm_commissioner/version.rb +1 -1
  163. data/lib/spree_cm_commissioner.rb +35 -1
  164. data/lib/tasks/create_default_non_permanent_inventory_items.rake +16 -0
  165. data/lib/tasks/ensure_correct_product_type.rake +7 -0
  166. data/lib/tasks/generate_inventory_items.rake +7 -0
  167. data/spree_cm_commissioner.gemspec +7 -0
  168. metadata +134 -39
  169. data/app/assets/images/cm-hangmeas-checkout_image.svg +0 -63
  170. data/app/assets/images/cm-hangmeas-failed.svg +0 -56
  171. data/app/assets/images/cm-hangmeas-loader.svg +0 -50
  172. data/app/assets/images/cm-hangmeas-success.svg +0 -51
  173. data/app/assets/javascripts/spree_cm_commissioner/tenant_payment_icon_fields.js +0 -65
  174. data/app/assets/stylesheets/spree_cm_commissioner/backend/tenant_payment_icon_fields.scss +0 -60
  175. data/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb +0 -44
  176. data/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb +0 -20
  177. data/app/interactors/spree_cm_commissioner/universal_link_fetcher.rb +0 -54
  178. data/app/models/concerns/spree_cm_commissioner/event_check_in_flowable.rb +0 -30
  179. data/app/models/spree_cm_commissioner/branch.rb +0 -12
  180. data/app/models/spree_cm_commissioner/line_item_seat.rb +0 -10
  181. data/app/models/spree_cm_commissioner/option_value_vehicle_type.rb +0 -8
  182. data/app/models/spree_cm_commissioner/stop.rb +0 -23
  183. data/app/models/spree_cm_commissioner/vehicle_seat.rb +0 -11
  184. data/app/models/spree_cm_commissioner/vehicle_type.rb +0 -76
  185. data/app/overrides/spree/admin/taxons/_form/check_in_flows.html.erb.deface +0 -18
  186. data/app/queries/spree_cm_commissioner/trip_search_query.rb +0 -76
  187. data/app/queries/spree_cm_commissioner/variant_availability/non_permanent_stock_query.rb +0 -45
  188. data/app/queries/spree_cm_commissioner/variant_availability/permanent_stock_query.rb +0 -55
  189. data/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb +0 -11
  190. data/db/migrate/20250616084219_add_description_to_cm_vendor_place.rb +0 -5
  191. data/db/migrate/20250701093203_add_configurations_to_cm_dynamic_field.rb +0 -6
  192. data/db/migrate/20250702091305_add_dynamic_field_option_to_guest_dynamic_field.rb +0 -5
  193. data/db/migrate/20250702091935_add_status_to_dynamic_field_option.rb +0 -5
  194. data/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb +0 -12
  195. data/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb +0 -8
  196. data/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb +0 -8
  197. data/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb +0 -7
  198. data/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb +0 -14
  199. data/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb +0 -8
  200. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb +0 -8
  201. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +0 -96
  202. data/lib/spree_cm_commissioner/trip_seat_layout_result.rb +0 -11
@@ -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,9 +2,7 @@ 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
- base.include SpreeCmCommissioner::EventCheckInFlowable
8
6
 
9
7
  base.preference :background_color, :string
10
8
  base.preference :foreground_color, :string
@@ -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,25 +1,39 @@
1
- require_dependency 'spree_cm_commissioner'
2
1
  module SpreeCmCommissioner
3
- class Trip < SpreeCmCommissioner::Base
2
+ class Trip < Base
3
+ include SpreeCmCommissioner::RouteType
4
+
4
5
  attr_accessor :hours, :minutes, :seconds
5
6
 
6
7
  before_validation :convert_duration_to_seconds
7
- belongs_to :variant, class_name: 'Spree::Variant'
8
8
 
9
- belongs_to :route, class_name: 'Spree::Product'
10
- belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle'
11
- validates :departure_time, presence: true
9
+ belongs_to :product, class_name: 'Spree::Product', inverse_of: :trip, optional: false
10
+ belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle', optional: false
11
+ belongs_to :origin, class_name: 'SpreeCmCommissioner::Place', optional: true
12
+ belongs_to :destination, class_name: 'SpreeCmCommissioner::Place', optional: true
13
+
14
+ has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy
15
+ has_many :variants, through: :product
16
+ has_many :inventory_items, through: :variants
12
17
 
13
18
  validates :departure_time, presence: true
14
19
  validates :duration, numericality: { greater_than: 0 }
15
20
  validate :origin_and_destination_cannot_be_the_same
16
21
 
22
+ accepts_nested_attributes_for :trip_stops, allow_destroy: true
23
+
24
+ after_create :create_trip_stops
25
+
17
26
  def convert_duration_to_seconds
18
27
  return if hours.blank? && minutes.blank? && seconds.blank?
19
28
 
20
29
  self.duration = (hours.to_i * 3600) + (minutes.to_i * 60) + seconds.to_i
21
30
  end
22
31
 
32
+ def create_trip_stops
33
+ trip_stops.find_or_create_by(stop_type: :boarding, stop_id: origin_id)
34
+ trip_stops.find_or_create_by(stop_type: :drop_off, stop_id: destination_id)
35
+ end
36
+
23
37
  def duration_in_hms
24
38
  return { hours: 0, minutes: 0, seconds: 0 } if duration.nil?
25
39
 
@@ -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,10 +1,9 @@
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'
6
+ belongs_to :trip, class_name: 'SpreeCmCommissioner::Trip'
8
7
  belongs_to :stop, class_name: 'SpreeCmCommissioner::Place'
9
8
 
10
9
  before_validation :set_stop_name
@@ -2,6 +2,5 @@ module SpreeCmCommissioner
2
2
  class UserTaxon < Base
3
3
  belongs_to :user, class_name: Spree.user_class.to_s, optional: false
4
4
  belongs_to :taxon, class_name: 'Spree::Taxon', optional: false
5
- has_many :invite_user_taxons, class_name: 'SpreeCmCommissioner::InviteUserTaxon', dependent: :destroy
6
5
  end
7
6
  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
@@ -1,6 +1,7 @@
1
1
  module SpreeCmCommissioner
2
2
  module VariantDecorator
3
- def self.prepended(base)
3
+ def self.prepended(base) # rubocop:disable Metrics/AbcSize
4
+ base.include SpreeCmCommissioner::ProductType
4
5
  base.include SpreeCmCommissioner::ProductDelegation
5
6
  base.include SpreeCmCommissioner::VariantOptionsConcern
6
7
  base.include SpreeCmCommissioner::KycBitwise
@@ -20,24 +21,21 @@ module SpreeCmCommissioner
20
21
  base.has_many :video_on_demands, class_name: 'SpreeCmCommissioner::VideoOnDemand', dependent: :destroy
21
22
  base.has_many :complete_line_items, -> { complete }, class_name: 'Spree::LineItem'
22
23
 
24
+ base.has_many :variant_blocks, class_name: 'SpreeCmCommissioner::VariantBlock', dependent: :destroy
25
+ base.has_many :blocks, class_name: 'SpreeCmCommissioner::Block', through: :variant_blocks
26
+
23
27
  base.has_many :variant_guest_card_class, class_name: 'SpreeCmCommissioner::VariantGuestCardClass'
24
28
  base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variant_guest_card_class
25
29
 
30
+ base.has_many :inventory_items, class_name: 'SpreeCmCommissioner::InventoryItem'
31
+
26
32
  base.scope :subscribable, -> { active.joins(:product).where(product: { subscribable: true, status: :active }) }
27
- base.has_one :trip,
28
- class_name: 'SpreeCmCommissioner::Trip'
29
- base.has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy, foreign_key: :trip_id
30
- base.accepts_nested_attributes_for :option_values
31
- base.after_commit :sync_trip, if: :transit?
32
- base.accepts_nested_attributes_for :trip_stops, allow_destroy: true
33
- base.after_commit :create_trip_stops, if: :transit?
34
- end
33
+ base.scope :with_permanent_stock, -> { where(product_type: base::PERMANENT_STOCK_PRODUCT_TYPES) }
34
+ base.scope :with_non_permanent_stock, -> { where.not(product_type: base::PERMANENT_STOCK_PRODUCT_TYPES) }
35
35
 
36
- def create_trip_stops
37
- return if is_master?
36
+ base.accepts_nested_attributes_for :option_values
38
37
 
39
- trip_stops.find_or_create_by(stop_type: :boarding, stop_id: options.origin)
40
- trip_stops.find_or_create_by(stop_type: :drop_off, stop_id: options.destination)
38
+ base.before_save -> { self.product_type = product.product_type }, if: -> { product_type.nil? }
41
39
  end
42
40
 
43
41
  def delivery_required?
@@ -53,8 +51,20 @@ module SpreeCmCommissioner
53
51
  super || product.discontinued?
54
52
  end
55
53
 
56
- def permanent_stock?
57
- accommodation?
54
+ def default_inventory_item_exist?
55
+ inventory_items.exists?(inventory_date: nil)
56
+ end
57
+
58
+ def create_default_non_permanent_inventory_item!(quantity_available: nil, max_capacity: nil)
59
+ return if product_type.blank? # handle in case product not exist for variant.
60
+ return unless should_track_inventory?
61
+ return if default_inventory_item_exist?
62
+
63
+ inventory_items.create!(
64
+ product_type: product_type,
65
+ quantity_available: [0, quantity_available || total_on_hand].max,
66
+ max_capacity: [0, max_capacity || total_on_hand].max
67
+ )
58
68
  end
59
69
 
60
70
  # override
@@ -72,30 +82,25 @@ module SpreeCmCommissioner
72
82
  "#{display_sku} - #{display_price}"
73
83
  end
74
84
 
75
- def transit?
76
- product.product_type == 'transit'
77
- end
78
-
79
85
  # override
80
- def in_stock?
81
- available_quantity.positive?
86
+ def in_stock?(options = {})
87
+ SpreeCmCommissioner::Stock::AvailabilityChecker.new(self, options).can_supply?
82
88
  end
83
89
 
84
- private
90
+ def price_for_date(currency:, date:)
91
+ currency = currency.upcase
85
92
 
86
- def total_purchases
87
- Spree::LineItem.complete.where(variant_id: id).sum(:quantity).to_i
93
+ prices.joins(:inventory_item)
94
+ .find_by(
95
+ currency: currency,
96
+ inventory_item: { inventory_date: date }
97
+ )
88
98
  end
89
99
 
90
- def available_quantity
91
- stock_count = stock_items.sum(&:count_on_hand)
92
- return stock_count if delivery_required?
93
-
94
- stock_count - total_purchases
95
- end
100
+ private
96
101
 
97
102
  def update_vendor_price
98
- return unless vendor.present? && product&.product_type == vendor&.primary_product_type
103
+ return unless vendor.present? && product_type == vendor&.primary_product_type
99
104
 
100
105
  vendor.update(min_price: price) if vendor.min_price.nil? || price < vendor.min_price
101
106
  vendor.update(max_price: price) if vendor.max_price.nil? || price > vendor.max_price
@@ -119,22 +124,6 @@ module SpreeCmCommissioner
119
124
  end
120
125
  end
121
126
  end
122
-
123
- def sync_trip
124
- return unless product.product_type == 'transit'
125
-
126
- trip = SpreeCmCommissioner::Trip.find_or_initialize_by(variant_id: id)
127
-
128
- trip.assign_attributes(
129
- product_id: product_id,
130
- origin_id: options.origin,
131
- destination_id: options.destination,
132
- departure_time: options.departure_time,
133
- duration: options.total_duration_in_seconds,
134
- vehicle_id: options.vehicle
135
- )
136
- trip.save
137
- end
138
127
  end
139
128
  end
140
129
 
@@ -143,28 +143,5 @@ module SpreeCmCommissioner
143
143
  def number_of_guests
144
144
  number_of_adults + number_of_kids
145
145
  end
146
-
147
- def origin
148
- @origin ||= option_value_name_for(option_type_name: 'origin')&.to_i
149
- end
150
-
151
- def destination
152
- @destination ||= option_value_name_for(option_type_name: 'destination')&.to_i
153
- end
154
-
155
- def vehicle
156
- @vehicle ||= option_value_name_for(option_type_name: 'vehicle')&.to_i
157
- end
158
-
159
- def allow_seat_selection
160
- @allow_seat_selection ||= option_value_name_for(option_type_name: 'allow-seat-selection')&.to_i
161
- end
162
-
163
- def departure_time
164
- @departure_time ||= begin
165
- time = option_value_name_for(option_type_name: 'departure-time')
166
- Time.zone.parse(time) if time.present?
167
- end
168
- end
169
146
  end
170
147
  end
@@ -1,29 +1,24 @@
1
- require_dependency 'spree_cm_commissioner'
2
-
3
1
  module SpreeCmCommissioner
4
- class Vehicle < SpreeCmCommissioner::Base
2
+ class Vehicle < Base
5
3
  include SpreeCmCommissioner::RouteType
6
4
 
7
- belongs_to :vehicle_type, class_name: 'SpreeCmCommissioner::VehicleType'
8
- has_one :primary_photo, -> { order(position: :asc) }, class_name: 'SpreeCmCommissioner::VehiclePhoto', as: :viewable, dependent: :destroy
9
5
  belongs_to :vendor, class_name: 'Spree::Vendor'
10
6
 
11
- after_commit :create_vehicle_option_value
7
+ has_one :primary_photo, -> { order(position: :asc) }, class_name: 'SpreeCmCommissioner::VehiclePhoto', as: :viewable, dependent: :destroy
8
+ has_one :seat_layout, as: :layoutable, class_name: 'SpreeCmCommissioner::SeatLayout'
12
9
 
13
- has_many :vehicle_photo, class_name: 'SpreeCmCommissioner::VehiclePhoto', as: :viewable, dependent: :destroy
14
- has_many :vehicle_seats, class_name: 'SpreeCmCommissioner::VehicleSeat', through: :vehicle_type
10
+ has_many :vehicle_photos, class_name: 'SpreeCmCommissioner::VehiclePhoto', as: :viewable, dependent: :destroy
15
11
 
16
12
  has_many :vehicle_option_types, class_name: 'SpreeCmCommissioner::VehicleOptionType', dependent: :destroy
17
13
  has_many :option_value_vehicles, class_name: 'SpreeCmCommissioner::OptionValueVehicle', dependent: :destroy
18
14
 
19
15
  validates :code, uniqueness: { scope: :vendor_id }, presence: true
20
- validates :license_plate, uniqueness: {}, allow_blank: true
21
-
22
- def create_vehicle_option_value
23
- SpreeCmCommissioner::VehicleOptionValueCreator.call(self)
24
- end
16
+ validates :license_plate, uniqueness: true, allow_blank: true
25
17
 
26
18
  self.whitelisted_ransackable_attributes = %w[license_plate code]
27
- self.whitelisted_ransackable_associations = %w[vehicle_type]
19
+
20
+ def display_name
21
+ "#{code} (#{license_plate})"
22
+ end
28
23
  end
29
24
  end
@@ -36,12 +36,13 @@ module SpreeCmCommissioner
36
36
  base.has_many :vendor_kind_option_values,
37
37
  through: :option_value_vendors, source: :option_value
38
38
 
39
- base.has_many :branches, lambda {
40
- where(cm_vendor_places: { place_type: 0 })
41
- }, through: :vendor_places, class_name: 'SpreeCmCommissioner::VendorPlace'
42
- base.has_many :stops, -> { where(cm_vendor_places: { place_type: 1 }) }, through: :vendor_places, class_name: 'SpreeCmCommissioner::VendorPlace'
43
- base.has_many :locations, -> { where(cm_vendor_places: { place_type: 2 }) },
44
- through: :vendor_places, class_name: 'SpreeCmCommissioner::VendorPlace'
39
+ base.has_many :branches, -> { branches }, class_name: 'SpreeCmCommissioner::VendorPlace'
40
+ base.has_many :stops, -> { stops }, class_name: 'SpreeCmCommissioner::VendorPlace'
41
+ base.has_many :locations, -> { locations }, class_name: 'SpreeCmCommissioner::VendorPlace'
42
+
43
+ base.has_many :branch_places, through: :branches, class_name: 'SpreeCmCommissioner::Place', source: :place
44
+ base.has_many :stop_places, through: :stops, class_name: 'SpreeCmCommissioner::Place', source: :place
45
+ base.has_many :location_places, through: :locations, class_name: 'SpreeCmCommissioner::Place', source: :place
45
46
 
46
47
  base.has_many :places,
47
48
  through: :nearby_places, source: :place, class_name: 'SpreeCmCommissioner::Place'
@@ -83,10 +84,7 @@ module SpreeCmCommissioner
83
84
  class_name: 'SpreeCmCommissioner::HomepageSectionRelatable',
84
85
  dependent: :destroy, inverse_of: :relatable
85
86
 
86
- base.has_many :vehicle_types, class_name: 'SpreeCmCommissioner::VehicleType', dependent: :destroy
87
87
  base.has_many :vehicles, class_name: 'SpreeCmCommissioner::Vehicle', dependent: :destroy
88
- # base.has_many :vehicles, through: :vehicle_types, class_name: 'SpreeCmCommissioner::Vehicle', dependent: :destroy
89
-
90
88
  base.validates :account_name, :account_number, presence: true, if: lambda {
91
89
  payment_qrcode.present? && Spree::Store.default.code.include?('billing')
92
90
  }
@@ -7,13 +7,14 @@ module SpreeCmCommissioner
7
7
  belongs_to :place, class_name: 'SpreeCmCommissioner::Place', optional: false
8
8
 
9
9
  validates :place_type, presence: true
10
- validates :place_id, uniqueness: { scope: %i[vendor_id place_type] }
10
+ validates :position, presence: true, numericality: { only_integer: true, greater_than: 0 }
11
11
 
12
- accepts_nested_attributes_for :place, allow_destroy: true
12
+ # include place when using these delegate to avoid N+1
13
+ def display_name
14
+ place&.name
15
+ end
13
16
 
14
- scope :branches, -> { where(place_type: :branch) }
15
- scope :stops, -> { where(place_type: :stop) }
16
- scope :locations, -> { where(place_type: :location) }
17
+ accepts_nested_attributes_for :place, allow_destroy: true
17
18
 
18
19
  def selected
19
20
  vendor.selected_place_references.include?(place&.reference)
@@ -1,6 +1,5 @@
1
- require_dependency 'spree_cm_commissioner'
2
1
  module SpreeCmCommissioner
3
- class VendorStop < ApplicationRecord
2
+ class VendorStop < Base
4
3
  belongs_to :vendor, class_name: 'Spree::Vendor'
5
4
  belongs_to :stop, class_name: 'SpreeCmCommissioner::Place'
6
5
 
@@ -2,13 +2,13 @@
2
2
 
3
3
  <p>Guest Information Fields</p>
4
4
  <div class="form-check mb-3">
5
- <%= f.check_box :use_product_kyc, id: "toggleGuestInfo", class: 'form-check-input mr-2', checked: @variant.kyc.nil? %>
5
+ <%= f.check_box :use_product_kyc, id: "toggleGuestInfo", class: 'form-check-input mr-2', checked: (@variant.kyc == @product.kyc) %>
6
6
  <label for="toggleGuestInfo" class="form-check-label">
7
7
  Use Product Guest Information
8
8
  </label>
9
9
  </div>
10
10
 
11
- <div id="guestInfoForm" <% if @variant.kyc.nil? %>style="display: none;"<% end %>">
11
+ <div id="guestInfoForm" <% if @variant.kyc == @product.kyc %>style="display: none;"<% end %>">
12
12
  <small class="form-text text-muted mb-4">
13
13
  <%= raw I18n.t('kyc.variant_note') %>
14
14
  </small>
@@ -8,36 +8,20 @@ module SpreeCmCommissioner
8
8
 
9
9
  def event_id = params[:event_id]
10
10
 
11
- def call # rubocop:disable Metrics/PerceivedComplexity
11
+ def call
12
12
  return SpreeCmCommissioner::Guest.none if event_id.blank?
13
13
 
14
- if params[:qr_data].present? && order_qr_data?
15
- search_by_order_qr
16
- elsif params[:qr_data].present? && line_item_qr_data?
17
- search_by_line_item_qr
18
- elsif params[:qr_data].present? && guest_qr_data?
14
+ if params[:qr_data].present?
19
15
  search_by_guest_qr
20
16
  elsif params[:term].present?
21
17
  search_by_term
22
- elsif params[:ids].present?
23
- SpreeCmCommissioner::Guest.complete_or_canceled.where(event_id: event_id, id: params[:ids])
24
18
  else
25
- SpreeCmCommissioner::Guest.complete_or_canceled.where(event_id: event_id)
19
+ SpreeCmCommissioner::Guest.none
26
20
  end
27
21
  end
28
22
 
29
23
  private
30
24
 
31
- def search_by_order_qr
32
- order = Spree::Order.complete_or_canceled.search_by_qr_data!(params[:qr_data])
33
- order.guests.where(event_id: event_id)
34
- end
35
-
36
- def search_by_line_item_qr
37
- line_item = Spree::LineItem.complete_or_canceled.search_by_qr_data!(params[:qr_data])
38
- line_item.guests.where(event_id: event_id)
39
- end
40
-
41
25
  def search_by_guest_qr
42
26
  SpreeCmCommissioner::Guest.complete_or_canceled.where(
43
27
  token: params[:qr_data],
@@ -45,32 +29,6 @@ module SpreeCmCommissioner
45
29
  )
46
30
  end
47
31
 
48
- def order_qr_data?
49
- matches = construct_matches
50
- matches&.size == 2
51
- end
52
-
53
- def line_item_qr_data?
54
- matches = construct_matches
55
- matches&.size == 3
56
- end
57
-
58
- def guest_qr_data?
59
- matches = construct_matches
60
- matches.nil?
61
- end
62
-
63
- def construct_matches
64
- qr_data = params[:qr_data]
65
- return nil unless qr_data
66
-
67
- if qr_data =~ /-L\d+$/
68
- qr_data.match(/(R\d+)-([A-Za-z0-9_\-]+)-(L\d+)/)&.captures
69
- else
70
- qr_data.match(/(R\d+)-([A-Za-z0-9_\-]+)/)&.captures
71
- end
72
- end
73
-
74
32
  def search_by_term
75
33
  terms = params[:term].split.map { |term| "%#{term.downcase}%" }
76
34