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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/api/chatrace/guests_controller.rb +37 -20
- data/app/controllers/spree/api/v2/operator/check_in_bulks_controller.rb +9 -0
- data/app/controllers/spree/api/v2/storefront/account/qr_data_controller.rb +30 -0
- data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +1 -1
- data/app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb +4 -4
- data/app/controllers/spree/transit/trips_controller.rb +3 -3
- data/app/finders/spree_cm_commissioner/places/find_with_route.rb +12 -12
- data/app/finders/spree_cm_commissioner/routes/find_popular.rb +35 -20
- data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +4 -11
- data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +1 -10
- data/app/{services/spree_cm_commissioner/transit_order/create.rb → interactors/spree_cm_commissioner/transit/draft_order_creator.rb} +16 -13
- data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +3 -4
- data/app/interactors/spree_cm_commissioner/trip_stops_creator.rb +2 -2
- data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +10 -0
- data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +10 -0
- data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +13 -0
- data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +10 -0
- data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +10 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +16 -11
- data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +2 -2
- data/app/models/concerns/spree_cm_commissioner/route_trip_count_callbacks.rb +48 -0
- data/app/models/concerns/spree_cm_commissioner/user_identity.rb +0 -6
- data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +4 -0
- data/app/models/spree_cm_commissioner/guest.rb +4 -4
- data/app/models/spree_cm_commissioner/place.rb +8 -5
- data/app/models/spree_cm_commissioner/product_decorator.rb +0 -1
- data/app/models/spree_cm_commissioner/route.rb +5 -46
- data/app/models/spree_cm_commissioner/trip.rb +33 -8
- data/app/models/spree_cm_commissioner/trip_connection.rb +36 -0
- data/app/models/spree_cm_commissioner/trip_stop.rb +2 -16
- data/app/models/spree_cm_commissioner/user_decorator.rb +30 -18
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +1 -3
- data/app/models/spree_cm_commissioner/vendor_route.rb +9 -0
- data/app/queries/spree_cm_commissioner/guest_searcher_query.rb +25 -2
- data/app/queries/spree_cm_commissioner/trip_query.rb +2 -2
- data/app/serializers/spree/v2/storefront/user_serializer_decorator.rb +2 -1
- data/app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb +1 -0
- data/app/serializers/spree_cm_commissioner/v2/operator/line_item_serializer.rb +2 -2
- data/app/serializers/spree_cm_commissioner/v2/operator/{line_item_order_serializer.rb → order_serializer.rb} +1 -2
- data/app/serializers/spree_cm_commissioner/v2/operator/user_serializer.rb +11 -0
- data/app/serializers/spree_cm_commissioner/v2/operator/variant_serializer.rb +9 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/route_serializer.rb +1 -3
- data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +1 -1
- data/app/services/spree_cm_commissioner/{route_metrics/update_route_metrics.rb → routes/base_update_order_metrics.rb} +16 -11
- data/app/services/spree_cm_commissioner/routes/decrement_previous_trip_count.rb +30 -0
- data/app/services/spree_cm_commissioner/routes/decrement_trip_count.rb +33 -0
- data/app/services/spree_cm_commissioner/{route_metrics/increase_fulfilled_order_count.rb → routes/increment_fulfilled_order_count.rb} +3 -3
- data/app/services/spree_cm_commissioner/{route_metrics/increase_order_count.rb → routes/increment_order_count.rb} +3 -3
- data/app/services/spree_cm_commissioner/routes/increment_trip_count.rb +33 -0
- data/app/services/spree_cm_commissioner/seeds/user_usernames.rb +86 -0
- data/app/services/spree_cm_commissioner/users/qr_data/extract_login.rb +18 -0
- data/app/services/spree_cm_commissioner/users/qr_data/generate.rb +14 -0
- data/app/services/spree_cm_commissioner/users/qr_data/invalidate.rb +19 -0
- data/app/services/spree_cm_commissioner/users/qr_data/verify.rb +33 -0
- data/app/services/spree_cm_commissioner/users/username/generate.rb +68 -0
- data/app/views/spree/transit/trip_stops/index.html.erb +2 -4
- data/app/views/spree_cm_commissioner/guest_mailer/send_ticket_to_guest.html.erb +1 -0
- data/config/initializers/spree_permitted_attributes.rb +1 -11
- data/config/routes.rb +2 -6
- data/db/migrate/20260126110528_seed_user_initial_usernames.rb +5 -0
- data/lib/spree_cm_commissioner/test_helper/factories/route_factory.rb +6 -7
- data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +6 -0
- data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +1 -4
- data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +1 -3
- data/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb +1 -1
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +1 -2
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +0 -22
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +0 -4
- metadata +29 -38
- data/app/controllers/spree/api/v2/tenant/popular_route_places_controller.rb +0 -60
- data/app/controllers/spree/api/v2/tenant/routes_controller.rb +0 -50
- data/app/controllers/spree/api/v2/tenant/transit/draft_orders_controller.rb +0 -46
- data/app/finders/spree_cm_commissioner/route_metrics/find_popular.rb +0 -44
- data/app/finders/spree_cm_commissioner/routes/find.rb +0 -94
- data/app/jobs/spree_cm_commissioner/route_metrics/decrease_trip_count_job.rb +0 -10
- data/app/jobs/spree_cm_commissioner/route_metrics/increase_fulfilled_order_count_job.rb +0 -10
- data/app/jobs/spree_cm_commissioner/route_metrics/increase_order_count_job.rb +0 -10
- data/app/jobs/spree_cm_commissioner/route_metrics/increase_trip_count_job.rb +0 -10
- data/app/models/spree_cm_commissioner/route_metric.rb +0 -21
- data/app/models/spree_cm_commissioner/route_photo.rb +0 -12
- data/app/serializers/spree/v2/tenant/transit_cart_serializer.rb +0 -11
- data/app/serializers/spree_cm_commissioner/v2/storefront/transit_line_item_serializer.rb +0 -17
- data/app/services/spree_cm_commissioner/route_metrics/decrease_trip_count.rb +0 -31
- data/app/services/spree_cm_commissioner/route_metrics/increase_trip_count.rb +0 -31
- data/app/services/spree_cm_commissioner/routes/create.rb +0 -51
- data/app/services/spree_cm_commissioner/routes/update.rb +0 -25
- data/app/services/spree_cm_commissioner/trips/create_single_leg.rb +0 -123
- data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +0 -54
- data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +0 -88
- data/app/services/spree_cm_commissioner/trips/variants/create.rb +0 -103
- data/db/migrate/20251224033103_migrate_cm_routes_to_cm_route_metrics.rb +0 -17
- data/db/migrate/20251224033910_migrate_cm_vendor_routes_to_cm_routes.rb +0 -30
- data/db/migrate/20260105072450_migrate_cm_trip_stops_to_support_trip_connection.rb +0 -12
- data/db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb +0 -5
- data/lib/spree_cm_commissioner/test_helper/factories/route_metric_factory.rb +0 -12
- data/lib/spree_cm_commissioner/test_helper/factories/route_photo_factory.rb +0 -5
- data/lib/spree_cm_commissioner/transit/route_stop.rb +0 -61
- data/lib/spree_cm_commissioner/transit/route_stop_collection.rb +0 -175
- data/lib/spree_cm_commissioner/transit/trip_form.rb +0 -81
- data/lib/spree_cm_commissioner/transit/trip_stop_form.rb +0 -61
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
|
-
module
|
|
3
|
-
class
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
42
|
-
SpreeCmCommissioner::
|
|
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
|
|
3
|
-
class
|
|
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::
|
|
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
|
|
3
|
-
class
|
|
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::
|
|
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(:
|
|
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.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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,13 +1,12 @@
|
|
|
1
1
|
FactoryBot.define do
|
|
2
2
|
factory :cm_transit_vendor, parent: :vendor do
|
|
3
|
-
|
|
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
|
|
@@ -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
|
|