spree_cm_commissioner 2.5.1.pre.pre2 → 2.5.1.pre.pre3

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/spree/api/chatrace/guests_controller.rb +37 -20
  4. data/app/controllers/spree/api/v2/operator/check_in_bulks_controller.rb +9 -0
  5. data/app/controllers/spree/api/v2/storefront/account/qr_data_controller.rb +30 -0
  6. data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +1 -1
  7. data/app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb +4 -4
  8. data/app/controllers/spree/transit/trips_controller.rb +3 -3
  9. data/app/finders/spree_cm_commissioner/places/find_with_route.rb +12 -12
  10. data/app/finders/spree_cm_commissioner/routes/find_popular.rb +35 -20
  11. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +4 -11
  12. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +1 -10
  13. data/app/{services/spree_cm_commissioner/transit_order/create.rb → interactors/spree_cm_commissioner/transit/draft_order_creator.rb} +16 -13
  14. data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +3 -4
  15. data/app/interactors/spree_cm_commissioner/trip_stops_creator.rb +2 -2
  16. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +2 -2
  17. data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +10 -0
  18. data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +10 -0
  19. data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +13 -0
  20. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +10 -0
  21. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +10 -0
  22. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +16 -11
  23. data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +2 -2
  24. data/app/models/concerns/spree_cm_commissioner/route_trip_count_callbacks.rb +48 -0
  25. data/app/models/concerns/spree_cm_commissioner/user_identity.rb +0 -6
  26. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +4 -0
  27. data/app/models/spree_cm_commissioner/guest.rb +4 -4
  28. data/app/models/spree_cm_commissioner/place.rb +8 -5
  29. data/app/models/spree_cm_commissioner/product_decorator.rb +0 -1
  30. data/app/models/spree_cm_commissioner/route.rb +5 -46
  31. data/app/models/spree_cm_commissioner/trip.rb +33 -8
  32. data/app/models/spree_cm_commissioner/trip_connection.rb +36 -0
  33. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -16
  34. data/app/models/spree_cm_commissioner/user_decorator.rb +30 -18
  35. data/app/models/spree_cm_commissioner/vendor_decorator.rb +1 -3
  36. data/app/models/spree_cm_commissioner/vendor_route.rb +9 -0
  37. data/app/queries/spree_cm_commissioner/guest_searcher_query.rb +25 -2
  38. data/app/queries/spree_cm_commissioner/trip_query.rb +2 -2
  39. data/app/serializers/spree/v2/storefront/user_serializer_decorator.rb +2 -1
  40. data/app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb +1 -0
  41. data/app/serializers/spree_cm_commissioner/v2/operator/line_item_serializer.rb +2 -2
  42. data/app/serializers/spree_cm_commissioner/v2/operator/{line_item_order_serializer.rb → order_serializer.rb} +1 -2
  43. data/app/serializers/spree_cm_commissioner/v2/operator/user_serializer.rb +11 -0
  44. data/app/serializers/spree_cm_commissioner/v2/operator/variant_serializer.rb +9 -0
  45. data/app/serializers/spree_cm_commissioner/v2/storefront/route_serializer.rb +1 -3
  46. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +1 -1
  47. data/app/services/spree_cm_commissioner/{route_metrics/update_route_metrics.rb → routes/base_update_order_metrics.rb} +16 -11
  48. data/app/services/spree_cm_commissioner/routes/decrement_previous_trip_count.rb +30 -0
  49. data/app/services/spree_cm_commissioner/routes/decrement_trip_count.rb +33 -0
  50. data/app/services/spree_cm_commissioner/{route_metrics/increase_fulfilled_order_count.rb → routes/increment_fulfilled_order_count.rb} +3 -3
  51. data/app/services/spree_cm_commissioner/{route_metrics/increase_order_count.rb → routes/increment_order_count.rb} +3 -3
  52. data/app/services/spree_cm_commissioner/routes/increment_trip_count.rb +33 -0
  53. data/app/services/spree_cm_commissioner/seeds/user_usernames.rb +86 -0
  54. data/app/services/spree_cm_commissioner/users/qr_data/extract_login.rb +18 -0
  55. data/app/services/spree_cm_commissioner/users/qr_data/generate.rb +14 -0
  56. data/app/services/spree_cm_commissioner/users/qr_data/invalidate.rb +19 -0
  57. data/app/services/spree_cm_commissioner/users/qr_data/verify.rb +33 -0
  58. data/app/services/spree_cm_commissioner/users/username/generate.rb +68 -0
  59. data/app/views/spree/transit/trip_stops/index.html.erb +2 -4
  60. data/app/views/spree_cm_commissioner/guest_mailer/send_ticket_to_guest.html.erb +1 -0
  61. data/config/initializers/spree_permitted_attributes.rb +1 -11
  62. data/config/routes.rb +2 -6
  63. data/db/migrate/20260126110528_seed_user_initial_usernames.rb +5 -0
  64. data/lib/spree_cm_commissioner/test_helper/factories/route_factory.rb +6 -7
  65. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +6 -0
  66. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +1 -4
  67. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +1 -3
  68. data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +1 -1
  69. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -2
  70. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +0 -22
  71. data/lib/spree_cm_commissioner/version.rb +1 -1
  72. data/lib/spree_cm_commissioner.rb +0 -4
  73. metadata +29 -38
  74. data/app/controllers/spree/api/v2/tenant/popular_route_places_controller.rb +0 -60
  75. data/app/controllers/spree/api/v2/tenant/routes_controller.rb +0 -50
  76. data/app/controllers/spree/api/v2/tenant/transit/draft_orders_controller.rb +0 -46
  77. data/app/finders/spree_cm_commissioner/route_metrics/find_popular.rb +0 -44
  78. data/app/finders/spree_cm_commissioner/routes/find.rb +0 -94
  79. data/app/jobs/spree_cm_commissioner/route_metrics/decrease_trip_count_job.rb +0 -10
  80. data/app/jobs/spree_cm_commissioner/route_metrics/increase_fulfilled_order_count_job.rb +0 -10
  81. data/app/jobs/spree_cm_commissioner/route_metrics/increase_order_count_job.rb +0 -10
  82. data/app/jobs/spree_cm_commissioner/route_metrics/increase_trip_count_job.rb +0 -10
  83. data/app/models/spree_cm_commissioner/route_metric.rb +0 -21
  84. data/app/models/spree_cm_commissioner/route_photo.rb +0 -12
  85. data/app/serializers/spree/v2/tenant/transit_cart_serializer.rb +0 -11
  86. data/app/serializers/spree_cm_commissioner/v2/storefront/transit_line_item_serializer.rb +0 -17
  87. data/app/services/spree_cm_commissioner/route_metrics/decrease_trip_count.rb +0 -31
  88. data/app/services/spree_cm_commissioner/route_metrics/increase_trip_count.rb +0 -31
  89. data/app/services/spree_cm_commissioner/routes/create.rb +0 -51
  90. data/app/services/spree_cm_commissioner/routes/update.rb +0 -25
  91. data/app/services/spree_cm_commissioner/trips/create_single_leg.rb +0 -123
  92. data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +0 -54
  93. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +0 -88
  94. data/app/services/spree_cm_commissioner/trips/variants/create.rb +0 -103
  95. data/db/migrate/20251224033103_migrate_cm_routes_to_cm_route_metrics.rb +0 -17
  96. data/db/migrate/20251224033910_migrate_cm_vendor_routes_to_cm_routes.rb +0 -30
  97. data/db/migrate/20260105072450_migrate_cm_trip_stops_to_support_trip_connection.rb +0 -12
  98. data/db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb +0 -5
  99. data/lib/spree_cm_commissioner/test_helper/factories/route_metric_factory.rb +0 -12
  100. data/lib/spree_cm_commissioner/test_helper/factories/route_photo_factory.rb +0 -5
  101. data/lib/spree_cm_commissioner/transit/route_stop.rb +0 -61
  102. data/lib/spree_cm_commissioner/transit/route_stop_collection.rb +0 -175
  103. data/lib/spree_cm_commissioner/transit/trip_form.rb +0 -81
  104. data/lib/spree_cm_commissioner/transit/trip_stop_form.rb +0 -61
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
- module RouteMetrics
3
- class UpdateRouteMetrics
2
+ module Routes
3
+ class BaseUpdateOrderMetrics
4
4
  attr_reader :order, :attribute
5
5
 
6
6
  SUPPORTED_ATTRIBUTES = %i[order_count fulfilled_order_count].freeze
@@ -31,21 +31,26 @@ module SpreeCmCommissioner
31
31
  trip = product&.trip
32
32
  return unless trip
33
33
 
34
- route_metric = find_or_create_route_metric_for_trip(trip)
35
- increment_route_by(route_metric, line_item.quantity)
36
-
37
- route = trip.route
38
- increment_route_by(route, line_item.quantity) if route
34
+ route = find_or_create_route_for_trip(trip)
35
+ ensure_trip_route(trip, route)
36
+ increment_route_by(route, line_item.quantity)
39
37
  end
40
38
 
41
- def find_or_create_route_metric_for_trip(trip)
42
- SpreeCmCommissioner::RouteMetric.find_or_create_by(
39
+ def find_or_create_route_for_trip(trip)
40
+ SpreeCmCommissioner::Route.find_or_create_by(
43
41
  origin_place_id: trip.origin_place_id,
44
- destination_place_id: trip.destination_place_id,
45
- route_type: trip.route_type
42
+ destination_place_id: trip.destination_place_id
46
43
  )
47
44
  end
48
45
 
46
+ def ensure_trip_route(trip, route)
47
+ trip_route_id = trip.respond_to?(:route_id) ? trip.route_id : nil
48
+
49
+ return unless trip.respond_to?(:update_column) && trip_route_id != route.id
50
+
51
+ trip.update_column(:route_id, route.id) # rubocop:disable Rails/SkipsModelValidations
52
+ end
53
+
49
54
  def increment_route_by(route, qty)
50
55
  route.with_lock do
51
56
  current_value = route.public_send(attribute) || 0
@@ -0,0 +1,30 @@
1
+ module SpreeCmCommissioner
2
+ module Routes
3
+ class DecrementPreviousTripCount
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(previous_route_id:)
7
+ previous_route = SpreeCmCommissioner::Route.find_by(id: previous_route_id)
8
+ return failure(nil, 'Route not found') unless previous_route
9
+
10
+ if previous_route.vendors.exists?
11
+
12
+ # Build a trip-like object that responds to :route and :vendor so the delegated
13
+ # service can locate vendor routes correctly.
14
+ trip_like = Struct.new(:route, :vendor).new(previous_route, previous_route.vendors.first)
15
+ result = SpreeCmCommissioner::Routes::DecrementTripCount.call(trip: trip_like)
16
+ return result if result.failure?
17
+ else
18
+ previous_route.with_lock do
19
+ new_count = [previous_route.trip_count.to_i - 1, 0].max
20
+ previous_route.update!(trip_count: new_count)
21
+ end
22
+ end
23
+
24
+ success(previous_route: previous_route)
25
+ rescue StandardError => e
26
+ failure(nil, e.message)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module SpreeCmCommissioner
2
+ module Routes
3
+ class DecrementTripCount
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(trip:)
7
+ return failure(nil, 'Trip not found') unless trip
8
+
9
+ vendor = trip.vendor
10
+ return failure(nil, 'Vendor not found') unless vendor
11
+
12
+ ActiveRecord::Base.transaction do
13
+ route = trip.route
14
+
15
+ route.update!(trip_count: [route.trip_count - 1, 0].max)
16
+
17
+ vendor_route = locate_vendor_route(vendor: vendor, route: route)
18
+ vendor_route&.update!(trip_count: [vendor_route.trip_count - 1, 0].max)
19
+ end
20
+
21
+ success(trip: trip)
22
+ rescue StandardError => e
23
+ failure(nil, e.message)
24
+ end
25
+
26
+ private
27
+
28
+ def locate_vendor_route(vendor:, route:)
29
+ vendor.vendor_routes.find_by(route_id: route.id)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,12 +1,12 @@
1
1
  module SpreeCmCommissioner
2
- module RouteMetrics
3
- class IncreaseFulfilledOrderCount
2
+ module Routes
3
+ class IncrementFulfilledOrderCount
4
4
  prepend ::Spree::ServiceModule::Base
5
5
 
6
6
  def call(order:)
7
7
  return failure(nil, 'Order not found') unless order
8
8
 
9
- SpreeCmCommissioner::RouteMetrics::UpdateRouteMetrics
9
+ SpreeCmCommissioner::Routes::BaseUpdateOrderMetrics
10
10
  .call(order: order, attribute: :fulfilled_order_count)
11
11
 
12
12
  success(order: order)
@@ -1,12 +1,12 @@
1
1
  module SpreeCmCommissioner
2
- module RouteMetrics
3
- class IncreaseOrderCount
2
+ module Routes
3
+ class IncrementOrderCount
4
4
  prepend ::Spree::ServiceModule::Base
5
5
 
6
6
  def call(order:)
7
7
  return failure(nil, 'Order not found') unless order
8
8
 
9
- SpreeCmCommissioner::RouteMetrics::UpdateRouteMetrics
9
+ SpreeCmCommissioner::Routes::BaseUpdateOrderMetrics
10
10
  .new(order: order, attribute: :order_count)
11
11
  .call
12
12
 
@@ -0,0 +1,33 @@
1
+ module SpreeCmCommissioner
2
+ module Routes
3
+ class IncrementTripCount
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(trip:)
7
+ return failure(nil, 'Trip not found') unless trip
8
+
9
+ vendor = trip.vendor
10
+ return failure(nil, 'Vendor not found') unless vendor
11
+
12
+ ActiveRecord::Base.transaction do
13
+ route = trip.route
14
+
15
+ route.update!(trip_count: route.trip_count + 1)
16
+
17
+ vendor_route = find_or_create_vendor_route(vendor: vendor, route: route)
18
+ vendor_route.update!(trip_count: vendor_route.trip_count + 1)
19
+ end
20
+
21
+ success(trip: trip)
22
+ rescue StandardError => e
23
+ failure(nil, e.message)
24
+ end
25
+
26
+ private
27
+
28
+ def find_or_create_vendor_route(vendor:, route:)
29
+ vendor.vendor_routes.find_or_create_by!(route_id: route.id)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,86 @@
1
+ module SpreeCmCommissioner
2
+ module Seeds
3
+ class UserUsernames
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call(batch_size: 1000)
7
+ users = Spree::User.all
8
+ total_count = users.count
9
+ updated_count = 0
10
+ failed_count = 0
11
+ current_index = 0
12
+
13
+ Rails.logger.debug { "\n🚀 Starting login update for #{total_count} users (batch size: #{batch_size})..." }
14
+ start_time = Time.current
15
+
16
+ users.find_each(batch_size: batch_size) do |user|
17
+ current_index += 1
18
+
19
+ begin
20
+ new_login = SpreeCmCommissioner::Users::Username::Generate.call(user: user)
21
+
22
+ if user.update_column(:login, new_login) # rubocop:disable Rails/SkipsModelValidations
23
+ updated_count += 1
24
+
25
+ # Log progress every 100 users to avoid log spam
26
+ if (current_index % 100).zero? || current_index == total_count
27
+ elapsed = (Time.current - start_time).round(2)
28
+ rate = (current_index / elapsed).round(2)
29
+
30
+ Rails.logger.debug { "[#{current_index}/#{total_count}] ✓ Updated #{updated_count}, Failed #{failed_count} (#{rate} users/sec)" }
31
+ end
32
+ else
33
+ failed_count += 1
34
+ Rails.logger.warn { "[#{current_index}/#{total_count}] ✗ User ##{user.id}: Failed to update" }
35
+ end
36
+ rescue StandardError => e
37
+ failed_count += 1
38
+ Rails.logger.error { "[#{current_index}/#{total_count}] ✗ User ##{user.id}: #{e.message}" }
39
+ end
40
+ end
41
+
42
+ elapsed = (Time.current - start_time).round(2)
43
+ Rails.logger.debug { "\n✅ Completed in #{elapsed}s! Updated #{updated_count}/#{total_count} users (Failed: #{failed_count})" }
44
+ end
45
+
46
+ private
47
+
48
+ # Generate username with collision safety
49
+ # Format: NamePrefixNumber (TheaTraveler1) or PrefixNumber (Traveler1)
50
+ def generate_unique_username!(user, max_attempts: 10)
51
+ max_attempts.times do
52
+ username = generate_clean_username(user)
53
+ return username unless Spree::User.exists?(login: username)
54
+ end
55
+
56
+ # Last resort: use extended number range
57
+ generate_clean_username(user, number_range: 10_000..99_999)
58
+ end
59
+
60
+ # Generate clean username: NamePrefixNumber or PrefixNumber
61
+ # Examples: TheaTraveler1, Traveler1
62
+ def generate_clean_username(user, number_range: 1..9999)
63
+ name = format_name(user.first_name)
64
+ prefix = SAFE_USERNAME_PREFIXES.sample
65
+ number = rand(number_range)
66
+
67
+ name.present? ? "#{name}#{prefix}#{number}" : "#{prefix}#{number}"
68
+ end
69
+
70
+ # Format first name for username: title case, English only, max 8 chars
71
+ # Returns nil if name is empty or contains non-English characters
72
+ def format_name(first_name)
73
+ return nil if first_name.nil? || first_name.strip.empty?
74
+
75
+ # Remove special chars and whitespace
76
+ cleaned = first_name.strip.gsub(/[^a-zA-Z]/, '')
77
+
78
+ # Return nil if empty after cleaning or contains non-Latin characters
79
+ return nil if cleaned.empty? || first_name.match?(/[^\x00-\x7F]/)
80
+
81
+ # Title case and truncate to 8 chars
82
+ cleaned.capitalize.slice(0, 8)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,18 @@
1
+ module SpreeCmCommissioner
2
+ module Users::QrData
3
+ class ExtractLogin
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call(qr_data:)
7
+ decoded_token = JWT.decode(qr_data, nil, false)
8
+ payload = decoded_token[0]
9
+
10
+ return success(payload['login']) if payload.key?('login')
11
+
12
+ failure(:user_login_not_found, 'User login not found in QR code payload')
13
+ rescue JWT::DecodeError
14
+ failure(:invalid_qr_data, 'Provided QR code is invalid')
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module SpreeCmCommissioner
2
+ module Users::QrData
3
+ class Generate
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call(user:)
7
+ # Keep key as small as possible to reduce QR code size
8
+ payload = { login: user.login, ver: user.qr_data_version, exp: 30.days.from_now.to_i }
9
+ qr_data = JWT.encode(payload, user.secure_token, 'HS256')
10
+ success(qr_data)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module SpreeCmCommissioner
2
+ module Users::QrData
3
+ class Invalidate
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call(user:)
7
+ user.qr_data_version ||= 0
8
+ user.qr_data_version += 1
9
+ user.qr_data_invalidated_at = Time.zone.now
10
+
11
+ if user.save
12
+ success(user)
13
+ else
14
+ failure(:user_save_failed, user.errors.full_messages.to_sentence)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ module SpreeCmCommissioner
2
+ module Users::QrData
3
+ class Verify
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call(qr_data:)
7
+ user = find_and_verify_by!(qr_data: qr_data)
8
+ success(user) if user.present?
9
+ rescue JWT::ExpiredSignature
10
+ failure(:expired_qr_data, 'Provided QR code has expired')
11
+ rescue JWT::DecodeError, JWT::VerificationError
12
+ failure(:invalid_qr_data, 'Provided QR code is invalid')
13
+ end
14
+
15
+ def find_and_verify_by!(qr_data:)
16
+ decoded_token = JWT.decode(qr_data, nil, false)
17
+ payload = decoded_token[0]
18
+ login = payload['login']
19
+
20
+ # we don't reveal whether user exists or not in error messages for security reasons.
21
+ user = Spree::User.find_by(login: login)
22
+ raise JWT::DecodeError, 'QR code is invalid' if user.nil?
23
+
24
+ JWT.decode(qr_data, user.secure_token, true, { algorithm: 'HS256' })
25
+
26
+ # Validate version to ensure QR code hasn't been invalidated
27
+ raise JWT::VerificationError, 'QR code has been invalidated' if payload['ver'] != user.qr_data_version
28
+
29
+ user
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,68 @@
1
+ require 'securerandom'
2
+
3
+ module SpreeCmCommissioner
4
+ module Users
5
+ module Username
6
+ class Generate
7
+ # Travel/Social Username Prefixes - See https://github.com/channainfo/commissioner/pull/3389
8
+ # Format: NamePrefixNumber (e.g., TheaTraveler1) or PrefixNumber (e.g., Traveler1)
9
+ SAFE_USERNAME_PREFIXES = %w[
10
+ Traveler Rider Explorer Voyager Passenger
11
+ Journey Buddy Mate Friend Crew
12
+ ].freeze
13
+
14
+ # Generate unique username for a user with collision safety
15
+ # @param user [Spree::User] User object to generate username for
16
+ # @param max_attempts [Integer] Maximum retry attempts before fallback
17
+ # @return [String] Generated unique username
18
+ def self.call(user:, max_attempts: 10)
19
+ new(user, max_attempts: max_attempts).call
20
+ end
21
+
22
+ def initialize(user, max_attempts: 10)
23
+ @user = user
24
+ @max_attempts = max_attempts
25
+ end
26
+
27
+ def call
28
+ @max_attempts.times do
29
+ username = generate_clean_username
30
+ return username unless Spree::User.exists?(login: username)
31
+ end
32
+
33
+ # Last resort: use extended number range
34
+ generate_clean_username(number_range: 10_000..99_999)
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :user
40
+
41
+ # Generate clean username: NamePrefixNumber or PrefixNumber
42
+ # Examples: TheaTraveler1, Traveler1
43
+ def generate_clean_username(number_range: 1..9999)
44
+ name = format_name(user.first_name)
45
+ prefix = SAFE_USERNAME_PREFIXES.sample
46
+ number = rand(number_range)
47
+
48
+ name.present? ? "#{name}#{prefix}#{number}" : "#{prefix}#{number}"
49
+ end
50
+
51
+ # Format first name for username: title case, English only, max 8 chars
52
+ # Returns nil if name is empty or contains non-English characters
53
+ def format_name(first_name)
54
+ return nil if first_name.nil? || first_name.strip.empty?
55
+
56
+ # Remove special chars and whitespace
57
+ cleaned = first_name.strip.gsub(/[^a-zA-Z]/, '')
58
+
59
+ # Return nil if empty after cleaning or contains non-Latin characters
60
+ return nil if cleaned.empty? || first_name.match?(/[^\x00-\x7F]/)
61
+
62
+ # Title case and truncate to 8 chars
63
+ cleaned.capitalize.slice(0, 8)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -9,8 +9,7 @@
9
9
  <tr data-hook="option_header">
10
10
  <th class="no-border handel-head"></th>
11
11
  <th><%= Spree.t(:name) %></th>
12
- <th><%= Spree.t(:allow_boarding) %></th>
13
- <th><%= Spree.t(:allow_drop_off) %></th>
12
+ <th><%= Spree.t(:stop_type) %></th>
14
13
  <th><%= Spree.t(:sequence) %></th>
15
14
  <th><%= Spree.t(:created_at) %></th>
16
15
  </tr>
@@ -22,8 +21,7 @@
22
21
  <%= svg_icon name: "grip-vertical.svg", width: '18', height: '18' %>
23
22
  </td>
24
23
  <td><%= stop.stop_name %></td>
25
- <td> <%= stop.allow_boarding ? 'Yes' : 'No' %></td>
26
- <td> <%= stop.allow_drop_off ? 'Yes' : 'No' %></td>
24
+ <td> <%= stop.stop_type %></td>
27
25
  <td> <%= stop.sequence %></td>
28
26
  <td> <%= stop.created_at.to_date %></td>
29
27
  </tr>
@@ -151,6 +151,7 @@
151
151
  <% @trip.trip_stops.order(:sequence).each do |stop| %>
152
152
  <tr>
153
153
  <td>
154
+ <span class="stop-type <%= stop.stop_type %>"><%= stop.stop_type.to_s.titleize %></span>
154
155
  <div class="stop-name"><strong><%= stop.stop_name %></strong></div>
155
156
  <% if stop.respond_to?(:location_place) && stop.location_place.present? %>
156
157
  <% if stop.location_place.respond_to?(:address) && stop.location_place.address.present? %>
@@ -1,8 +1,5 @@
1
1
  module Spree
2
2
  module PermittedAttributes
3
- ATTRIBUTES << :route_attributes unless ATTRIBUTES.include?(:route_attributes)
4
- mattr_reader :route_attributes
5
-
6
3
  @@vendor_attributes << :logo
7
4
 
8
5
  # Permitted all guest attributes for now as permitting only some guest attributes is not working by design
@@ -15,6 +12,7 @@ module Spree
15
12
  ]
16
13
 
17
14
  @@user_attributes += %i[
15
+ login
18
16
  first_name
19
17
  last_name
20
18
  dob gender
@@ -88,13 +86,5 @@ module Spree
88
86
  originator_type
89
87
  originator_id
90
88
  ]
91
-
92
- @@route_attributes = %i[
93
- route_name
94
- short_name
95
- vendor_id
96
- route_type
97
- route_stops
98
- ]
99
89
  end
100
90
  end
data/config/routes.rb CHANGED
@@ -560,12 +560,6 @@ Spree::Core::Engine.add_routes do
560
560
  resources :trip_places, only: :index
561
561
  resources :trip_search, only: [:index]
562
562
  resources :trips, only: %i[show]
563
- resources :popular_route_places, only: [:index]
564
- resources :routes, only: [:index]
565
-
566
- namespace :transit do
567
- resources :draft_orders, only: %i[create]
568
- end
569
563
 
570
564
  namespace :intercity_taxi do
571
565
  resource :draft_orders, only: %i[create update]
@@ -634,6 +628,8 @@ Spree::Core::Engine.add_routes do
634
628
 
635
629
  namespace :account do
636
630
  resource :preferred_payment_method, controller: :preferred_payment_method, only: %i[show update]
631
+ resource :qr_data, controller: :qr_data, only: %i[update]
632
+
637
633
  patch :mark_guest_info_complete, to: 'mark_guest_info_complete#update'
638
634
  resources :guests, only: [] do
639
635
  patch 'dynamic_fields/:phase', to: 'guest_dynamic_fields#update_dynamic_field', as: :update_dynamic_fields
@@ -0,0 +1,5 @@
1
+ class SeedUserInitialUsernames < ActiveRecord::Migration[7.0]
2
+ def change
3
+ SpreeCmCommissioner::Seeds::UserUsernames.call
4
+ end
5
+ end
@@ -1,11 +1,10 @@
1
1
  FactoryBot.define do
2
- factory :cm_route, class: SpreeCmCommissioner::Route do
3
- association :vendor, factory: :vendor
4
- association :tenant, factory: :cm_tenant
5
- association :origin_place, factory: :cm_place
6
- association :destination_place, factory: :cm_place
7
-
2
+ factory :cm_route, class: 'SpreeCmCommissioner::Route' do
8
3
  sequence(:route_name) { |n| "Route #{n}" }
9
- sequence(:short_name) { |n| "R#{n}" }
4
+ origin_place_id { create(:cm_place).id }
5
+ destination_place_id { create(:cm_place).id }
6
+ trip_count { 0 }
7
+ order_count { 0 }
8
+ fulfilled_order_count { 0 }
10
9
  end
11
10
  end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :cm_trip_connection, class: SpreeCmCommissioner::TripConnection do
3
+ from_trip_id { create(:cm_trip) }
4
+ to_trip_id { create(:cm_trip) }
5
+ end
6
+ end
@@ -11,10 +11,7 @@ FactoryBot.define do
11
11
  association :vehicle, factory: :cm_vehicle
12
12
  association :origin_place, factory: :cm_place
13
13
  association :destination_place, factory: :cm_place
14
-
15
- # Don't create route by default to avoid uniqueness conflicts
16
- # Tests that need these should create them explicitly
17
- route { nil }
14
+ association :route, factory: :cm_route
18
15
  end
19
16
 
20
17
  factory :cm_trip_with_seat_counts, parent: :cm_trip do
@@ -4,9 +4,7 @@ FactoryBot.define do
4
4
  association :stop_place, factory: :cm_place
5
5
  association :location_place, factory: :cm_place
6
6
 
7
- allow_boarding { true }
8
- allow_drop_off { true }
9
-
7
+ stop_type { :boarding }
10
8
  stop_name { stop_place.name }
11
9
 
12
10
  arrival_time { Time.current }
@@ -1,6 +1,6 @@
1
1
  FactoryBot.define do
2
2
  factory :cm_vehicle_type, class: SpreeCmCommissioner::VehicleType do
3
- sequence(:name) { |n| "vehicle_type_#{n}" }
3
+ name { "vehicle_type_#{SecureRandom.hex(4)}" }
4
4
  kind { :air_bus }
5
5
  association :vendor, factory: :vendor
6
6
  end
@@ -1,13 +1,12 @@
1
1
  FactoryBot.define do
2
2
  factory :cm_transit_vendor, parent: :vendor do
3
- from_email { FFaker::Internet.email } # required only when tenant exist.
3
+ sequence(:name) { |n| "#{FFaker::Company.name} #{n}#{Kernel.rand(9999)}" }
4
4
  state { :active }
5
5
  primary_product_type { :transit }
6
6
  end
7
7
 
8
8
  factory :cm_vendor, parent: :vendor do
9
9
  sequence(:name) { |n| "#{FFaker::Company.name} #{n}#{Kernel.rand(9999)}" }
10
- from_email { FFaker::Internet.email } # required only when tenant exist.
11
10
  state { :active }
12
11
  default_state_id { Spree::State.first&.id }
13
12
  primary_product_type { :ecommerce }
@@ -7,17 +7,6 @@ FactoryBot.define do
7
7
  position { FFaker::Number.number }
8
8
 
9
9
  place_type { :location }
10
-
11
- transient do
12
- place_name { nil }
13
- end
14
-
15
- before :create do |vendor_place, evaluator|
16
- if evaluator.place_name.present?
17
- vendor_place.place.name = evaluator.place_name
18
- vendor_place.place.save!
19
- end
20
- end
21
10
  end
22
11
 
23
12
  factory :cm_vendor_place, class: SpreeCmCommissioner::VendorPlace do
@@ -41,16 +30,5 @@ FactoryBot.define do
41
30
  place_type { :location }
42
31
  location { nil }
43
32
  end
44
-
45
- transient do
46
- place_name { nil }
47
- end
48
-
49
- before :create do |vendor_place, evaluator|
50
- if evaluator.place_name.present?
51
- vendor_place.place.name = evaluator.place_name
52
- vendor_place.place.save!
53
- end
54
- end
55
33
  end
56
34
  end
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.5.1.pre.pre2'.freeze
2
+ VERSION = '2.5.1.pre.pre3'.freeze
3
3
 
4
4
  module_function
5
5
 
@@ -23,10 +23,6 @@ require 'spree_cm_commissioner/trip_query_result'
23
23
  require 'spree_cm_commissioner/cached_inventory_item'
24
24
  require 'spree_cm_commissioner/transit/leg'
25
25
  require 'spree_cm_commissioner/transit/seat_selection'
26
- require 'spree_cm_commissioner/transit/route_stop'
27
- require 'spree_cm_commissioner/transit/route_stop_collection'
28
- require 'spree_cm_commissioner/transit/trip_form'
29
- require 'spree_cm_commissioner/transit/trip_stop_form'
30
26
  require 'spree_cm_commissioner/intercity_taxi/map_place'
31
27
  require 'spree_cm_commissioner/distance'
32
28