spree_cm_commissioner 2.0.3.pre.pre6 → 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 (46) 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/platform/places_controller.rb +4 -1
  5. data/app/controllers/spree/api/v2/storefront/dynamic_fields_controller.rb +42 -0
  6. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +2 -0
  7. data/app/controllers/spree/api/v2/storefront/trip_places_controller.rb +1 -1
  8. data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +1 -1
  9. data/app/controllers/spree/api/v2/storefront/trips_controller.rb +24 -0
  10. data/app/interactors/spree_cm_commissioner/billing_address_creator.rb +33 -0
  11. data/app/interactors/spree_cm_commissioner/create_vendor.rb +11 -1
  12. data/app/interactors/spree_cm_commissioner/google_places_fetcher.rb +1 -0
  13. data/app/interactors/spree_cm_commissioner/intercity_taxi_order_creator.rb +106 -0
  14. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +1 -0
  15. data/app/models/spree_cm_commissioner/guest.rb +105 -56
  16. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +37 -27
  17. data/app/models/spree_cm_commissioner/taxon_decorator.rb +7 -0
  18. data/app/models/spree_cm_commissioner/user_decorator.rb +1 -1
  19. data/app/models/spree_cm_commissioner/variant_options.rb +4 -0
  20. data/app/models/spree_cm_commissioner/vehicle.rb +2 -0
  21. data/app/overrides/spree/admin/shared/sub_menu/_configuration/locations_tab.html.erb.deface +5 -0
  22. data/app/serializers/{spree → spree_cm_commissioner}/v2/storefront/amenity_serializer.rb +2 -2
  23. data/app/serializers/spree_cm_commissioner/v2/storefront/block_serializer.rb +9 -0
  24. data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +1 -3
  25. data/app/serializers/spree_cm_commissioner/v2/storefront/seat_layout_serializer.rb +12 -0
  26. data/app/serializers/spree_cm_commissioner/v2/storefront/seat_section_serializer.rb +11 -0
  27. data/app/serializers/{spree → spree_cm_commissioner}/v2/storefront/trip_place_serializer.rb +1 -1
  28. data/app/serializers/{spree → spree_cm_commissioner}/v2/storefront/trip_query_result_serializer.rb +2 -2
  29. data/app/serializers/{spree → spree_cm_commissioner}/v2/storefront/trip_result_serializer.rb +4 -4
  30. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_serializer.rb +17 -0
  31. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +9 -0
  32. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_variant_serializer.rb +12 -0
  33. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vehicle_serializer.rb +16 -0
  34. data/app/serializers/{spree → spree_cm_commissioner}/v2/storefront/trip_vendor_serializer.rb +4 -1
  35. data/app/serializers/spree_cm_commissioner/v2/storefront/variant_block_serializer.rb +9 -0
  36. data/app/views/shared/_map.html.erb +5 -4
  37. data/app/views/spree/admin/locations/_form.html.erb +133 -0
  38. data/app/views/spree/admin/locations/edit.html.erb +11 -0
  39. data/app/views/spree/admin/locations/index.html.erb +67 -0
  40. data/app/views/spree/admin/locations/new.html.erb +11 -0
  41. data/config/locales/en.yml +2 -0
  42. data/config/routes.rb +4 -0
  43. data/db/migrate/20250812121745_add_data_fill_stage_phase_to_cm_guests.rb +5 -0
  44. data/lib/spree_cm_commissioner/version.rb +1 -1
  45. metadata +26 -8
  46. data/app/serializers/spree/v2/storefront/trip_vehicle_serializer.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3386c245e402dd11660e1215a50dac52acfd59950f95afb03117bff6c56053a5
4
- data.tar.gz: 551927bdc54caee21d0ca26601c2d42b5ee0cd14b5c4264da1a5ae3968270d4d
3
+ metadata.gz: 0a6380c1548ccc1bbd8756837c2a3d4fc629613fa39f4f4949580c62f3c6fd97
4
+ data.tar.gz: 30371e0f95da706ac53f81141745cd36d88a9c631feb9de813fd6c2a00c4d641
5
5
  SHA512:
6
- metadata.gz: f8c9f527ea17e43fabfcf7ecba55e70887a51a31a85849e918cf508cc8bc8d6a3b6f6165f534139fad9704902a7aea8875b58798514caf63882b2914534cc693
7
- data.tar.gz: ca8d78212f84afd5091ac23617a88d49b096cc526e84a3aedc2c9b432fe02aaf55546a5a54bfe8c0704be30a72050d01c7be88a505b37abea3f4d634a1829ea5
6
+ metadata.gz: 362e42693a4d2ef816db1a3465efe55f789753d1ee1ffa9a9795dbe727be4b03b0088f9c888274589ddb604b76cc6143c7119388f9094a6b66c185e9f8b6b8dc
7
+ data.tar.gz: ab28c8455539f16d82b0c7c24fc74ab9f1947679e296b4b849785820fba719ed09aadfb0338c570304b1c874d83d6d20743cd40f176801c6bf529a90f3401bee
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.0.3.pre.pre6)
37
+ spree_cm_commissioner (2.0.3.pre.pre8)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -0,0 +1,62 @@
1
+ module Spree
2
+ module Admin
3
+ class LocationsController < Spree::Admin::ResourceController
4
+ def index
5
+ @search = model_class.ransack(params[:q])
6
+ @locations = @search.result.order(created_at: :desc).page(params[:page]).per(15)
7
+ end
8
+
9
+ def create
10
+ if location_exist?
11
+ flash[:error] = I18n.t('location.already_exist')
12
+ render :new, status: :unprocessable_entity and return
13
+ end
14
+
15
+ location = model_class.new(permitted_resource_params)
16
+
17
+ if location.save
18
+ flash[:success] = flash_message_for(location, :successfully_created)
19
+ redirect_to spree.admin_locations_path
20
+ else
21
+ flash.now[:error] = location.errors.full_messages.to_sentence
22
+ render :new, status: :unprocessable_entity
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def location_exist?
29
+ SpreeCmCommissioner::Place.find_by(reference: permitted_resource_params[:reference])
30
+ end
31
+
32
+ # overrided
33
+ def model_class
34
+ SpreeCmCommissioner::Place
35
+ end
36
+
37
+ # override
38
+ def object_name
39
+ 'spree_cm_commissioner_location'
40
+ end
41
+
42
+ def collection_url(options = {})
43
+ admin_locations_url(options)
44
+ end
45
+
46
+ def edit_object_url(object = nil, options = {})
47
+ edit_admin_location_url(object || @object, options)
48
+ end
49
+
50
+ # overrided
51
+ def new_object_url(options = {})
52
+ new_admin_location_url(options)
53
+ end
54
+
55
+ def permitted_resource_params
56
+ params.require(:spree_cm_commissioner_place).permit(:name, :lat, :lon, :formatted_address, :types, :reference,
57
+ :vicinity, :icon, :rating
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -41,10 +41,13 @@ module Spree
41
41
 
42
42
  def struct_places_with_id(places)
43
43
  places.map do |place|
44
+ short_addr = place.vicinity || place.formatted_address
45
+ name = "#{place.name} - #{short_addr}"
46
+
44
47
  place_struct = Struct.new(:id, :name, :base_64_content)
45
48
  place_struct.new(
46
49
  id: place.reference,
47
- name: place.name,
50
+ name: name,
48
51
  base_64_content: Base64.strict_encode64(place.to_json)
49
52
  )
50
53
  end
@@ -0,0 +1,42 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class DynamicFieldsController < ::Spree::Api::V2::ResourceController
6
+ before_action :load_product
7
+
8
+ # override
9
+ def collection
10
+ return @collection if defined?(@collection)
11
+
12
+ dynamic_fields = @product.dynamic_fields
13
+ if params[:phase].present?
14
+ requested_phase = params[:phase].to_s
15
+ if SpreeCmCommissioner::DynamicField.data_fill_stages.key?(requested_phase)
16
+ dynamic_fields = dynamic_fields.where(data_fill_stage: requested_phase)
17
+ end
18
+ end
19
+
20
+ @collection = dynamic_fields.order(:position)
21
+ end
22
+
23
+ private
24
+
25
+ def load_product
26
+ return render_error_payload({ error: 'product_id is required' }, 400) if params[:product_id].blank?
27
+
28
+ @product ||= Spree::Product.find(params[:product_id])
29
+ end
30
+
31
+ def collection_serializer
32
+ SpreeCmCommissioner::V2::Storefront::DynamicFieldSerializer
33
+ end
34
+
35
+ def model_class
36
+ SpreeCmCommissioner::DynamicField
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -29,6 +29,7 @@ module Spree
29
29
  resource = scope.new(guest_params)
30
30
 
31
31
  if resource.save
32
+ resource.save_and_move_to_next_stage
32
33
  render_serialized_payload(201) { serialize_resource(resource) }
33
34
  else
34
35
  render_error_payload(resource.errors, 400)
@@ -54,6 +55,7 @@ module Spree
54
55
  resource.save!
55
56
  end
56
57
 
58
+ resource.save_and_move_to_next_stage
57
59
  render_serialized_payload { serialize_resource(resource) }
58
60
  rescue ActiveRecord::RecordInvalid => e
59
61
  render_error_payload(e.record.errors, 400)
@@ -20,7 +20,7 @@ module Spree
20
20
  end
21
21
 
22
22
  def collection_serializer
23
- Spree::V2::Storefront::TripPlaceSerializer
23
+ SpreeCmCommissioner::V2::Storefront::TripPlaceSerializer
24
24
  end
25
25
  end
26
26
  end
@@ -29,7 +29,7 @@ module Spree
29
29
  end
30
30
 
31
31
  def serialize_collection(collection)
32
- serialized_data = Spree::V2::Storefront::TripQueryResultSerializer.new(
32
+ serialized_data = SpreeCmCommissioner::V2::Storefront::TripQueryResultSerializer.new(
33
33
  collection,
34
34
  include: default_resource_includes,
35
35
  params: serializer_params
@@ -0,0 +1,24 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class TripsController < ::Spree::Api::V2::ResourceController
6
+ # override
7
+ def resource
8
+ model_class.find(params[:id])
9
+ end
10
+
11
+ # override
12
+ def model_class
13
+ SpreeCmCommissioner::Trip
14
+ end
15
+
16
+ # override
17
+ def resource_serializer
18
+ SpreeCmCommissioner::V2::Storefront::TripSerializer
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ module SpreeCmCommissioner
2
+ class BillingAddressCreator < BaseInteractor
3
+ delegate :order, to: :context
4
+
5
+ def call
6
+ validate_context!
7
+ create_address
8
+ end
9
+
10
+ private
11
+
12
+ def validate_context!
13
+ context.fail!(message: 'Order is missing') unless order
14
+ end
15
+
16
+ def create_address
17
+ line_item = order.line_items.first
18
+ guest = line_item&.guests&.first
19
+
20
+ billing_address = order.build_bill_address(
21
+ firstname: guest&.first_name,
22
+ lastname: guest&.last_name,
23
+ phone: order.intel_phone_number || order.phone_number
24
+ )
25
+
26
+ context.fail!(message: billing_address.errors.full_messages.to_sentence) unless billing_address.save!
27
+ # Persist association on the order so subsequent reloads reflect the new billing address
28
+ order.update!(bill_address: billing_address)
29
+
30
+ context.billing_address = billing_address
31
+ end
32
+ end
33
+ end
@@ -8,6 +8,7 @@ module SpreeCmCommissioner
8
8
  ActiveRecord::Base.transaction do
9
9
  create_vendor
10
10
  assign_vendor_to_user
11
+ create_logo
11
12
  create_role
12
13
  end
13
14
  end
@@ -30,9 +31,18 @@ module SpreeCmCommissioner
30
31
  context.user.vendors << @vendor
31
32
  end
32
33
 
34
+ def create_logo
35
+ return if context.logo.blank?
36
+
37
+ vendor_logo = SpreeCmCommissioner::VendorLogo.create(viewable: @vendor, attachment: context.logo)
38
+ return if vendor_logo.persisted?
39
+
40
+ context.fail!(message: vendor_logo.errors.full_messages.join(', '))
41
+ end
42
+
33
43
  def create_role
34
44
  role = context.user.spree_roles.create(
35
- name: "#{@vendor.name.downcase.parameterize(separator: '-')}-organizer",
45
+ name: "#{@vendor.id}-organizer",
36
46
  vendor_id: @vendor.id,
37
47
  presentation: 'Organizer'
38
48
  )
@@ -11,6 +11,7 @@ module SpreeCmCommissioner
11
11
  types: json['types'].blank? ? '' : json['types'][0],
12
12
  name: json['name'],
13
13
  vicinity: json['vicinity'],
14
+ formatted_address: json['formatted_address'],
14
15
  lat: json['geometry']['location']['lat'],
15
16
  lon: json['geometry']['location']['lng'],
16
17
  icon: json['icon'],
@@ -0,0 +1,106 @@
1
+ module SpreeCmCommissioner
2
+ class IntercityTaxiOrderCreator < BaseInteractor
3
+ delegate :trip, :raw_params, :current_vendor, to: :context
4
+
5
+ def call
6
+ validate_context!
7
+
8
+ ActiveRecord::Base.transaction do
9
+ build_order_params
10
+ create_order
11
+ create_billing_address
12
+ advance_order
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def validate_context!
19
+ context.fail!(message: 'Trip is missing') unless trip
20
+ context.fail!(message: 'Raw parameters are missing') unless raw_params
21
+ context.fail!(message: 'Current vendor is missing') unless current_vendor
22
+ end
23
+
24
+ def build_order_params
25
+ context.order_params = build_taxi_booking_params
26
+ end
27
+
28
+ def build_taxi_booking_params
29
+ permitted_params = raw_params.require(:order).permit(
30
+ :intel_phone_number,
31
+ :email,
32
+ :store_id,
33
+ line_items_attributes: [
34
+ :remark,
35
+ :variant_id,
36
+ :quantity,
37
+ :vendor_id,
38
+ :from_date,
39
+ :to_date,
40
+ :currency,
41
+ :direction,
42
+ :trip_id,
43
+ :pickup_place_name,
44
+ :drop_off_place_name,
45
+ :pickup_lat,
46
+ :pickup_lng,
47
+ :drop_off_lat,
48
+ :drop_off_lng,
49
+ :passenger_count,
50
+ :pickup_oob_confirmed,
51
+ :drop_off_oob_confirmed,
52
+ { guests_attributes: %i[
53
+ first_name last_name gender age nationality_id
54
+ ]
55
+ }
56
+ ]
57
+ )
58
+
59
+ process_line_items_attributes(permitted_params)
60
+ permitted_params
61
+ end
62
+
63
+ def process_line_items_attributes(params)
64
+ date_param = raw_params[:date] || Date.current.to_s
65
+
66
+ params[:line_items_attributes].each_value do |item|
67
+ pickup_time_minutes = item[:from_date]
68
+ combined_datetime = "#{date_param} #{pickup_time_minutes}"
69
+
70
+ item[:from_date] = combined_datetime
71
+ item[:to_date] = combined_datetime
72
+ item[:variant_id] = load_variant
73
+ item[:vendor_id] = current_vendor.id
74
+ item[:quantity] = 1
75
+ item[:currency] = 'USD'
76
+ item[:direction] = 'outbound'
77
+ item[:trip_id] = trip.id
78
+ end
79
+ end
80
+
81
+ def create_order
82
+ context.order = Spree::Order.create(context.order_params)
83
+
84
+ context.fail!(message: context.order.errors.full_messages.to_sentence) unless context.order.persisted?
85
+ end
86
+
87
+ def create_billing_address
88
+ return if context.order.bill_address.present?
89
+
90
+ SpreeCmCommissioner::BillingAddressCreator.call(
91
+ order: context.order
92
+ )
93
+ end
94
+
95
+ def advance_order
96
+ context.order.update_with_updater!
97
+
98
+ advance_service = Spree::Api::Dependencies.storefront_checkout_advance_service.constantize
99
+ advance_service.call(order: context.order)
100
+ end
101
+
102
+ def load_variant
103
+ trip.product.master.id
104
+ end
105
+ end
106
+ end
@@ -29,6 +29,7 @@ module SpreeCmCommissioner
29
29
  :bib_pre_generation_on_create?,
30
30
  :seat_number_positions,
31
31
  :seat_number_layouts,
32
+ :seat_type,
32
33
  to: :options
33
34
  end
34
35
 
@@ -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