spree_cm_commissioner 2.0.3.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 -43
  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 -16
  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
@@ -6,20 +6,10 @@ module SpreeCmCommissioner
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  PRODUCT_TYPES = %i[accommodation service ecommerce transit].freeze
9
- PERMANENT_STOCK_PRODUCT_TYPES = %w[accommodation service transit].freeze
10
- PRE_INVENTORY_DAYS = { 'transit' => 90, 'accommodation' => 365, 'service' => 30 }.freeze
11
9
 
12
10
  included do
13
11
  enum product_type: PRODUCT_TYPES if table_exists? && column_names.include?('product_type')
14
12
  enum primary_product_type: PRODUCT_TYPES if table_exists? && column_names.include?('primary_product_type')
15
13
  end
16
-
17
- def permanent_stock?
18
- PERMANENT_STOCK_PRODUCT_TYPES.include?(product_type)
19
- end
20
-
21
- def pre_inventory_days
22
- PRE_INVENTORY_DAYS[product_type]
23
- end
24
14
  end
25
15
  end
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
2
2
  module RouteType
3
3
  extend ActiveSupport::Concern
4
4
 
5
- ROUTE_TYPES = %i[bus intercity_taxi airport_shuttle rails ferry].freeze
5
+ ROUTE_TYPES = %i[automobile subway rails ferry].freeze
6
6
 
7
7
  included do
8
8
  enum route_type: ROUTE_TYPES if table_exists? && column_names.include?('route_type')
@@ -3,7 +3,7 @@ module SpreeCmCommissioner
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- enum kind: { category: 0, cms: 1, event: 2, occupation: 3, nationality: 4, organization: 5 }
6
+ enum kind: { category: 0, cms: 1, event: 2, occupation: 3, nationality: 4, organization: 5, transit: 6 }
7
7
  end
8
8
  end
9
9
  end
@@ -9,6 +9,7 @@ module SpreeCmCommissioner
9
9
  preference :payment_failed_image, :string, default: ''
10
10
  preference :payment_success_image, :string, default: ''
11
11
  preference :payment_loader, :string, default: ''
12
+ preference :brand_primary_color, :string, default: ''
12
13
  end
13
14
  end
14
15
  end
@@ -53,8 +53,9 @@ module SpreeCmCommissioner
53
53
  # No need to fallback to event start date to avoid n+1.
54
54
  # To get end_time duration of event, include event when needed instead.
55
55
  def start_date_time
56
- return nil if start_date.blank?
56
+ return nil if start_date.blank? && start_time.blank?
57
57
  return start_date if start_time.blank?
58
+ return start_time if start_date.blank?
58
59
 
59
60
  start_date.change(hour: start_time.hour, min: start_time.min, sec: start_time.sec)
60
61
  end
@@ -62,8 +63,9 @@ module SpreeCmCommissioner
62
63
  # No need to fallback to event end date to avoid n+1.
63
64
  # To get end_time duration of event, include event when needed instead.
64
65
  def end_date_time
65
- return nil if end_date.blank?
66
+ return nil if end_date.blank? && end_time.blank?
66
67
  return end_date if end_time.blank?
68
+ return end_time if end_date.blank?
67
69
 
68
70
  end_date.change(hour: end_time.hour, min: end_time.min, sec: end_time.sec)
69
71
  end
@@ -0,0 +1,12 @@
1
+ require_dependency 'spree_cm_commissioner'
2
+
3
+ module SpreeCmCommissioner
4
+ class Branch < SpreeCmCommissioner::Place
5
+ belongs_to :state, class_name: 'Spree::State', optional: true
6
+ belongs_to :vendor, class_name: 'Spree::Vendor'
7
+
8
+ def validate_reference?
9
+ false
10
+ end
11
+ end
12
+ end
@@ -9,12 +9,14 @@ module SpreeCmCommissioner
9
9
  has_many :product_dynamic_fields, class_name: 'SpreeCmCommissioner::ProductDynamicField', dependent: :destroy
10
10
 
11
11
  enum data_type: { text: 0, number: 1, boolean: 2, checkbox: 3, radio: 4, selection: 5, textarea: 6 }
12
+ enum data_fill_stage: { pre_registration: 0, post_registration: 1, during_check_in: 2 }
12
13
 
13
14
  accepts_nested_attributes_for :dynamic_field_options, allow_destroy: true, reject_if: :all_blank
14
15
 
15
16
  validates :label, presence: true
16
17
  validates :data_type, presence: true
17
18
  validates :configurations, presence: true, allow_blank: false
19
+ validate :validate_data_fill_stage_phase
18
20
 
19
21
  def requires_dynamic_field_options?
20
22
  checkbox? || radio? || selection?
@@ -30,5 +32,23 @@ module SpreeCmCommissioner
30
32
  self.configurations ||= {}
31
33
  self.configurations['multiple_select'] = ActiveModel::Type::Boolean.new.cast(value)
32
34
  end
35
+
36
+ def required_at_stage?(stage)
37
+ data_fill_stage == stage.to_s
38
+ end
39
+
40
+ def display_label
41
+ label + " (#{data_fill_stage.humanize})"
42
+ end
43
+
44
+ private
45
+
46
+ def validate_data_fill_stage_phase
47
+ return if data_fill_stage.blank?
48
+
49
+ return if data_fill_stage.in?(self.class.data_fill_stages.keys)
50
+
51
+ errors.add(:data_fill_stage, "must be one of: #{self.class.data_fill_stages.keys.join(', ')}")
52
+ end
33
53
  end
34
54
  end
@@ -24,16 +24,10 @@ module SpreeCmCommissioner
24
24
  scope :none_bib, -> { where(bib_prefix: [nil, '']) }
25
25
 
26
26
  belongs_to :line_item, class_name: 'Spree::LineItem'
27
-
28
27
  has_one :variant, class_name: 'Spree::Variant', through: :line_item
29
-
30
28
  belongs_to :user, class_name: 'Spree::User'
31
29
  belongs_to :occupation, class_name: 'Spree::Taxon'
32
30
  belongs_to :nationality, class_name: 'Spree::Taxon'
33
- belongs_to :block, class_name: 'SpreeCmCommissioner::Block', optional: true
34
-
35
- has_one :reserved_block, class_name: 'SpreeCmCommissioner::ReservedBlock'
36
-
37
31
  scope :checked_ins, -> { joins(:check_in) }
38
32
  scope :no_show, -> { left_outer_joins(:check_in).where(cm_check_ins: { id: nil }) }
39
33
 
@@ -54,10 +48,6 @@ module SpreeCmCommissioner
54
48
 
55
49
  before_create :generate_bib, if: -> { line_item.reload && variant.bib_pre_generation_on_create? }
56
50
 
57
- after_create :preload_order_block_ids, if: -> { block_id.present? }
58
- after_update :preload_order_block_ids, if: :saved_change_to_block_id?
59
- after_destroy :preload_order_block_ids
60
-
61
51
  validates :seat_number, uniqueness: { scope: :event_id }, allow_nil: true, if: -> { event_id.present? }
62
52
  validates :bib_index, uniqueness: true, allow_nil: true
63
53
 
@@ -223,6 +213,62 @@ module SpreeCmCommissioner
223
213
  save!
224
214
  end
225
215
 
216
+ def missing_dynamic_fields_for_stage(stage)
217
+ required_fields = line_item.product.dynamic_fields.where(data_fill_stage: stage)
218
+ filled_ids = guest_dynamic_fields.map(&:dynamic_field_id)
219
+
220
+ required_fields.reject { |field| filled_ids.include?(field.id) }
221
+ end
222
+
223
+ def all_required_dynamic_fields_completed?(stage)
224
+ missing_dynamic_fields_for_stage(stage).blank?
225
+ end
226
+
227
+ def validate_dynamic_fields_for_phase(phase)
228
+ required_fields = line_item.product.dynamic_fields.where(data_fill_stage: phase)
229
+ filled_ids = guest_dynamic_fields.map(&:dynamic_field_id)
230
+
231
+ required_fields.each do |field|
232
+ errors.add(:base, "#{field.label} is required for #{phase} phase") unless filled_ids.include?(field.id)
233
+ end
234
+ end
235
+
236
+ def pre_registration_completed?
237
+ return true unless line_item.product.dynamic_fields.pre_registration.any?
238
+
239
+ pre_registration_fields_completed?
240
+ end
241
+
242
+ def post_registration_completed?
243
+ return true unless line_item.product.dynamic_fields.post_registration.any?
244
+
245
+ post_registration_fields_completed?
246
+ end
247
+
248
+ def check_in_completed?
249
+ return true unless line_item.product.dynamic_fields.during_check_in.any?
250
+
251
+ check_in_fields_completed?
252
+ end
253
+
254
+ def current_collection_phase
255
+ if check_in.present?
256
+ :during_check_in
257
+ elsif line_item.order.completed?
258
+ :post_registration
259
+ else
260
+ :pre_registration
261
+ end
262
+ end
263
+
264
+ def phase_completion_status
265
+ {
266
+ pre_registration: pre_registration_completed?,
267
+ post_registration: post_registration_completed?,
268
+ during_check_in: check_in_completed?
269
+ }
270
+ end
271
+
226
272
  # bib_number: 345, bib_prefix: 5KM, bib_zerofill: 5 => return 5KM00345
227
273
  # bib_number: 345, bib_prefix: 5KM, bib_zerofill: 2 => return 5KM345
228
274
  def formatted_bib_number
@@ -237,14 +283,6 @@ module SpreeCmCommissioner
237
283
  filled_bib_number
238
284
  end
239
285
  end
240
-
241
- def preload_order_block_ids
242
- return if line_item.blank?
243
- return if line_item.order.blank?
244
-
245
- block_ids = line_item.order.blocks.pluck(:id)
246
- line_item.order.update(preload_block_ids: block_ids)
247
- end
248
286
  end
249
287
  end
250
288
  # rubocop:enable Metrics/ClassLength
@@ -7,6 +7,7 @@ module SpreeCmCommissioner
7
7
  validates :value, presence: true
8
8
  validate :validate_value_format, if: -> { value.present? && dynamic_field.present? }
9
9
  validate :validate_option_reference, if: -> { dynamic_field_option.present? }
10
+ validate :check_required_fields_based_on_phase
10
11
 
11
12
  private
12
13
 
@@ -34,10 +35,49 @@ module SpreeCmCommissioner
34
35
 
35
36
  def validate_option_reference
36
37
  return if dynamic_field.blank? || dynamic_field_option.blank?
37
-
38
38
  return if dynamic_field_option.dynamic_field_id == dynamic_field_id
39
39
 
40
40
  errors.add(:dynamic_field_option, 'must belong to the same dynamic field')
41
41
  end
42
+
43
+ def before_checkin_phase?
44
+ guest.present? && guest.line_item.present? && guest.check_in.nil?
45
+ end
46
+
47
+ def after_purchase_phase?
48
+ guest.present? && guest.line_item.present? && guest.line_item.completed? && guest.check_in.nil?
49
+ end
50
+
51
+ def during_checkin_phase?
52
+ guest.present? && guest.check_in.present?
53
+ end
54
+
55
+ def pre_registration_required?
56
+ dynamic_field.data_fill_stage.to_sym == :pre_registration &&
57
+ guest.line_item.order.completed? && guest.check_in.blank?
58
+ end
59
+
60
+ def post_registration_required?
61
+ dynamic_field.data_fill_stage.to_sym == :post_registration &&
62
+ guest.line_item.order.completed? && guest.check_in.blank?
63
+ end
64
+
65
+ def during_check_in_required?
66
+ dynamic_field.data_fill_stage.to_sym == :during_check_in &&
67
+ guest.check_in.present?
68
+ end
69
+
70
+ def check_required_fields_based_on_phase
71
+ return if dynamic_field.blank? || value.present?
72
+
73
+ case dynamic_field.data_fill_stage.to_sym
74
+ when :pre_registration
75
+ errors.add(:value, I18n.t('guest_dynamic_field.errors.pre_registration_required')) if pre_registration_required?
76
+ when :post_registration
77
+ errors.add(:value, I18n.t('guest_dynamic_field.errors.post_registration_required')) if post_registration_required?
78
+ when :during_check_in
79
+ errors.add(:value, I18n.t('guest_dynamic_field.errors.during_check_in_required')) if during_check_in_required?
80
+ end
81
+ end
42
82
  end
43
83
  end
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  module LineItemDecorator
3
- def self.prepended(base) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
3
+ def self.prepended(base) # rubocop:disable Metrics/MethodLength
4
4
  include_modules(base)
5
5
 
6
6
  base.belongs_to :accepter, class_name: 'Spree::User', optional: true
@@ -10,32 +10,31 @@ module SpreeCmCommissioner
10
10
  base.has_one :google_wallet, class_name: 'SpreeCmCommissioner::GoogleWallet', through: :product
11
11
 
12
12
  base.has_many :option_types, through: :product
13
-
14
- base.has_many :inventory_items, lambda { |line_item|
15
- where(inventory_date: nil).or(where(inventory_date: line_item.date_range))
16
- }, through: :variant
17
-
18
13
  base.has_many :taxons, class_name: 'Spree::Taxon', through: :product
19
14
  base.has_many :guests, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
20
- base.has_many :guests_with_blocks, -> { where.not(block_id: nil) }, class_name: 'SpreeCmCommissioner::Guest'
21
- base.has_many :reserved_blocks, through: :guests, class_name: 'SpreeCmCommissioner::ReservedBlock'
22
15
  base.has_many :pending_guests, pending_guests_query, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
23
16
  base.has_many :product_completion_steps, class_name: 'SpreeCmCommissioner::ProductCompletionStep', through: :product
17
+ base.has_many :line_item_seats, class_name: 'SpreeCmCommissioner::LineItemSeat', dependent: :destroy
24
18
 
25
19
  base.before_save :update_vendor_id
20
+ base.before_save :update_quantity, if: :transit?
21
+
22
+ base.validate :validate_seats_reservation, if: :transit?
26
23
 
27
24
  base.before_create :add_due_date, if: :subscription?
28
- base.before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
29
25
 
30
26
  base.validate :ensure_not_exceed_max_quantity_per_order, if: -> { variant&.max_quantity_per_order.present? }
31
27
 
32
28
  base.whitelisted_ransackable_associations |= %w[guests order]
33
29
  base.whitelisted_ransackable_attributes |= %w[number to_date from_date vendor_id]
34
30
 
35
- base.delegate :delivery_required?, :high_demand,
31
+ base.delegate :delivery_required?, :permanent_stock?, :high_demand, :transit?,
36
32
  to: :variant
33
+ base.delegate :discontinue_on, :product_type, :accommodation?, :service?, :ecommerce?, :need_confirmation,
34
+ to: :product
37
35
 
38
36
  base.accepts_nested_attributes_for :guests, allow_destroy: true
37
+ base.accepts_nested_attributes_for :line_item_seats, allow_destroy: true
39
38
 
40
39
  def base.json_api_columns
41
40
  json_api_columns = column_names.reject { |c| c.match(/_id$|id|preferences|(.*)password|(.*)token|(.*)api_key/) }
@@ -43,10 +42,6 @@ module SpreeCmCommissioner
43
42
  json_api_columns << :vendor_id
44
43
  end
45
44
 
46
- def discontinue_on
47
- variant.discontinue_on || product.discontinue_on
48
- end
49
-
50
45
  def base.search_by_qr_data!(data)
51
46
  matches = data.match(/(R\d+)-([A-Za-z0-9_\-]+)-(L\d+)/)&.captures
52
47
 
@@ -65,7 +60,6 @@ module SpreeCmCommissioner
65
60
  base.include SpreeCmCommissioner::LineItemDurationable
66
61
  base.include SpreeCmCommissioner::LineItemsFilterScope
67
62
  base.include SpreeCmCommissioner::LineItemGuestsConcern
68
- base.include SpreeCmCommissioner::ProductType
69
63
  base.include SpreeCmCommissioner::ProductDelegation
70
64
  base.include SpreeCmCommissioner::KycBitwise
71
65
  end
@@ -81,39 +75,24 @@ module SpreeCmCommissioner
81
75
  }
82
76
  end
83
77
 
84
- # override to calculate price per date for permanent stock
85
- def update_price_from_modifier(currency, opts)
86
- self.currency = currency
87
-
88
- # these fields can be null during this process below.
89
- # it is needed for permanent_stock?, date_range to work.
90
- self.product_type = variant.product_type
91
- self.from_date = opts.delete(:from_date)&.to_date
92
- self.to_date = opts.delete(:to_date)&.to_date
93
-
94
- base_price = variant.price_in(currency)
95
-
96
- self.price = if permanent_stock?
97
- date_range.map do |date|
98
- selected_price = variant.price_for_date(date: date, currency: currency) || base_price
99
- (selected_price.amount || 0) + variant.price_modifier_amount_in(currency, opts.merge(date: date))
100
- end.sum
101
- else
102
- (base_price.amount || 0) + variant.price_modifier_amount_in(currency, opts)
103
- end
78
+ def reservation?
79
+ date_present? && !subscription?
104
80
  end
105
81
 
106
- # override to calculate price per date for permanent stock
107
- def update_price
108
- base_price = variant.price_in(order.currency)
109
- self.price = if permanent_stock?
110
- date_range.map do |date|
111
- selected_price = variant.price_for_date(date: date, currency: order.currency) || base_price
112
- selected_price.amount || 0
113
- end.sum
114
- else
115
- base_price.amount || 0
116
- end
82
+ # date_unit could be number of nights, or days or hours depending on usecases
83
+ # For example:
84
+ # - accomodation uses number of nights.
85
+ # - appointment uses number of hours, etc.
86
+ #
87
+ # override
88
+ def amount
89
+ base_price = price * quantity
90
+
91
+ if permanent_stock? && date_unit.present?
92
+ base_price * date_unit
93
+ else
94
+ base_price
95
+ end
117
96
  end
118
97
 
119
98
  def allowed_self_check_in?
@@ -200,6 +179,8 @@ module SpreeCmCommissioner
200
179
 
201
180
  # override
202
181
  def sufficient_stock?
182
+ return transit_sufficient_stock? if variant.product.product_type == 'transit'
183
+
203
184
  SpreeCmCommissioner::Stock::LineItemAvailabilityChecker.new(self).can_supply?(quantity)
204
185
  end
205
186
 
@@ -218,6 +199,18 @@ module SpreeCmCommissioner
218
199
  errors.add(:quantity, 'exceeded_max_quantity_per_order') if quantity > variant.max_quantity_per_order
219
200
  end
220
201
 
202
+ def transit_sufficient_stock?
203
+ return selected_seats_available? if reservation_trip.allow_seat_selection
204
+
205
+ seat_quantity_available?(reservation_trip)
206
+ end
207
+
208
+ def update_quantity
209
+ return if line_item_seats.blank?
210
+
211
+ self.quantity = line_item_seats.size
212
+ end
213
+
221
214
  def update_vendor_id
222
215
  self.vendor_id = variant.vendor_id
223
216
  end
@@ -240,6 +233,45 @@ module SpreeCmCommissioner
240
233
 
241
234
  from_date + variant.month.month + day.days
242
235
  end
236
+
237
+ def validate_seats_reservation
238
+ if reservation_trip.allow_seat_selection && !selected_seats_available?
239
+ errors.add(:base, :some_seats_are_booked, message: 'Some seats are already booked')
240
+ elsif !reservation_trip.allow_seat_selection && !seat_quantity_available?(reservation_trip)
241
+ errors.add(:quantity, :exceeded_available_quantity, message: 'exceeded available quantity')
242
+ end
243
+ end
244
+
245
+ def selected_seats_available?
246
+ selected_seat_ids = line_item_seats.map(&:seat_id)
247
+ !selected_seat_ids_occupied?(selected_seat_ids)
248
+ end
249
+
250
+ def seat_quantity_available?(trip)
251
+ booked_quantity = Spree::LineItem.joins(:order)
252
+ .where(variant_id: variant_id, date: date, spree_orders: { state: 'complete' })
253
+ .where.not(spree_line_items: { id: id })
254
+ .sum(:quantity)
255
+ remaining_quantity = trip.vehicle.number_of_seats - booked_quantity
256
+ remaining_quantity >= quantity
257
+ end
258
+
259
+ def reservation_trip
260
+ return @trip if defined? @trip
261
+
262
+ route = Spree::Variant.find_by(id: variant_id).product
263
+ @trip = route.trip
264
+ end
265
+
266
+ def selected_seat_ids_occupied?(selected_seat_ids)
267
+ # check to see if there are any selected_ids exist in the line_item_seats and belongs to completed order
268
+ SpreeCmCommissioner::LineItemSeat.joins(line_item: :order)
269
+ .where(seat_id: selected_seat_ids, date: date, variant_id: variant_id, spree_orders: { state: 'complete',
270
+ canceled_at: nil
271
+ }
272
+ )
273
+ .present?
274
+ end
243
275
  end
244
276
  end
245
277
 
@@ -0,0 +1,10 @@
1
+ require_dependency 'spree_cm_commissioner'
2
+ module SpreeCmCommissioner
3
+ class LineItemSeat < SpreeCmCommissioner::Base
4
+ belongs_to :line_item, class_name: 'Spree::LineItem'
5
+ belongs_to :seat, class_name: 'SpreeCmCommissioner::VehicleSeat'
6
+ belongs_to :variant, class_name: 'Spree::Variant'
7
+
8
+ validates :date, presence: true
9
+ end
10
+ end
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- class NotificationTaxon < Base
2
+ class NotificationTaxon < SpreeCmCommissioner::Base
3
3
  belongs_to :customer_notification, class_name: 'SpreeCmCommissioner::CustomerNotification'
4
4
  belongs_to :taxon, class_name: 'Spree::Taxon'
5
5
  end
@@ -6,7 +6,7 @@ module SpreeCmCommissioner
6
6
  base.include SpreeCmCommissioner::ParameterizeName
7
7
  base.include SpreeCmCommissioner::OptionTypeAttrType
8
8
 
9
- base.enum kind: %i[variant product vendor vehicle]
9
+ base.enum kind: %i[variant product vendor vehicle_type transit]
10
10
 
11
11
  base.validates :name, presence: true
12
12
 
@@ -19,6 +19,16 @@ module SpreeCmCommissioner
19
19
  base.scope :promoted, -> { where(promoted: true) }
20
20
  base.whitelisted_ransackable_attributes = %w[kind]
21
21
 
22
+ def base.amenities
23
+ Spree::OptionType.where(kind: 'vehicle_type', name: 'amenities', presentation: 'Amenities', attr_type: 'amenity').first_or_create
24
+ end
25
+
26
+ def base.vehicle
27
+ Spree::OptionType.where(presentation: 'vehicle', attr_type: 'vehicle_id', kind: 'variant',
28
+ name: 'vehicle'
29
+ ).first_or_create
30
+ end
31
+
22
32
  def base.rules_option_type
23
33
  Spree::OptionType.find_by(name: RULES_OPTION_TYPE_NAME)
24
34
  end
@@ -0,0 +1,8 @@
1
+ require_dependency 'spree_cm_commissioner'
2
+
3
+ module SpreeCmCommissioner
4
+ class OptionValueVehicleType < ApplicationRecord
5
+ belongs_to :vehicle_type, class_name: 'SpreeCmCommissioner::VehicleType', dependent: :destroy
6
+ belongs_to :option_value, class_name: 'Spree::OptionValue', dependent: :destroy
7
+ end
8
+ end
@@ -2,7 +2,6 @@ module SpreeCmCommissioner
2
2
  module OrderDecorator
3
3
  def self.prepended(base) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
4
4
  base.include SpreeCmCommissioner::PhoneNumberSanitizer
5
- base.include SpreeCmCommissioner::OrderSeatable
6
5
  base.include SpreeCmCommissioner::OrderStateMachine
7
6
 
8
7
  base.scope :subscription, -> { where.not(subscription_id: nil) }
@@ -43,8 +42,6 @@ module SpreeCmCommissioner
43
42
  base.has_many :vendors, through: :products, class_name: 'Spree::Vendor'
44
43
  base.has_many :taxons, through: :products, class_name: 'Spree::Taxon'
45
44
  base.has_many :guests, through: :line_items, class_name: 'SpreeCmCommissioner::Guest'
46
- base.has_many :blocks, through: :guests, class_name: 'SpreeCmCommissioner::Block', source: :block
47
- base.has_many :reserved_blocks, through: :guests, class_name: 'SpreeCmCommissioner::ReservedBlock'
48
45
  base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variants
49
46
 
50
47
  base.delegate :customer, to: :user, allow_nil: true
@@ -61,13 +58,6 @@ module SpreeCmCommissioner
61
58
  end
62
59
  end
63
60
 
64
- # override
65
- # spree use this method to check stock availability & consider whether :order can continue to next state.
66
- def insufficient_stock_lines
67
- checker = SpreeCmCommissioner::Stock::OrderAvailabilityChecker.new(self)
68
- checker.insufficient_stock_lines
69
- end
70
-
71
61
  def ticket_seller_user?
72
62
  return false if user.nil?
73
63
 
@@ -116,14 +106,7 @@ module SpreeCmCommissioner
116
106
  end
117
107
 
118
108
  def mark_as_archive
119
- blocks_canceled = begin
120
- cancel_blocks!
121
- true
122
- rescue StandardError
123
- false
124
- end
125
-
126
- update(archived_at: Time.current) if blocks_canceled
109
+ update(archived_at: Time.current)
127
110
  end
128
111
 
129
112
  # overrided
@@ -216,18 +199,6 @@ module SpreeCmCommissioner
216
199
 
217
200
  private
218
201
 
219
- def unstock_inventory_in_redis!
220
- CmAppLogger.log(label: "#{self.class.name}#unstock_inventory_in_redis!", data: { order_id: id }) do
221
- SpreeCmCommissioner::RedisStock::InventoryUpdater.new(line_item_ids).unstock!
222
- end
223
- end
224
-
225
- def restock_inventory_in_redis!
226
- CmAppLogger.log(label: "#{self.class.name}#restock_inventory_in_redis!", data: { order_id: id }) do
227
- SpreeCmCommissioner::RedisStock::InventoryUpdater.new(line_item_ids).restock!
228
- end
229
- end
230
-
231
202
  # override :spree_api
232
203
  def webhook_payload_body
233
204
  resource_serializer.new(
@@ -1,5 +1,7 @@
1
+ require_dependency 'spree_cm_commissioner'
2
+
1
3
  module SpreeCmCommissioner
2
- class Place < Base
4
+ class Place < ApplicationRecord
3
5
  acts_as_nested_set
4
6
 
5
7
  validates :reference, presence: true, if: :validate_reference?
@@ -15,6 +17,7 @@ module SpreeCmCommissioner
15
17
  has_many :products, through: :product_places
16
18
 
17
19
  has_many :children, -> { order(:lft) }, class_name: 'SpreeCmCommissioner::Place', foreign_key: :parent_id, dependent: :destroy
20
+ has_many :vendor_stops, class_name: 'SpreeCmCommissioner::VendorStop', dependent: :destroy, foreign_key: :stop_id
18
21
 
19
22
  has_many :taxon_places, class_name: 'SpreeCmCommissioner::TaxonPlace', dependent: :destroy
20
23
  has_many :taxons, through: :taxon_places