spree_cm_commissioner 2.0.3.pre.pre5 → 2.0.3.pre.pre8

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/spree/admin/locations_controller.rb +62 -0
  4. data/app/controllers/spree/api/v2/operator/event_charts_controller.rb +2 -8
  5. data/app/controllers/spree/api/v2/platform/places_controller.rb +4 -1
  6. data/app/controllers/spree/api/v2/storefront/dynamic_fields_controller.rb +42 -0
  7. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +2 -0
  8. data/app/controllers/spree/api/v2/storefront/trip_places_controller.rb +29 -0
  9. data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +3 -1
  10. data/app/controllers/spree/api/v2/storefront/trips_controller.rb +24 -0
  11. data/app/controllers/spree_cm_commissioner/admin/products_controller_decorator.rb +14 -0
  12. data/app/interactors/spree_cm_commissioner/billing_address_creator.rb +33 -0
  13. data/app/interactors/spree_cm_commissioner/create_vendor.rb +11 -1
  14. data/app/interactors/spree_cm_commissioner/google_places_fetcher.rb +1 -0
  15. data/app/interactors/spree_cm_commissioner/intercity_taxi_order_creator.rb +106 -0
  16. data/app/interactors/spree_cm_commissioner/transit/draft_order_creator.rb +17 -4
  17. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +1 -1
  18. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +1 -0
  19. data/app/models/spree_cm_commissioner/address_decorator.rb +17 -3
  20. data/app/models/spree_cm_commissioner/guest.rb +105 -56
  21. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +37 -27
  22. data/app/models/spree_cm_commissioner/order/address_book_decorator.rb +30 -12
  23. data/app/models/spree_cm_commissioner/order_decorator.rb +8 -1
  24. data/app/models/spree_cm_commissioner/place.rb +2 -0
  25. data/app/models/spree_cm_commissioner/product_decorator.rb +29 -0
  26. data/app/models/spree_cm_commissioner/saved_guest.rb +2 -0
  27. data/app/models/spree_cm_commissioner/taxon_decorator.rb +7 -0
  28. data/app/models/spree_cm_commissioner/trip.rb +4 -2
  29. data/app/models/spree_cm_commissioner/user_decorator.rb +1 -1
  30. data/app/models/spree_cm_commissioner/variant_options.rb +4 -0
  31. data/app/models/spree_cm_commissioner/vehicle.rb +2 -0
  32. data/app/models/spree_cm_commissioner/vendor_decorator.rb +3 -0
  33. data/app/overrides/spree/admin/products/_form/industry_taxons.html.erb.deface +26 -0
  34. data/app/overrides/spree/admin/products/_form/taxons.html.erb.deface +26 -0
  35. data/app/overrides/spree/admin/shared/sub_menu/_configuration/locations_tab.html.erb.deface +5 -0
  36. data/app/queries/spree_cm_commissioner/trip_query.rb +18 -18
  37. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +2 -0
  38. data/app/serializers/{spree → spree_cm_commissioner}/v2/storefront/amenity_serializer.rb +2 -2
  39. data/app/serializers/spree_cm_commissioner/v2/storefront/block_serializer.rb +9 -0
  40. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +1 -3
  41. data/app/serializers/spree_cm_commissioner/v2/storefront/seat_layout_serializer.rb +12 -0
  42. data/app/serializers/spree_cm_commissioner/v2/storefront/seat_section_serializer.rb +11 -0
  43. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_place_serializer.rb +10 -0
  44. data/app/serializers/{spree → spree_cm_commissioner}/v2/storefront/trip_query_result_serializer.rb +2 -2
  45. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_result_serializer.rb +17 -0
  46. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_serializer.rb +17 -0
  47. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +9 -0
  48. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_variant_serializer.rb +12 -0
  49. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vehicle_serializer.rb +16 -0
  50. data/app/serializers/{spree → spree_cm_commissioner}/v2/storefront/trip_vendor_serializer.rb +4 -1
  51. data/app/serializers/spree_cm_commissioner/v2/storefront/variant_block_serializer.rb +9 -0
  52. data/app/views/shared/_map.html.erb +5 -4
  53. data/app/views/spree/admin/locations/_form.html.erb +133 -0
  54. data/app/views/spree/admin/locations/edit.html.erb +11 -0
  55. data/app/views/spree/admin/locations/index.html.erb +67 -0
  56. data/app/views/spree/admin/locations/new.html.erb +11 -0
  57. data/config/locales/en.yml +2 -0
  58. data/config/routes.rb +4 -0
  59. data/db/migrate/20250812015522_remove_unique_indexes_from_cm_user_identity_providers.rb +8 -0
  60. data/db/migrate/20250812121745_add_data_fill_stage_phase_to_cm_guests.rb +5 -0
  61. data/db/migrate/20250814073854_add_call_to_action_to_spree_products.rb +5 -0
  62. data/lib/spree_cm_commissioner/transit/leg.rb +14 -0
  63. data/lib/spree_cm_commissioner/transit/seat_selection.rb +9 -1
  64. data/lib/spree_cm_commissioner/trip_query_result.rb +34 -0
  65. data/lib/spree_cm_commissioner/trip_result.rb +8 -1
  66. data/lib/spree_cm_commissioner/version.rb +1 -1
  67. metadata +31 -7
  68. data/app/serializers/spree/v2/storefront/trip_result_serializer.rb +0 -16
  69. data/app/serializers/spree/v2/storefront/trip_vehicle_serializer.rb +0 -11
@@ -226,62 +226,6 @@ module SpreeCmCommissioner
226
226
  save!
227
227
  end
228
228
 
229
- def missing_dynamic_fields_for_stage(stage)
230
- required_fields = line_item.product.dynamic_fields.where(data_fill_stage: stage)
231
- filled_ids = guest_dynamic_fields.map(&:dynamic_field_id)
232
-
233
- required_fields.reject { |field| filled_ids.include?(field.id) }
234
- end
235
-
236
- def all_required_dynamic_fields_completed?(stage)
237
- missing_dynamic_fields_for_stage(stage).blank?
238
- end
239
-
240
- def validate_dynamic_fields_for_phase(phase)
241
- required_fields = line_item.product.dynamic_fields.where(data_fill_stage: phase)
242
- filled_ids = guest_dynamic_fields.map(&:dynamic_field_id)
243
-
244
- required_fields.each do |field|
245
- errors.add(:base, "#{field.label} is required for #{phase} phase") unless filled_ids.include?(field.id)
246
- end
247
- end
248
-
249
- def pre_registration_completed?
250
- return true unless line_item.product.dynamic_fields.pre_registration.any?
251
-
252
- pre_registration_fields_completed?
253
- end
254
-
255
- def post_registration_completed?
256
- return true unless line_item.product.dynamic_fields.post_registration.any?
257
-
258
- post_registration_fields_completed?
259
- end
260
-
261
- def check_in_completed?
262
- return true unless line_item.product.dynamic_fields.during_check_in.any?
263
-
264
- check_in_fields_completed?
265
- end
266
-
267
- def current_collection_phase
268
- if check_in.present?
269
- :during_check_in
270
- elsif line_item.order.completed?
271
- :post_registration
272
- else
273
- :pre_registration
274
- end
275
- end
276
-
277
- def phase_completion_status
278
- {
279
- pre_registration: pre_registration_completed?,
280
- post_registration: post_registration_completed?,
281
- during_check_in: check_in_completed?
282
- }
283
- end
284
-
285
229
  # bib_number: 345, bib_prefix: 5KM, bib_zerofill: 5 => return 5KM00345
286
230
  # bib_number: 345, bib_prefix: 5KM, bib_zerofill: 2 => return 5KM345
287
231
  def formatted_bib_number
@@ -318,6 +262,111 @@ module SpreeCmCommissioner
318
262
  block_ids = line_item.order.blocks.pluck(:id)
319
263
  line_item.order.update(preload_block_ids: block_ids)
320
264
  end
265
+
266
+ def all_required_dynamic_fields_completed?(stage)
267
+ missing_dynamic_fields_for_stage(stage).blank?
268
+ end
269
+
270
+ state_machine :data_fill_stage_phase, initial: 'pre_registration' do
271
+ state 'pre_registration', value: 'pre_registration'
272
+ state 'post_registration', value: 'post_registration'
273
+ state 'during_check_in', value: 'during_check_in'
274
+ state 'completed', value: 'completed'
275
+
276
+ event :complete_pre_registration do
277
+ transition 'pre_registration' => 'post_registration',
278
+ if: -> (guest) { guest.pre_registration_completed? && guest.post_registration_fields? }
279
+ transition 'pre_registration' => 'during_check_in',
280
+ if: -> (guest) { guest.pre_registration_completed? && !guest.post_registration_fields? && guest.during_check_in_fields? }
281
+ transition 'pre_registration' => 'completed',
282
+ if: -> (guest) { guest.pre_registration_completed? && !guest.post_registration_fields? && !guest.during_check_in_fields? }
283
+ end
284
+
285
+ event :complete_post_registration do
286
+ transition 'post_registration' => 'during_check_in',
287
+ if: -> (guest) { guest.post_registration_completed? && guest.during_check_in_fields? }
288
+ transition 'post_registration' => 'completed',
289
+ if: -> (guest) { guest.post_registration_completed? && !guest.during_check_in_fields? }
290
+ end
291
+
292
+ event :complete_check_in do
293
+ transition 'during_check_in' => 'completed',
294
+ if: -> (guest) { guest.check_in_completed? }
295
+ end
296
+ end
297
+
298
+ def save_and_move_to_next_stage(_transition = nil)
299
+ # Trigger state machine transitions based on completion status
300
+ case data_fill_stage_phase
301
+ when 'pre_registration'
302
+ complete_pre_registration! if pre_registration_completed?
303
+ when 'post_registration'
304
+ complete_post_registration! if post_registration_completed?
305
+ when 'during_check_in'
306
+ complete_check_in! if check_in_completed?
307
+ end
308
+ end
309
+
310
+ def completed_phases
311
+ phases = []
312
+ phases << 'pre_registration' if pre_registration_completed?
313
+ phases << 'post_registration' if post_registration_completed?
314
+ phases << 'during_check_in' if check_in_completed?
315
+ phases
316
+ end
317
+
318
+ def pre_registration_completed?
319
+ return true unless line_item&.product&.dynamic_fields&.pre_registration&.any?
320
+
321
+ required_fields = line_item.product.dynamic_fields.pre_registration.pluck(:id)
322
+ filled_fields = guest_dynamic_fields.where(dynamic_field_id: required_fields).pluck(:dynamic_field_id)
323
+ (required_fields - filled_fields).empty?
324
+ end
325
+
326
+ def post_registration_completed?
327
+ return true unless line_item&.product&.dynamic_fields&.post_registration&.any?
328
+
329
+ required_fields = line_item.product.dynamic_fields.post_registration.pluck(:id)
330
+ filled_fields = guest_dynamic_fields.where(dynamic_field_id: required_fields).pluck(:dynamic_field_id)
331
+ (required_fields - filled_fields).empty?
332
+ end
333
+
334
+ def check_in_completed?
335
+ return true unless line_item&.product&.dynamic_fields&.during_check_in&.any?
336
+
337
+ required_fields = line_item.product.dynamic_fields.during_check_in.pluck(:id)
338
+ filled_fields = guest_dynamic_fields.where(dynamic_field_id: required_fields).pluck(:dynamic_field_id)
339
+ (required_fields - filled_fields).empty?
340
+ end
341
+
342
+ def pre_registration_fields?
343
+ line_item&.product&.dynamic_fields&.pre_registration&.any?
344
+ end
345
+
346
+ def post_registration_fields?
347
+ line_item&.product&.dynamic_fields&.post_registration&.any?
348
+ end
349
+
350
+ def during_check_in_fields?
351
+ line_item&.product&.dynamic_fields&.during_check_in&.any?
352
+ end
353
+
354
+ private
355
+
356
+ def product_dynamic_fields
357
+ @product_dynamic_fields ||= line_item.product.dynamic_fields
358
+ end
359
+
360
+ def missing_dynamic_fields_for_stage(stage)
361
+ return [] if line_item.blank? || line_item.product.blank?
362
+
363
+ # Cache product dynamic fields to avoid multiple queries
364
+ @product_dynamic_fields ||= line_item.product.dynamic_fields
365
+ required_fields = @product_dynamic_fields.where(data_fill_stage: stage)
366
+ filled_ids = guest_dynamic_fields&.pluck(:dynamic_field_id) || []
367
+
368
+ required_fields.reject { |field| filled_ids.include?(field.id) }
369
+ end
321
370
  end
322
371
  end
323
372
  # rubocop:enable Metrics/ClassLength
@@ -4,10 +4,13 @@ module SpreeCmCommissioner
4
4
  belongs_to :dynamic_field, class_name: 'SpreeCmCommissioner::DynamicField', optional: false
5
5
  belongs_to :dynamic_field_option, class_name: 'SpreeCmCommissioner::DynamicFieldOption', optional: true
6
6
 
7
- validates :value, presence: true
8
7
  validate :validate_value_format, if: -> { value.present? && dynamic_field.present? }
9
8
  validate :validate_option_reference, if: -> { dynamic_field_option.present? }
9
+ validate :validate_phase_sequence
10
10
  validate :check_required_fields_based_on_phase
11
+ validate :reject_empty_string_for_text_fields
12
+
13
+ after_commit :sync_guest_data_fill_stage_phase
11
14
 
12
15
  private
13
16
 
@@ -15,6 +18,8 @@ module SpreeCmCommissioner
15
18
  return if dynamic_field.blank?
16
19
 
17
20
  case dynamic_field.data_type.to_s
21
+ when 'text', 'textarea'
22
+ errors.add(:value, "can't be blank") if value.is_a?(String) && value.strip == ''
18
23
  when 'number'
19
24
  errors.add(:value, 'must be a number') unless value.to_s.match?(/\A\d+\z/)
20
25
  when 'boolean'
@@ -40,44 +45,49 @@ module SpreeCmCommissioner
40
45
  errors.add(:dynamic_field_option, 'must belong to the same dynamic field')
41
46
  end
42
47
 
43
- def before_checkin_phase?
44
- guest.present? && guest.line_item.present? && guest.check_in.nil?
45
- end
48
+ def validate_phase_sequence
49
+ return if dynamic_field.blank? || guest.blank?
46
50
 
47
- def after_purchase_phase?
48
- guest.present? && guest.line_item.present? && guest.line_item.completed? && guest.check_in.nil?
51
+ case dynamic_field.data_fill_stage.to_sym
52
+ when :post_registration
53
+ errors.add(:base, 'Must complete pre_registration fields first') unless guest.pre_registration_completed?
54
+ when :during_check_in
55
+ unless guest.post_registration_completed? || !guest.post_registration_fields?
56
+ errors.add(:base, 'Must complete post_registration fields first')
57
+ end
58
+ end
49
59
  end
50
60
 
51
- def during_checkin_phase?
52
- guest.present? && guest.check_in.present?
53
- end
61
+ def field_required_in_current_phase?
62
+ return false if dynamic_field.blank? || guest.blank?
54
63
 
55
- def pre_registration_required?
56
- dynamic_field.data_fill_stage.to_sym == :pre_registration &&
64
+ case dynamic_field.data_fill_stage.to_sym
65
+ when :pre_registration, :post_registration
57
66
  guest.line_item.order.completed? && guest.check_in.blank?
67
+ when :during_check_in
68
+ guest.check_in.present?
69
+ else
70
+ false
71
+ end
58
72
  end
59
73
 
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?
74
+ def check_required_fields_based_on_phase
75
+ return if dynamic_field.blank? || value.present?
76
+ return unless field_required_in_current_phase?
77
+
78
+ errors.add(:value, I18n.t("guest_dynamic_field.errors.#{dynamic_field.data_fill_stage}_required"))
63
79
  end
64
80
 
65
- def during_check_in_required?
66
- dynamic_field.data_fill_stage.to_sym == :during_check_in &&
67
- guest.check_in.present?
81
+ def sync_guest_data_fill_stage_phase
82
+ guest&.save_and_move_to_next_stage
68
83
  end
69
84
 
70
- def check_required_fields_based_on_phase
71
- return if dynamic_field.blank? || value.present?
85
+ def reject_empty_string_for_text_fields
86
+ return if dynamic_field.blank?
87
+ return unless %w[text textarea].include?(dynamic_field.data_type.to_s)
88
+ return unless value.is_a?(String)
72
89
 
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
90
+ errors.add(:value, "can't be blank") if value.strip == ''
81
91
  end
82
92
  end
83
93
  end
@@ -5,30 +5,48 @@
5
5
  module SpreeCmCommissioner
6
6
  module Order
7
7
  module AddressBookDecorator
8
- def guest_order? = user.blank?
9
-
10
8
  # override
11
9
  def bill_address_attributes=(attributes)
12
- attributes[:id] = bill_address&.id if attributes[:id].blank?
13
- return if guest_order? && !basic_info_included?(attributes)
10
+ attributes[:id] = bill_address_id if attributes[:id].blank?
11
+ attributes[:phone] = intel_phone_number if intel_phone_number.present?
14
12
 
15
- super
13
+ self.bill_address = assign_address_attributes(attributes)
14
+ user.bill_address = bill_address if user && user.bill_address.nil?
16
15
  end
17
16
 
18
17
  # override
19
18
  def ship_address_attributes=(attributes)
20
- attributes[:id] = bill_address&.id if attributes[:id].blank?
21
- return if guest_order? && !basic_info_included?(attributes)
19
+ attributes[:id] = ship_address_id if attributes[:id].blank?
20
+ attributes[:phone] = intel_phone_number if intel_phone_number.present?
22
21
 
23
- super
22
+ self.ship_address = assign_address_attributes(attributes)
23
+ user.ship_address = ship_address if user && user.ship_address.nil?
24
24
  end
25
25
 
26
- private
26
+ def assign_address_attributes(attributes = {})
27
+ return if attributes.blank?
28
+
29
+ attributes.transform_values! { |v| v == '' ? nil : v }
30
+ attributes = attributes.to_h.symbolize_keys
27
31
 
28
- def basic_info_included?(attributes)
29
- return true if attributes[:id].present?
32
+ existing_address = attributes[:id].present? ? find_existing_address(attributes[:id]) : nil
33
+
34
+ if existing_address&.editable?
35
+ existing_address.assign_attributes(attributes)
36
+ existing_address
37
+ else
38
+ ::Spree::Address.new(attributes.except(:id, :updated_at, :created_at))
39
+ end
40
+ end
30
41
 
31
- %i[firstname lastname phone].all? { |key| attributes[key].present? }
42
+ def find_existing_address(id)
43
+ if user.present?
44
+ ::Spree::Address.where('(user_id = ? OR user_id IS NULL) AND deleted_at IS NULL', user.id)
45
+ .find_by(id: id)
46
+ else
47
+ ::Spree::Address.where(user_id: nil, deleted_at: nil)
48
+ .find_by(id: id)
49
+ end
32
50
  end
33
51
  end
34
52
  end
@@ -43,7 +43,12 @@ module SpreeCmCommissioner
43
43
  base.has_many :vendors, through: :products, class_name: 'Spree::Vendor'
44
44
  base.has_many :taxons, through: :products, class_name: 'Spree::Taxon'
45
45
  base.has_many :guests, through: :line_items, class_name: 'SpreeCmCommissioner::Guest'
46
- base.has_many :saved_guests, -> { distinct }, through: :guests, class_name: 'SpreeCmCommissioner::SavedGuest'
46
+
47
+ base.has_many :saved_guests, -> { distinct.reorder('cm_saved_guests.created_at DESC') },
48
+ through: :guests,
49
+ source: :saved_guest,
50
+ class_name: 'SpreeCmCommissioner::SavedGuest'
51
+
47
52
  base.has_many :blocks, through: :guests, class_name: 'SpreeCmCommissioner::Block', source: :block
48
53
  base.has_many :reserved_blocks, through: :guests, class_name: 'SpreeCmCommissioner::ReservedBlock'
49
54
  base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variants
@@ -53,6 +58,8 @@ module SpreeCmCommissioner
53
58
  base.whitelisted_ransackable_associations |= %w[customer taxon payments guests invoice]
54
59
  base.whitelisted_ransackable_attributes |= %w[intel_phone_number phone_number email number state]
55
60
 
61
+ base.accepts_nested_attributes_for :saved_guests, allow_destroy: true
62
+
56
63
  def base.search_by_qr_data!(data)
57
64
  token = data.match(/^R\d{9,}-([A-Za-z0-9_\-]+)$/)&.captures
58
65
 
@@ -14,6 +14,8 @@ module SpreeCmCommissioner
14
14
  has_many :product_places, class_name: 'SpreeCmCommissioner::ProductPlace', dependent: :destroy
15
15
  has_many :products, through: :product_places
16
16
 
17
+ has_many :location_vendor_places, -> { where(place_type: :location) }, class_name: 'SpreeCmCommissioner::VendorPlace'
18
+
17
19
  has_many :children, -> { order(:lft) }, class_name: 'SpreeCmCommissioner::Place', foreign_key: :parent_id, dependent: :destroy
18
20
 
19
21
  has_many :taxon_places, class_name: 'SpreeCmCommissioner::TaxonPlace', dependent: :destroy
@@ -48,6 +48,9 @@ module SpreeCmCommissioner
48
48
 
49
49
  base.belongs_to :event, class_name: 'Spree::Taxon', optional: true
50
50
 
51
+ base.has_many :industry_classifications, -> { joins(:taxon).where(spree_taxons: { kind: :industry }) }, class_name: 'Spree::Classification'
52
+ base.has_many :industry_taxons, through: :industry_classifications, source: :taxon
53
+
51
54
  base.scope :min_price, lambda { |vendor|
52
55
  joins(:prices_including_master)
53
56
  .where(vendor_id: vendor.id, product_type: vendor.primary_product_type)
@@ -74,6 +77,16 @@ module SpreeCmCommissioner
74
77
  base.after_update :sync_event_id_to_children, if: :saved_change_to_event_id?
75
78
 
76
79
  base.enum purchasable_on: { both: 0, web: 1, app: 2 }
80
+ base.enum call_to_action: { buy_now: 0,
81
+ book_now: 1,
82
+ reserve: 2,
83
+ register: 3,
84
+ apply: 4,
85
+ get_ticket: 5,
86
+ secure_your_seat: 6,
87
+ join_the_event: 7,
88
+ request_to_book: 8
89
+ }
77
90
 
78
91
  base.multi_tenant :tenant, class_name: 'SpreeCmCommissioner::Tenant'
79
92
  base.before_save :set_tenant
@@ -83,6 +96,22 @@ module SpreeCmCommissioner
83
96
  "#{Spree::Store.default.formatted_url}/tickets/#{slug}"
84
97
  end
85
98
 
99
+ def action_button
100
+ return :request_to_book if need_confirmation?
101
+ return :reserve if free?
102
+ return call_to_action.to_sym if call_to_action.present?
103
+
104
+ :buy_now
105
+ end
106
+
107
+ def free?
108
+ return false if prices_including_master.empty?
109
+
110
+ prices_including_master.all? do |master_price|
111
+ master_price.price.zero? && (master_price.compare_at_price.nil? || master_price.compare_at_price.zero?)
112
+ end
113
+ end
114
+
86
115
  def dynamic_fields_by_collection_phase
87
116
  {
88
117
  pre_registration: dynamic_fields.pre_registration.order(:position),
@@ -9,5 +9,7 @@ module SpreeCmCommissioner
9
9
  belongs_to :nationality, class_name: 'Spree::Taxon', optional: true
10
10
 
11
11
  has_many :guests, class_name: 'SpreeCmCommissioner::Guest', dependent: :nullify
12
+
13
+ accepts_nested_attributes_for :guests, allow_destroy: true
12
14
  end
13
15
  end
@@ -46,6 +46,7 @@ module SpreeCmCommissioner
46
46
  base.validates_associated :category_icon
47
47
  base.before_save :set_kind
48
48
  base.before_save :set_slug
49
+ base.before_save :normalize_custom_redirect_url
49
50
 
50
51
  base.after_update :sync_event_dates_to_line_items, if: -> { saved_change_to_from_date? || saved_change_to_to_date? }
51
52
 
@@ -140,6 +141,12 @@ module SpreeCmCommissioner
140
141
 
141
142
  ::SpreeCmCommissioner::EventLineItemsDateSyncerJob.perform_later(id)
142
143
  end
144
+
145
+ private
146
+
147
+ def normalize_custom_redirect_url
148
+ self.custom_redirect_url = nil if custom_redirect_url.blank?
149
+ end
143
150
  end
144
151
  end
145
152
 
@@ -8,6 +8,7 @@ module SpreeCmCommissioner
8
8
 
9
9
  belongs_to :vendor, class_name: 'Spree::Vendor', inverse_of: :trips, optional: false
10
10
  belongs_to :product, class_name: 'Spree::Product', inverse_of: :trip, optional: false
11
+
11
12
  belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle', optional: false
12
13
 
13
14
  belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place', optional: false
@@ -17,8 +18,8 @@ module SpreeCmCommissioner
17
18
  has_many :blazer_queries, through: :trip_blazer_queries, source: :blazer_query, class_name: 'Blazer::Query'
18
19
 
19
20
  has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy
20
- has_many :boarding_trip_stops, -> { boarding }, class_name: 'SpreeCmCommissioner::TripStop'
21
- has_many :drop_off_trip_stops, -> { drop_off }, class_name: 'SpreeCmCommissioner::TripStop'
21
+ has_many :boarding_trip_stops, -> { boarding.order('departure_time ASC NULLS LAST, id ASC') }, class_name: 'SpreeCmCommissioner::TripStop'
22
+ has_many :drop_off_trip_stops, -> { drop_off.order('arrival_time ASC NULLS LAST') }, class_name: 'SpreeCmCommissioner::TripStop'
22
23
 
23
24
  has_many :variants, through: :product
24
25
  has_many :inventory_items, through: :variants
@@ -32,6 +33,7 @@ module SpreeCmCommissioner
32
33
  accepts_nested_attributes_for :trip_stops, :product, allow_destroy: true
33
34
 
34
35
  self.whitelisted_ransackable_associations = %w[product vehicle]
36
+ self.whitelisted_ransackable_attributes = %w[origin_place_id destination_place_id]
35
37
 
36
38
  def convert_duration_to_seconds
37
39
  return if hours.blank? && minutes.blank? && seconds.blank?
@@ -90,7 +90,7 @@ module SpreeCmCommissioner
90
90
 
91
91
  def vendor_organizer?(vendor)
92
92
  spree_roles.any? do |role|
93
- role.name == "#{vendor.name.downcase.parameterize(separator: '-')}-organizer"
93
+ role.name == "#{vendor.id}-organizer"
94
94
  end
95
95
  end
96
96
 
@@ -139,6 +139,10 @@ module SpreeCmCommissioner
139
139
  @seat_number_layouts ||= option_value_name_for(option_type_name: 'seat-number-layouts')&.split(',')
140
140
  end
141
141
 
142
+ def seat_type
143
+ @seat_type ||= option_value_name_for(option_type_name: 'seat-type')
144
+ end
145
+
142
146
  # can consider as customers.
143
147
  def number_of_guests
144
148
  number_of_adults + number_of_kids
@@ -12,6 +12,8 @@ module SpreeCmCommissioner
12
12
  has_many :vehicle_option_types, class_name: 'SpreeCmCommissioner::VehicleOptionType', dependent: :destroy
13
13
  has_many :option_value_vehicles, class_name: 'SpreeCmCommissioner::OptionValueVehicle', dependent: :destroy
14
14
  has_many :option_values, through: :option_value_vehicles, class_name: 'Spree::OptionValue'
15
+ has_many :amenities, through: :option_value_vehicles, source: :option_value
16
+
15
17
  validates :code, uniqueness: { scope: :vendor_id }, presence: true
16
18
  validates :license_plate, uniqueness: true, allow_blank: true
17
19
 
@@ -84,6 +84,9 @@ module SpreeCmCommissioner
84
84
 
85
85
  base.has_many :vehicles, class_name: 'SpreeCmCommissioner::Vehicle', dependent: :destroy
86
86
  base.has_many :trips, class_name: 'SpreeCmCommissioner::Trip', dependent: :destroy
87
+ base.has_many :trip_stops, through: :trips, class_name: 'SpreeCmCommissioner::TripStop'
88
+ base.has_many :boarding_trip_stops, through: :trips, class_name: 'SpreeCmCommissioner::TripStop', source: :boarding_trip_stops
89
+ base.has_many :drop_off_trip_stops, through: :trips, class_name: 'SpreeCmCommissioner::TripStop', source: :drop_off_trip_stops
87
90
 
88
91
  base.validates :account_name, :account_number, presence: true, if: lambda {
89
92
  payment_qrcode.present? && Spree::Store.default.code.include?('billing')
@@ -0,0 +1,26 @@
1
+ <!-- insert_after "[data-hook='admin_product_form_taxons']" -->
2
+
3
+ <div data-hook="admin_product_form_industry_taxons">
4
+ <%= f.field_container :industry_taxons do %>
5
+ <%= f.label :industry_taxon_ids, Spree.t(:Industries) %>
6
+
7
+ <% if can? :modify, Spree::Classification %>
8
+ <%= f.select :industry_taxon_ids, options_from_collection_for_select(@product.industry_taxons, :id, :pretty_name, @product.industry_taxon_ids),
9
+ { include_hidden: true },
10
+ multiple: true,
11
+ data: { autocomplete_url_value: 'taxons_api_v2',
12
+ autocomplete_return_attr_value: 'pretty_name',
13
+ autocomplete_multiple_value: true,
14
+ autocomplete_additional_url_params_value: "q[kind_eq]=7" } %>
15
+ <% elsif @product.industry_taxons.any? %>
16
+ <ul class="text_list">
17
+ <% @product.industry_taxons.each do |taxon| %>
18
+ <li><%= taxon.name %></li>
19
+ <% end %>
20
+ </ul>
21
+ <% else %>
22
+ <div class="alert alert-info"><%= Spree.t(:no_resource_found, resource: :taxons) %></div>
23
+ <% end %>
24
+
25
+ <% end %>
26
+ </div>
@@ -0,0 +1,26 @@
1
+ <!-- replace "[data-hook='admin_product_form_taxons']" -->
2
+
3
+ <div data-hook="admin_product_form_taxons">
4
+ <%= f.field_container :taxons do %>
5
+ <%= f.label :taxon_ids, Spree.t(:taxons) %>
6
+
7
+ <% if can? :modify, Spree::Classification %>
8
+ <%= f.select :taxon_ids, options_from_collection_for_select(@product.taxons, :id, :pretty_name, @product.taxon_ids.reject { |id| @product.industry_taxon_ids.include?(id) }),
9
+ { include_hidden: true },
10
+ multiple: true,
11
+ data: { autocomplete_url_value: 'taxons_api_v2',
12
+ autocomplete_return_attr_value: 'pretty_name',
13
+ autocomplete_multiple_value: true,
14
+ autocomplete_additional_url_params_value: "q[kind_not_eq]=7" } %>
15
+ <% elsif @product.taxons.any? %>
16
+ <ul class="text_list">
17
+ <% @product.taxons.each do |taxon| %>
18
+ <li><%= taxon.name %></li>
19
+ <% end %>
20
+ </ul>
21
+ <% else %>
22
+ <div class="alert alert-info"><%= Spree.t(:no_resource_found, resource: :taxons) %></div>
23
+ <% end %>
24
+
25
+ <% end %>
26
+ </div>
@@ -0,0 +1,5 @@
1
+ <!-- insert_top "[data-hook='admin_configurations_sidebar_menu']" -->
2
+
3
+ <%= tab :locations, match_path: '/locations', label: 'locations' if can? :manage, SpreeCmCommissioner::Place %>
4
+
5
+