spree_cm_commissioner 2.3.0.pre.pre20 → 2.3.1.pre.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/admin/product_completion_steps_controller.rb +17 -1
- data/app/controllers/spree/admin/tenants_controller.rb +1 -1
- data/app/controllers/spree/api/v2/storefront/intercity_taxi/draft_orders_controller.rb +0 -1
- data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +3 -0
- data/app/controllers/spree/api/v2/tenant/intercity_taxi/draft_orders_controller.rb +41 -0
- data/app/controllers/spree/api/v2/tenant/trip_places_controller.rb +48 -0
- data/app/controllers/spree/api/v2/tenant/trip_search_controller.rb +103 -0
- data/app/controllers/spree/api/v2/tenant/trips_controller.rb +32 -0
- data/app/interactors/spree_cm_commissioner/create_ticket.rb +2 -0
- data/app/interactors/spree_cm_commissioner/google_routes_distance_calculator.rb +211 -0
- data/app/interactors/spree_cm_commissioner/host_matcher.rb +1 -1
- data/app/interactors/spree_cm_commissioner/intercity_taxi_order_creator.rb +52 -12
- data/app/interactors/spree_cm_commissioner/trip_distance_calculator.rb +165 -0
- data/app/interactors/spree_cm_commissioner/vendor_creation_telegram_alert_sender.rb +1 -1
- data/app/mailers/spree/order_mailer_decorator.rb +8 -9
- data/app/models/concerns/spree_cm_commissioner/order_scopes.rb +4 -5
- data/app/models/concerns/spree_cm_commissioner/service_recommendations.rb +2 -2
- data/app/models/concerns/spree_cm_commissioner/store_preference.rb +1 -0
- data/app/models/spree_cm_commissioner/product_completion_step.rb +4 -1
- data/app/models/spree_cm_commissioner/product_completion_step_banner.rb +12 -0
- data/app/models/spree_cm_commissioner/product_completion_steps/social_entry_url.rb +86 -19
- data/app/models/spree_cm_commissioner/tenant.rb +10 -1
- data/app/models/spree_cm_commissioner/vendor_place.rb +10 -1
- data/app/overrides/spree/admin/stores/_form/store_preferences.html.erb.deface +9 -0
- data/app/queries/spree_cm_commissioner/trip_query.rb +51 -33
- data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vehicle_serializer.rb +1 -1
- data/app/services/spree_cm_commissioner/intercity_taxi_order/create.rb +8 -0
- data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +35 -45
- data/app/services/spree_cm_commissioner/orders/bulk_archive_inactive_orders.rb +10 -2
- data/app/services/spree_cm_commissioner/orders/daily_archive_inactive_orders.rb +8 -0
- data/app/services/spree_cm_commissioner/trips/search.rb +7 -11
- data/app/views/shared/_asset_field.html.erb +13 -0
- data/app/views/spree/admin/product_completion_steps/_form.html.erb +28 -2
- data/app/views/spree/admin/product_completion_steps/_supported_fields.html.erb +33 -0
- data/app/views/spree/admin/tenants/_form.html.erb +1 -1
- data/config/initializers/spree_permitted_attributes.rb +1 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/km.yml +65 -61
- data/config/routes.rb +16 -0
- data/db/migrate/20251119093000_add_tenant_id_to_cm_vendor_places.rb +7 -0
- data/lib/spree_cm_commissioner/test_helper/factories/product_completion_step_banner_factory.rb +7 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +12 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7791d1c6474375315937bc020c5bb5274abecbddc5e9b9f5df5a73e53576d626
|
|
4
|
+
data.tar.gz: 03c0f3ccc698d12697c135f4daaab5718b1a72117288de9270069397660d62a5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5cb82c98aac134ebedbdb29c1daf2cc22035ac4e5856c548b22015ffb9a574e6873e897407b8043d899164a9a1ffa941ec04fc25df2263e255e5fd1ad362531c
|
|
7
|
+
data.tar.gz: 4ff0e66a420d2e7f589f2c63d55ca18acc0065aa015033b3917a5dbf603440df4f8a56a32834a02135f5e216a2e5f7d82e8e78f5c6d1df571dc7b261ddb90a79
|
data/Gemfile.lock
CHANGED
|
@@ -4,6 +4,7 @@ module Spree
|
|
|
4
4
|
belongs_to 'spree/product', find_by: :slug
|
|
5
5
|
|
|
6
6
|
before_action :load_step_types, if: :member_action?
|
|
7
|
+
before_action :build_assets, only: %i[create update]
|
|
7
8
|
|
|
8
9
|
def load_step_types
|
|
9
10
|
@step_types = [
|
|
@@ -14,10 +15,13 @@ module Spree
|
|
|
14
15
|
|
|
15
16
|
# override
|
|
16
17
|
def permitted_resource_params
|
|
18
|
+
return @permitted_resource_params if defined?(@permitted_resource_params)
|
|
19
|
+
|
|
17
20
|
key = ActiveModel::Naming.param_key(@object)
|
|
18
21
|
permit_keys = params.require(key).keys
|
|
19
22
|
|
|
20
|
-
params.require(key).permit(permit_keys)
|
|
23
|
+
@permitted_resource_params = params.require(key).permit(permit_keys)
|
|
24
|
+
@permitted_resource_params
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
# @overrided
|
|
@@ -54,6 +58,18 @@ module Spree
|
|
|
54
58
|
def location_after_save
|
|
55
59
|
edit_object_url(@object)
|
|
56
60
|
end
|
|
61
|
+
|
|
62
|
+
def remove_banner
|
|
63
|
+
@object.banner&.destroy
|
|
64
|
+
flash[:success] = Spree.t(:successfully_removed)
|
|
65
|
+
redirect_to edit_object_url(@object)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def build_assets
|
|
71
|
+
@object.build_banner(attachment: permitted_resource_params.delete(:banner)) if permitted_resource_params[:banner].present?
|
|
72
|
+
end
|
|
57
73
|
end
|
|
58
74
|
end
|
|
59
75
|
end
|
|
@@ -12,6 +12,7 @@ module Spree
|
|
|
12
12
|
origin_id: params[:origin_id],
|
|
13
13
|
destination_id: params[:destination_id],
|
|
14
14
|
date: params[:date],
|
|
15
|
+
route_type: params[:route_type],
|
|
15
16
|
vendor_id: params[:vendor_id],
|
|
16
17
|
number_of_guests: params[:number_of_guests],
|
|
17
18
|
params: params
|
|
@@ -36,6 +37,7 @@ module Spree
|
|
|
36
37
|
params[:origin_id],
|
|
37
38
|
params[:destination_id],
|
|
38
39
|
params[:date],
|
|
40
|
+
params[:route_type],
|
|
39
41
|
params[:vendor_id],
|
|
40
42
|
params[:number_of_guests],
|
|
41
43
|
resource_includes&.sort&.join(','),
|
|
@@ -62,6 +64,7 @@ module Spree
|
|
|
62
64
|
'trips.vendor',
|
|
63
65
|
'trips.vendor.logo',
|
|
64
66
|
'trips.vehicle',
|
|
67
|
+
'trips.vehicle.vehicle_photos',
|
|
65
68
|
'trips.amenities'
|
|
66
69
|
]
|
|
67
70
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
module IntercityTaxi
|
|
6
|
+
class DraftOrdersController < BaseController
|
|
7
|
+
def create
|
|
8
|
+
order = SpreeCmCommissioner::IntercityTaxiOrder::Create.call(
|
|
9
|
+
trip_id: params[:trip_id],
|
|
10
|
+
from_date: params[:from_date],
|
|
11
|
+
to_date: params[:to_date],
|
|
12
|
+
user_id: params[:user_id],
|
|
13
|
+
quantity: params[:quantity]
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
render_serialized_payload { serialize_resource(order) }
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
render_error_payload(e.message)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def update
|
|
22
|
+
order = SpreeCmCommissioner::IntercityTaxiOrder::Update.call(
|
|
23
|
+
params: params
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
render_serialized_payload { serialize_resource(order) }
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
render_error_payload(e.message)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def resource_serializer
|
|
34
|
+
SpreeCmCommissioner::V2::Storefront::IntercityTaxiCartSerializer
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
class TripPlacesController < BaseController
|
|
6
|
+
def index
|
|
7
|
+
render_serialized_payload do
|
|
8
|
+
serialize_collection(paginated_collection)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def paginated_collection
|
|
15
|
+
collection.page(params[:page]).per(params[:per_page])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def collection
|
|
19
|
+
@collection ||= begin
|
|
20
|
+
base_scope = scope
|
|
21
|
+
|
|
22
|
+
if params[:q].present?
|
|
23
|
+
@search = base_scope.ransack(params[:q])
|
|
24
|
+
@search.result
|
|
25
|
+
else
|
|
26
|
+
base_scope
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def scope
|
|
32
|
+
model_class.joins(:location_vendor_places)
|
|
33
|
+
.where(location_vendor_places: { tenant_id: @tenant.id })
|
|
34
|
+
.distinct
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def model_class
|
|
38
|
+
SpreeCmCommissioner::Place
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def collection_serializer
|
|
42
|
+
SpreeCmCommissioner::V2::Storefront::TripPlaceSerializer
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
class TripSearchController < BaseController
|
|
6
|
+
CACHE_EXPIRES_IN = 5.minutes
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
render_serialized_payload do
|
|
10
|
+
Rails.cache.fetch(collection_cache_key, collection_cache_opts) do
|
|
11
|
+
trips = SpreeCmCommissioner::TripQuery.new(
|
|
12
|
+
origin_id: params[:origin_id],
|
|
13
|
+
destination_id: params[:destination_id],
|
|
14
|
+
date: params[:date],
|
|
15
|
+
route_type: params[:route_type],
|
|
16
|
+
number_of_guests: params[:number_of_guests],
|
|
17
|
+
tenant_id: @tenant.id,
|
|
18
|
+
params: params
|
|
19
|
+
).call
|
|
20
|
+
|
|
21
|
+
serialize_collection(trips)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
# override from ContentCacheable
|
|
29
|
+
def max_age
|
|
30
|
+
CACHE_EXPIRES_IN.to_i
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# override
|
|
34
|
+
def collection_cache_key
|
|
35
|
+
cache_key_parts = [
|
|
36
|
+
'trip_search',
|
|
37
|
+
@tenant.id,
|
|
38
|
+
params[:origin_id],
|
|
39
|
+
params[:destination_id],
|
|
40
|
+
params[:date],
|
|
41
|
+
params[:route_type],
|
|
42
|
+
params[:number_of_guests],
|
|
43
|
+
resource_includes&.sort&.join(','),
|
|
44
|
+
sparse_fields&.sort&.join(','),
|
|
45
|
+
serializer_params.to_json,
|
|
46
|
+
params[:page]&.to_s&.strip,
|
|
47
|
+
params[:per_page]&.to_s&.strip
|
|
48
|
+
].compact.join('-')
|
|
49
|
+
|
|
50
|
+
Digest::MD5.hexdigest(cache_key_parts)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# override
|
|
54
|
+
def collection_cache_opts
|
|
55
|
+
{
|
|
56
|
+
namespace: Spree::Api::Config[:api_v2_collection_cache_namespace],
|
|
57
|
+
expires_in: CACHE_EXPIRES_IN
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# override
|
|
62
|
+
def default_resource_includes
|
|
63
|
+
[
|
|
64
|
+
'trips.vendor',
|
|
65
|
+
'trips.vendor.logo',
|
|
66
|
+
'trips.vehicle',
|
|
67
|
+
'trips.vehicle.vehicle_photos',
|
|
68
|
+
'trips.amenities'
|
|
69
|
+
]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def serialize_collection(collection)
|
|
73
|
+
serialized_data = SpreeCmCommissioner::V2::Storefront::TripQueryResultSerializer.new(
|
|
74
|
+
collection,
|
|
75
|
+
include: default_resource_includes,
|
|
76
|
+
params: serializer_params
|
|
77
|
+
).serializable_hash
|
|
78
|
+
serialized_data[:meta] = {
|
|
79
|
+
count: collection.size,
|
|
80
|
+
total_count: collection.respond_to?(:total_count) ? collection.total_count : collection.size,
|
|
81
|
+
current_page: collection.respond_to?(:current_page) ? collection.current_page : 1,
|
|
82
|
+
per_page: collection.respond_to?(:limit_value) ? collection.limit_value : collection.size,
|
|
83
|
+
pages: collection.respond_to?(:total_pages) ? collection.total_pages : 1,
|
|
84
|
+
next_page: collection.respond_to?(:next_page) ? collection.next_page : nil,
|
|
85
|
+
prev_page: collection.respond_to?(:prev_page) ? collection.prev_page : nil
|
|
86
|
+
}
|
|
87
|
+
serialized_data
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# override
|
|
91
|
+
def serializer_params
|
|
92
|
+
params.permit(:include).to_hash
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# override
|
|
96
|
+
def required_schema
|
|
97
|
+
SpreeCmCommissioner::TripSearchRequestSchema
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
class TripsController < BaseController
|
|
6
|
+
def show
|
|
7
|
+
resource = scope.find(params[:id])
|
|
8
|
+
|
|
9
|
+
render_serialized_payload do
|
|
10
|
+
serialize_resource(resource)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def scope
|
|
17
|
+
model_class.joins(:vendor)
|
|
18
|
+
.where(spree_vendors: { tenant_id: @tenant.id })
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def model_class
|
|
22
|
+
SpreeCmCommissioner::Trip
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def resource_serializer
|
|
26
|
+
SpreeCmCommissioner::V2::Storefront::TripSerializer
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -48,6 +48,8 @@ module SpreeCmCommissioner
|
|
|
48
48
|
name: 'ticket-type',
|
|
49
49
|
presentation: 'Ticket Type'
|
|
50
50
|
)
|
|
51
|
+
# auto assign the option type to the ticket
|
|
52
|
+
@ticket.option_types << @option_type unless @ticket.option_types.include?(@option_type)
|
|
51
53
|
|
|
52
54
|
@option_value = Spree::OptionValue.find_or_create_by!(
|
|
53
55
|
name: @ticket.name,
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
# Interactor wrapper around Google Routes API v2 to compute trip details.
|
|
3
|
+
#
|
|
4
|
+
# Inputs via context:
|
|
5
|
+
# - origin: { lat:, lng: } | "lat,lng" | [lat, lng]
|
|
6
|
+
# - destination: { lat:, lng: } | "lat,lng" | [lat, lng]
|
|
7
|
+
# - Optional arrays: waypoints, pickups, dropoffs (same point formats)
|
|
8
|
+
# - Optional boolean: optimize (default: true) to let Google reorder waypoints
|
|
9
|
+
#
|
|
10
|
+
# Outputs via context on success:
|
|
11
|
+
# - distance_km (Float)
|
|
12
|
+
# - ordered_waypoints (Array<Hash>)
|
|
13
|
+
# - ordered_points (Array<Hash>)
|
|
14
|
+
# - directions_url (String)
|
|
15
|
+
# - estimated_time_minutes (Integer) when optimize is true
|
|
16
|
+
#
|
|
17
|
+
# On failure, context.fail!(message: ...) is called.
|
|
18
|
+
class GoogleRoutesDistanceCalculator < BaseInteractor
|
|
19
|
+
delegate :origin, :destination, :waypoints, :pickups, :dropoffs, :optimize, to: :context
|
|
20
|
+
|
|
21
|
+
def call
|
|
22
|
+
initialize_points_and_options
|
|
23
|
+
|
|
24
|
+
via_points = combined_via_points
|
|
25
|
+
json = fetch_routes_for(@origin, @destination, via_points)
|
|
26
|
+
|
|
27
|
+
if json.nil?
|
|
28
|
+
context.fail!(message: 'Failed to fetch routes from Google API')
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if json['error']
|
|
33
|
+
error_message = json.dig('error', 'message') || json.dig('error', 'status')
|
|
34
|
+
context.fail!(message: "Google Routes API error: #{error_message}")
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
route = json['routes']&.first
|
|
39
|
+
context.fail!(message: 'No route found') if route.nil?
|
|
40
|
+
|
|
41
|
+
distance_meters = total_distance_of(route)
|
|
42
|
+
duration_seconds = total_duration_of(route)
|
|
43
|
+
ordered_waypoints = build_ordered_waypoints_from(route, via_points)
|
|
44
|
+
|
|
45
|
+
ordered_points = [@origin] + ordered_waypoints + [@destination]
|
|
46
|
+
|
|
47
|
+
context.distance_km = (distance_meters / 1000.0).round(3)
|
|
48
|
+
context.ordered_waypoints = ordered_waypoints
|
|
49
|
+
context.ordered_points = ordered_points
|
|
50
|
+
context.directions_url = build_human_link(ordered_waypoints)
|
|
51
|
+
# Only expose estimated time when optimize is enabled (per requirement)
|
|
52
|
+
context.estimated_time_minutes = (@optimize ? (duration_seconds / 60.0).round : nil)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def initialize_points_and_options
|
|
58
|
+
context.fail!(message: 'origin is required') if origin.blank?
|
|
59
|
+
context.fail!(message: 'destination is required') if destination.blank?
|
|
60
|
+
|
|
61
|
+
begin
|
|
62
|
+
@origin = normalize_point(origin)
|
|
63
|
+
@destination = normalize_point(destination)
|
|
64
|
+
|
|
65
|
+
extra_waypoints = Array(waypoints).compact
|
|
66
|
+
pickups_points = Array(pickups).compact
|
|
67
|
+
dropoff_points = Array(dropoffs).compact
|
|
68
|
+
|
|
69
|
+
@extra_waypoints = extra_waypoints.map { |p| normalize_point(p) }
|
|
70
|
+
@pickups_points = pickups_points.map { |p| normalize_point(p) }
|
|
71
|
+
@dropoff_points = dropoff_points.map { |p| normalize_point(p) }
|
|
72
|
+
rescue ArgumentError => e
|
|
73
|
+
context.fail!(message: e.message)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
@optimize = optimize.nil? ? true : !!optimize
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def google_map_key
|
|
80
|
+
@google_map_key ||= ENV.fetch('GOOGLE_MAP_KEY')
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def fetch_routes_for(from, to_point, via_waypoints)
|
|
84
|
+
url = "https://routes.googleapis.com/directions/v2:computeRoutes?key=#{google_map_key}"
|
|
85
|
+
headers = {
|
|
86
|
+
'Content-Type' => 'application/json',
|
|
87
|
+
'X-Goog-FieldMask' => [
|
|
88
|
+
'routes.duration',
|
|
89
|
+
'routes.distanceMeters',
|
|
90
|
+
'routes.legs',
|
|
91
|
+
'routes.optimizedIntermediateWaypointIndex',
|
|
92
|
+
'routes.legs.duration',
|
|
93
|
+
'routes.legs.distanceMeters'
|
|
94
|
+
].join(',')
|
|
95
|
+
}
|
|
96
|
+
body = build_request_body(from: from, to: to_point, via_waypoints: via_waypoints)
|
|
97
|
+
|
|
98
|
+
response = Faraday.post(url, body.to_json, headers)
|
|
99
|
+
return nil unless response.success?
|
|
100
|
+
|
|
101
|
+
JSON.parse(response.body)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def build_request_body(from:, to:, via_waypoints: [])
|
|
105
|
+
request = {
|
|
106
|
+
origin: {
|
|
107
|
+
location: {
|
|
108
|
+
latLng: {
|
|
109
|
+
latitude: from[:lat],
|
|
110
|
+
longitude: from[:lng]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
destination: {
|
|
115
|
+
location: {
|
|
116
|
+
latLng: {
|
|
117
|
+
latitude: to[:lat],
|
|
118
|
+
longitude: to[:lng]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
travelMode: 'DRIVE',
|
|
123
|
+
routingPreference: 'TRAFFIC_AWARE',
|
|
124
|
+
computeAlternativeRoutes: false,
|
|
125
|
+
units: 'METRIC'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if via_waypoints.any?
|
|
129
|
+
intermediates = via_waypoints.map do |point|
|
|
130
|
+
{
|
|
131
|
+
location: {
|
|
132
|
+
latLng: {
|
|
133
|
+
latitude: point[:lat],
|
|
134
|
+
longitude: point[:lng]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
request[:optimizeWaypointOrder] = true if @optimize
|
|
141
|
+
|
|
142
|
+
request[:intermediates] = intermediates
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
request
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def normalize_point(point)
|
|
149
|
+
return { lat: point[:lat].to_f, lng: point[:lng].to_f } if point.is_a?(Hash)
|
|
150
|
+
return { lat: point[0].to_f, lng: point[1].to_f } if point.is_a?(Array) && point.size == 2
|
|
151
|
+
|
|
152
|
+
if point.is_a?(String) && point.include?(',')
|
|
153
|
+
lat_str, lng_str = point.split(',', 2)
|
|
154
|
+
return { lat: lat_str.to_f, lng: lng_str.to_f }
|
|
155
|
+
end
|
|
156
|
+
raise ArgumentError, 'Invalid point format; expected { lat:, lng: }'
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def same_point?(point_a, point_b)
|
|
160
|
+
(point_a[:lat].to_f - point_b[:lat].to_f).abs < 1e-9 && (point_a[:lng].to_f - point_b[:lng].to_f).abs < 1e-9
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def combined_via_points
|
|
164
|
+
points = @extra_waypoints + @pickups_points + @dropoff_points
|
|
165
|
+
points.reject { |p| same_point?(p, @origin) || same_point?(p, @destination) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def total_distance_of(route)
|
|
169
|
+
# Routes API v2 provides distanceMeters at route level
|
|
170
|
+
distance = route['distanceMeters']
|
|
171
|
+
return distance.to_i if distance
|
|
172
|
+
|
|
173
|
+
# Fallback: sum from legs if route-level distance not available
|
|
174
|
+
route.fetch('legs', []).sum { |leg| leg['distanceMeters'].to_i }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def total_duration_of(route)
|
|
178
|
+
# Routes API v2 provides duration at route level as string "3600s"
|
|
179
|
+
duration_str = route['duration']
|
|
180
|
+
return duration_str.to_s.gsub('s', '').to_i if duration_str
|
|
181
|
+
|
|
182
|
+
# Fallback: sum from legs if route-level duration not available
|
|
183
|
+
route.fetch('legs', []).sum do |leg|
|
|
184
|
+
leg_duration = leg['duration']
|
|
185
|
+
leg_duration ? leg_duration.to_s.gsub('s', '').to_i : 0
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def build_ordered_waypoints_from(route, base_waypoints)
|
|
190
|
+
optimized_indices = route['optimizedIntermediateWaypointIndex'] || []
|
|
191
|
+
return base_waypoints if optimized_indices.empty? || !@optimize
|
|
192
|
+
|
|
193
|
+
optimized_indices.map { |idx| base_waypoints[idx] }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def format_point(point)
|
|
197
|
+
"#{point[:lat]},#{point[:lng]}"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def build_human_link(ordered_waypoints)
|
|
201
|
+
params = {
|
|
202
|
+
api: 1,
|
|
203
|
+
origin: format_point(@origin),
|
|
204
|
+
destination: format_point(@destination),
|
|
205
|
+
travelmode: 'driving'
|
|
206
|
+
}
|
|
207
|
+
params[:waypoints] = ordered_waypoints.map { |p| format_point(p) }.join('|') if ordered_waypoints.any?
|
|
208
|
+
"https://www.google.com/maps/dir/?#{URI.encode_www_form(params)}"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -49,10 +49,21 @@ module SpreeCmCommissioner
|
|
|
49
49
|
:passenger_count,
|
|
50
50
|
:pickup_oob_confirmed,
|
|
51
51
|
:drop_off_oob_confirmed,
|
|
52
|
+
:distance_km,
|
|
53
|
+
:directions_url,
|
|
54
|
+
:ordered_points,
|
|
55
|
+
:base_km,
|
|
56
|
+
:detour_pickup_km,
|
|
57
|
+
:detour_dropoff_km,
|
|
58
|
+
:extra_pickup_km,
|
|
59
|
+
:extra_dropoff_km,
|
|
60
|
+
:extra_pickup_charge_usd,
|
|
61
|
+
:extra_dropoff_charge_usd,
|
|
62
|
+
:estimated_time_minutes,
|
|
52
63
|
{ guests_attributes: %i[
|
|
53
64
|
first_name last_name gender age nationality_id
|
|
54
65
|
]
|
|
55
|
-
}
|
|
66
|
+
}
|
|
56
67
|
]
|
|
57
68
|
)
|
|
58
69
|
|
|
@@ -61,25 +72,54 @@ module SpreeCmCommissioner
|
|
|
61
72
|
end
|
|
62
73
|
|
|
63
74
|
def process_line_items_attributes(params)
|
|
64
|
-
date_param =
|
|
75
|
+
date_param = request_date
|
|
65
76
|
|
|
66
77
|
load_variant
|
|
67
78
|
|
|
68
79
|
params[:line_items_attributes].each_value do |item|
|
|
80
|
+
normalize_coordinate_group!(item, :pickup, :pickup_lat, :pickup_lng)
|
|
81
|
+
normalize_coordinate_group!(item, :drop_off, :drop_off_lat, :drop_off_lng)
|
|
82
|
+
|
|
69
83
|
pickup_time_minutes = item[:from_date]
|
|
70
|
-
combined_datetime =
|
|
71
|
-
|
|
72
|
-
item
|
|
73
|
-
item[:to_date] = combined_datetime
|
|
74
|
-
item[:variant_id] = @variant.id
|
|
75
|
-
item[:vendor_id] = current_vendor.id
|
|
76
|
-
item[:quantity] = 1
|
|
77
|
-
item[:currency] = 'USD'
|
|
78
|
-
item[:direction] = 'outbound'
|
|
79
|
-
item[:trip_id] = trip.id
|
|
84
|
+
combined_datetime = build_datetime(date_param, pickup_time_minutes)
|
|
85
|
+
|
|
86
|
+
assign_default_line_item_fields!(item, combined_datetime)
|
|
80
87
|
end
|
|
81
88
|
end
|
|
82
89
|
|
|
90
|
+
def request_date
|
|
91
|
+
raw_params[:date] || Date.current.to_s
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def build_datetime(date_string, time_string)
|
|
95
|
+
"#{date_string} #{time_string}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def normalize_coordinate_group!(item, group_key, lat_key, lng_key)
|
|
99
|
+
group = item[group_key]
|
|
100
|
+
return if group.blank?
|
|
101
|
+
|
|
102
|
+
if group.is_a?(Array)
|
|
103
|
+
item[lat_key], item[lng_key] = group
|
|
104
|
+
elsif group.is_a?(ActionController::Parameters) || group.is_a?(Hash)
|
|
105
|
+
item[lat_key] = group[:lat] || group['lat']
|
|
106
|
+
item[lng_key] = group[:lng] || group['lng']
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
item.delete(group_key)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def assign_default_line_item_fields!(item, combined_datetime)
|
|
113
|
+
item[:from_date] = combined_datetime
|
|
114
|
+
item[:to_date] = combined_datetime
|
|
115
|
+
item[:variant_id] = @variant.id
|
|
116
|
+
item[:vendor_id] = current_vendor.id
|
|
117
|
+
item[:quantity] = 1
|
|
118
|
+
item[:currency] = 'USD'
|
|
119
|
+
item[:direction] = 'outbound'
|
|
120
|
+
item[:trip_id] = trip.id
|
|
121
|
+
end
|
|
122
|
+
|
|
83
123
|
def create_order
|
|
84
124
|
context.order = Spree::Order.create(context.order_params)
|
|
85
125
|
|