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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
# Computes trip distance details and pricing payload.
|
|
3
|
+
#
|
|
4
|
+
# Inputs via context:
|
|
5
|
+
# - origin: { lat:, lng: }
|
|
6
|
+
# - configured_destination: { lat:, lng: }
|
|
7
|
+
# - pickups: Array<{ lat:, lng: }> (optional)
|
|
8
|
+
# - dropoffs: Array<{ lat:, lng: }> (optional)
|
|
9
|
+
# - pickup_oob_confirmed: Boolean (optional)
|
|
10
|
+
# - dropoff_oob_confirmed: Boolean (optional)
|
|
11
|
+
# - base_price_usd: Numeric
|
|
12
|
+
# - boundary_km: Numeric (optional, default 0)
|
|
13
|
+
#
|
|
14
|
+
# Outputs via context on success:
|
|
15
|
+
# - payload: Hash (compatible with previous controller response)
|
|
16
|
+
# - base_km, detour_pickup_km, detour_dropoff_km: Floats or nil
|
|
17
|
+
# - distance_km, directions_url, ordered_points, estimated_time_minutes (when optimize true)
|
|
18
|
+
class TripDistanceCalculator < BaseInteractor
|
|
19
|
+
delegate :origin,
|
|
20
|
+
:configured_destination,
|
|
21
|
+
:pickups,
|
|
22
|
+
:dropoffs,
|
|
23
|
+
:pickup_oob_confirmed,
|
|
24
|
+
:dropoff_oob_confirmed,
|
|
25
|
+
:base_price_usd,
|
|
26
|
+
:boundary_km,
|
|
27
|
+
to: :context
|
|
28
|
+
|
|
29
|
+
def call
|
|
30
|
+
validate_inputs
|
|
31
|
+
|
|
32
|
+
pickups_points, dropoffs_points, final_destination = prepare_points
|
|
33
|
+
base_km, detour_pickup_km, detour_dropoff_km = compute_route_kms(pickups_points, dropoffs_points, final_destination)
|
|
34
|
+
details_ctx = fetch_details(final_destination, pickups_points, dropoffs_points)
|
|
35
|
+
extra_data = compute_extra_data(base_km, detour_pickup_km, detour_dropoff_km)
|
|
36
|
+
payload = build_payload(details_ctx, base_km, detour_pickup_km, detour_dropoff_km, extra_data)
|
|
37
|
+
assign_context(details_ctx, base_km, detour_pickup_km, detour_dropoff_km, payload)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def validate_inputs
|
|
43
|
+
context.fail!(message: 'origin is required') if origin.blank?
|
|
44
|
+
context.fail!(message: 'configured_destination is required') if configured_destination.blank?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def compute_km(origin:, destination:, pickups: nil, dropoffs: nil, optimize: nil)
|
|
48
|
+
ctx = SpreeCmCommissioner::GoogleRoutesDistanceCalculator.call(
|
|
49
|
+
origin: origin,
|
|
50
|
+
destination: destination,
|
|
51
|
+
pickups: pickups,
|
|
52
|
+
dropoffs: dropoffs,
|
|
53
|
+
optimize: optimize
|
|
54
|
+
)
|
|
55
|
+
return nil unless ctx.success?
|
|
56
|
+
|
|
57
|
+
ctx.distance_km
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def prepare_points
|
|
61
|
+
pickups_points = Array(pickups).compact
|
|
62
|
+
dropoffs_points = Array(dropoffs).compact
|
|
63
|
+
final_destination = dropoffs_points.last || configured_destination
|
|
64
|
+
[pickups_points, dropoffs_points, final_destination]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def compute_route_kms(pickups_points, dropoffs_points, final_destination)
|
|
68
|
+
base_km = compute_km(origin: origin, destination: configured_destination)
|
|
69
|
+
detour_pickup_km =
|
|
70
|
+
if pickups_points.any?
|
|
71
|
+
compute_km(
|
|
72
|
+
origin: origin,
|
|
73
|
+
destination: configured_destination,
|
|
74
|
+
pickups: pickups_points,
|
|
75
|
+
optimize: false
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
detour_dropoff_km =
|
|
79
|
+
if dropoffs_points.any?
|
|
80
|
+
compute_km(
|
|
81
|
+
origin: origin,
|
|
82
|
+
destination: final_destination,
|
|
83
|
+
dropoffs: dropoffs_points,
|
|
84
|
+
optimize: false
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
[base_km, detour_pickup_km, detour_dropoff_km]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def fetch_details(final_destination, pickups_points, dropoffs_points)
|
|
91
|
+
ctx = SpreeCmCommissioner::GoogleRoutesDistanceCalculator.call(
|
|
92
|
+
origin: origin,
|
|
93
|
+
destination: final_destination,
|
|
94
|
+
pickups: pickups_points,
|
|
95
|
+
dropoffs: dropoffs_points,
|
|
96
|
+
optimize: true
|
|
97
|
+
)
|
|
98
|
+
context.fail!(message: ctx.message || 'Unable to calculate') unless ctx.success?
|
|
99
|
+
ctx
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def compute_extra_data(base_km, detour_pickup_km, detour_dropoff_km)
|
|
103
|
+
extra_pickup_km = compute_extra_km(detour_pickup_km, base_km, pickup_oob_confirmed)
|
|
104
|
+
extra_dropoff_km = compute_extra_km(detour_dropoff_km, base_km, dropoff_oob_confirmed)
|
|
105
|
+
extra_pickup_charge_usd = charge_for_extra_km(extra_pickup_km)
|
|
106
|
+
extra_dropoff_charge_usd = charge_for_extra_km(extra_dropoff_km)
|
|
107
|
+
extra_charge_usd = (extra_pickup_charge_usd + extra_dropoff_charge_usd).round(2)
|
|
108
|
+
total_price_usd = (base_price_usd.to_f + extra_charge_usd).round(2)
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
extra_pickup_km: extra_pickup_km,
|
|
112
|
+
extra_dropoff_km: extra_dropoff_km,
|
|
113
|
+
extra_pickup_charge_usd: extra_pickup_charge_usd,
|
|
114
|
+
extra_dropoff_charge_usd: extra_dropoff_charge_usd,
|
|
115
|
+
total_price_usd: total_price_usd
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_payload(details_ctx, base_km, detour_pickup_km, detour_dropoff_km, data)
|
|
120
|
+
{
|
|
121
|
+
distance_km: details_ctx.distance_km,
|
|
122
|
+
directions_url: details_ctx.directions_url,
|
|
123
|
+
ordered_points: details_ctx.ordered_points,
|
|
124
|
+
estimated_time_minutes: details_ctx.estimated_time_minutes,
|
|
125
|
+
base_price_usd: base_price_usd.to_f,
|
|
126
|
+
total_price_usd: data[:total_price_usd],
|
|
127
|
+
extra_pickup_km: data[:extra_pickup_km],
|
|
128
|
+
extra_dropoff_km: data[:extra_dropoff_km],
|
|
129
|
+
extra_pickup_charge_usd: data[:extra_pickup_charge_usd],
|
|
130
|
+
extra_dropoff_charge_usd: data[:extra_dropoff_charge_usd],
|
|
131
|
+
extra_charge_usd: data[:extra_charge_usd],
|
|
132
|
+
base_km: base_km,
|
|
133
|
+
detour_pickup_km: detour_pickup_km,
|
|
134
|
+
detour_dropoff_km: detour_dropoff_km
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def assign_context(details_ctx, base_km, detour_pickup_km, detour_dropoff_km, payload)
|
|
139
|
+
context.payload = payload
|
|
140
|
+
context.distance_km = details_ctx.distance_km
|
|
141
|
+
context.directions_url = details_ctx.directions_url
|
|
142
|
+
context.ordered_points = details_ctx.ordered_points
|
|
143
|
+
context.estimated_time_minutes = details_ctx.estimated_time_minutes
|
|
144
|
+
context.base_km = base_km
|
|
145
|
+
context.detour_pickup_km = detour_pickup_km
|
|
146
|
+
context.detour_dropoff_km = detour_dropoff_km
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def compute_extra_km(detour_km, base_km, confirmed)
|
|
150
|
+
return 0.0 unless confirmed && detour_km && base_km
|
|
151
|
+
|
|
152
|
+
[(detour_km - base_km - boundary_km), 0.0].max
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def charge_for_extra_km(extra_km)
|
|
156
|
+
x = extra_km.to_f
|
|
157
|
+
return 0.0 if x <= 0
|
|
158
|
+
return 2.0 if x <= 5.0
|
|
159
|
+
return 5.0 if x <= 10.0
|
|
160
|
+
return 10.0 if x <= 20.0
|
|
161
|
+
|
|
162
|
+
(10.0 + ((x - 20.0) * 0.5)).round(2)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -37,22 +37,21 @@ module Spree
|
|
|
37
37
|
|
|
38
38
|
def setup_tenant_and_store
|
|
39
39
|
@tenant = @order.tenant
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@current_store = @order.store
|
|
46
|
-
end
|
|
40
|
+
@current_store = @order.store
|
|
41
|
+
return if @tenant.blank?
|
|
42
|
+
|
|
43
|
+
@brand_color = @tenant.preferences[:brand_primary_color]
|
|
44
|
+
@vendor_logo_url = @tenant.active_vendor&.logo&.original_url
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
def build_subject(resend)
|
|
50
48
|
prefix = resend ? "[#{Spree.t(:resend).upcase}] " : ''
|
|
51
|
-
|
|
49
|
+
store_name = @tenant.present? ? @tenant.name : @current_store&.name
|
|
50
|
+
"#{prefix}#{store_name} Booking Confirmation ##{@order.number}"
|
|
52
51
|
end
|
|
53
52
|
|
|
54
53
|
def store_url
|
|
55
|
-
@tenant.present? ? @
|
|
54
|
+
@tenant.present? ? @tenant.url : @current_store.url
|
|
56
55
|
end
|
|
57
56
|
|
|
58
57
|
def ticket_email(guest, email)
|
|
@@ -17,16 +17,15 @@ module SpreeCmCommissioner
|
|
|
17
17
|
# Inactive orders scopes with parameterized dates
|
|
18
18
|
# Usage: Spree::Order.inactive_incomplete_all(threshold: 14.days.ago)
|
|
19
19
|
scope :inactive_incomplete_all, lambda { |threshold: 14.days.ago|
|
|
20
|
-
where(
|
|
21
|
-
.where(
|
|
20
|
+
where('updated_at < ?', threshold)
|
|
21
|
+
.where(archived_at: nil, completed_at: nil)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
# Usage: Spree::Order.inactive_incomplete(threshold: 14.days.ago, window: 7.days)
|
|
25
25
|
# Returns orders updated between (threshold - window) and threshold
|
|
26
26
|
scope :inactive_incomplete, lambda { |threshold: 14.days.ago, window: 7.days|
|
|
27
|
-
where(
|
|
28
|
-
.where(
|
|
29
|
-
.where('updated_at < ?', threshold)
|
|
27
|
+
where('updated_at >= ? AND updated_at < ?', threshold - window, threshold)
|
|
28
|
+
.where(archived_at: nil, completed_at: nil)
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
# Filter scopes
|
|
@@ -50,9 +50,9 @@ module SpreeCmCommissioner
|
|
|
50
50
|
private
|
|
51
51
|
|
|
52
52
|
def tenant_link_or_default(tenant, default_link)
|
|
53
|
-
return default_link unless tenant.respond_to?(:
|
|
53
|
+
return default_link unless tenant.respond_to?(:url)
|
|
54
54
|
|
|
55
|
-
site = tenant.
|
|
55
|
+
site = tenant.url.to_s.strip
|
|
56
56
|
site.presence || default_link
|
|
57
57
|
rescue StandardError
|
|
58
58
|
default_link
|
|
@@ -6,6 +6,7 @@ module SpreeCmCommissioner
|
|
|
6
6
|
preference :sms_sender_id, :string
|
|
7
7
|
preference :telegram_order_alert_chat_id, :string
|
|
8
8
|
preference :telegram_order_request_alert_chat_id, :string
|
|
9
|
+
preference :telegram_new_vendor_alert_chat_id, :string
|
|
9
10
|
preference :assetlinks, :string, default: ''
|
|
10
11
|
preference :apple_app_site_association, :string, default: ''
|
|
11
12
|
end
|
|
@@ -4,6 +4,8 @@ module SpreeCmCommissioner
|
|
|
4
4
|
|
|
5
5
|
belongs_to :product, class_name: '::Spree::Product', optional: false
|
|
6
6
|
|
|
7
|
+
has_one :banner, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::ProductCompletionStepBanner'
|
|
8
|
+
|
|
7
9
|
# When a completion step is changed, regenerate completion steps for all line items
|
|
8
10
|
# of the product so they reflect the latest step configuration.
|
|
9
11
|
after_destroy :regenerate_line_items_completion_steps
|
|
@@ -28,7 +30,8 @@ module SpreeCmCommissioner
|
|
|
28
30
|
action_label: action_label,
|
|
29
31
|
action_url: action_url_for(line_item),
|
|
30
32
|
completed_at: existing_data&.dig('completed_at'),
|
|
31
|
-
completed: completed?(line_item)
|
|
33
|
+
completed: completed?(line_item),
|
|
34
|
+
banner_url: banner.present? ? banner.original_url : nil
|
|
32
35
|
}
|
|
33
36
|
end
|
|
34
37
|
|
|
@@ -5,6 +5,7 @@ module SpreeCmCommissioner
|
|
|
5
5
|
# - https://t.me/ThePlatformKHBot?start={order.number}
|
|
6
6
|
# - https://example.com?guest_name={guests[0].first_name}&phone={guests[0].phone_number}
|
|
7
7
|
# - https://example.com?line_item_id={line_item.id}&quantity={line_item.quantity}
|
|
8
|
+
# - https://example.com?bill_address.first_name={bill_address.first_name}&bill_address.lastname={bill_address.lastname}
|
|
8
9
|
preference :entry_point_link, :string
|
|
9
10
|
|
|
10
11
|
# Allowed fields per object (excludes private/reference fields like *_id, *_by_id, metadata, etc.)
|
|
@@ -14,7 +15,11 @@ module SpreeCmCommissioner
|
|
|
14
15
|
state
|
|
15
16
|
email
|
|
16
17
|
total
|
|
18
|
+
display_total
|
|
17
19
|
item_total
|
|
20
|
+
display_item_total
|
|
21
|
+
adjustment_total
|
|
22
|
+
display_adjustment_total
|
|
18
23
|
created_at
|
|
19
24
|
completed_at
|
|
20
25
|
token
|
|
@@ -23,12 +28,31 @@ module SpreeCmCommissioner
|
|
|
23
28
|
currency
|
|
24
29
|
item_count
|
|
25
30
|
channel
|
|
31
|
+
special_instructions
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
BILL_ADDRESS_ALLOWED_FIELDS = %w[
|
|
35
|
+
first_name
|
|
36
|
+
last_name
|
|
37
|
+
full_name
|
|
38
|
+
address1
|
|
39
|
+
address2
|
|
40
|
+
city
|
|
41
|
+
zipcode
|
|
42
|
+
phone
|
|
43
|
+
state_name
|
|
44
|
+
alternative_phone
|
|
45
|
+
company
|
|
46
|
+
label
|
|
47
|
+
age
|
|
48
|
+
gender
|
|
26
49
|
].freeze
|
|
27
50
|
|
|
28
51
|
LINE_ITEM_ALLOWED_FIELDS = %w[
|
|
29
52
|
qr_data
|
|
30
53
|
quantity
|
|
31
54
|
price
|
|
55
|
+
display_price
|
|
32
56
|
from_date
|
|
33
57
|
to_date
|
|
34
58
|
number
|
|
@@ -45,7 +69,6 @@ module SpreeCmCommissioner
|
|
|
45
69
|
dob
|
|
46
70
|
gender
|
|
47
71
|
age
|
|
48
|
-
email
|
|
49
72
|
phone_number
|
|
50
73
|
address
|
|
51
74
|
seat_number
|
|
@@ -60,9 +83,6 @@ module SpreeCmCommissioner
|
|
|
60
83
|
expectation
|
|
61
84
|
other_occupation
|
|
62
85
|
other_organization
|
|
63
|
-
entry_type
|
|
64
|
-
upload_later
|
|
65
|
-
data_fill_stage_phase
|
|
66
86
|
created_at
|
|
67
87
|
updated_at
|
|
68
88
|
].freeze
|
|
@@ -72,32 +92,79 @@ module SpreeCmCommissioner
|
|
|
72
92
|
return nil if preferred_entry_point_link.blank?
|
|
73
93
|
|
|
74
94
|
url = preferred_entry_point_link.dup
|
|
95
|
+
url = replace_order_placeholders(url, line_item)
|
|
96
|
+
url = replace_bill_address_placeholders(url, line_item)
|
|
97
|
+
url = replace_line_item_placeholders(url, line_item)
|
|
98
|
+
|
|
99
|
+
replace_guest_placeholders(url, line_item)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def completed?(line_item)
|
|
103
|
+
return false if line_item.completion_steps.blank?
|
|
104
|
+
|
|
105
|
+
step_data = line_item.completion_steps.find { |step| step['position'].to_s == position.to_s }
|
|
106
|
+
step_data&.dig('completed_at').present?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns a hash of all supported placeholder fields grouped by object type.
|
|
110
|
+
# Used by the admin UI to display available placeholders when configuring the entry_point_link template.
|
|
111
|
+
# Example usage in template: {order.number}, {bill_address.first_name}, {line_item.quantity}, {guests[0].email}
|
|
112
|
+
#
|
|
113
|
+
# @return [Hash] Hash with keys :order, :bill_address, :line_item, :guests
|
|
114
|
+
# Each key maps to an array of allowed field names for that object type
|
|
115
|
+
def supported_fields
|
|
116
|
+
{
|
|
117
|
+
order: ORDER_ALLOWED_FIELDS,
|
|
118
|
+
bill_address: BILL_ADDRESS_ALLOWED_FIELDS,
|
|
119
|
+
line_item: LINE_ITEM_ALLOWED_FIELDS,
|
|
120
|
+
guests: GUEST_ALLOWED_FIELDS
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
75
125
|
|
|
76
|
-
|
|
77
|
-
line_item.order.
|
|
78
|
-
|
|
126
|
+
def replace_order_placeholders(url, line_item)
|
|
127
|
+
return url if line_item.order.blank?
|
|
128
|
+
|
|
129
|
+
ORDER_ALLOWED_FIELDS.each do |field|
|
|
130
|
+
value = line_item.order.public_send(field)
|
|
131
|
+
url.gsub!("{order.#{field}}", value.to_s) if value.present?
|
|
79
132
|
end
|
|
80
133
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
134
|
+
url
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def replace_bill_address_placeholders(url, line_item)
|
|
138
|
+
return url if line_item.order&.bill_address.blank?
|
|
139
|
+
|
|
140
|
+
BILL_ADDRESS_ALLOWED_FIELDS.each do |field|
|
|
141
|
+
value = line_item.order.bill_address.public_send(field)
|
|
142
|
+
url.gsub!("{bill_address.#{field}}", value.to_s) if value.present?
|
|
84
143
|
end
|
|
85
144
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
145
|
+
url
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def replace_line_item_placeholders(url, line_item)
|
|
149
|
+
LINE_ITEM_ALLOWED_FIELDS.each do |field|
|
|
150
|
+
value = line_item.public_send(field)
|
|
151
|
+
url.gsub!("{line_item.#{field}}", value.to_s) if value.present?
|
|
91
152
|
end
|
|
92
153
|
|
|
93
154
|
url
|
|
94
155
|
end
|
|
95
156
|
|
|
96
|
-
def
|
|
97
|
-
return
|
|
157
|
+
def replace_guest_placeholders(url, line_item)
|
|
158
|
+
return url if line_item.guests.blank?
|
|
98
159
|
|
|
99
|
-
|
|
100
|
-
|
|
160
|
+
line_item.guests.each_with_index do |guest, index|
|
|
161
|
+
GUEST_ALLOWED_FIELDS.each do |field|
|
|
162
|
+
value = guest.public_send(field)
|
|
163
|
+
url.gsub!("{guests[#{index}].#{field}}", value.to_s) if value.present?
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
url
|
|
101
168
|
end
|
|
102
169
|
end
|
|
103
170
|
end
|
|
@@ -12,7 +12,10 @@ module SpreeCmCommissioner
|
|
|
12
12
|
through: :vendors,
|
|
13
13
|
source: :payment_methods
|
|
14
14
|
|
|
15
|
-
validates :host,
|
|
15
|
+
validates :host, presence: true, uniqueness: true,
|
|
16
|
+
format: { without: %r{\Ahttps?://},
|
|
17
|
+
message: :format
|
|
18
|
+
}
|
|
16
19
|
|
|
17
20
|
enum state: { enabled: 0, disabled: 1 }
|
|
18
21
|
|
|
@@ -22,6 +25,12 @@ module SpreeCmCommissioner
|
|
|
22
25
|
vendors.where(state: :active).first
|
|
23
26
|
end
|
|
24
27
|
|
|
28
|
+
def url
|
|
29
|
+
return if host.blank?
|
|
30
|
+
|
|
31
|
+
Rails.env.development? || Rails.env.test? ? "http://#{host}" : "https://#{host}"
|
|
32
|
+
end
|
|
33
|
+
|
|
25
34
|
private
|
|
26
35
|
|
|
27
36
|
def generate_slug
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
class VendorPlace < Base
|
|
3
|
-
enum place_type: {
|
|
3
|
+
enum place_type: { venue: 0, branch: 1, stop: 2, location: 3 }
|
|
4
4
|
acts_as_list scope: :vendor
|
|
5
5
|
|
|
6
|
+
belongs_to :tenant, class_name: 'SpreeCmCommissioner::Tenant', optional: true
|
|
6
7
|
belongs_to :vendor, class_name: 'Spree::Vendor', optional: false
|
|
7
8
|
belongs_to :place, class_name: 'SpreeCmCommissioner::Place', optional: false
|
|
8
9
|
belongs_to :location, class_name: 'SpreeCmCommissioner::VendorPlace', optional: true
|
|
@@ -16,6 +17,8 @@ module SpreeCmCommissioner
|
|
|
16
17
|
|
|
17
18
|
accepts_nested_attributes_for :place, allow_destroy: true
|
|
18
19
|
|
|
20
|
+
before_save :set_tenant_id
|
|
21
|
+
|
|
19
22
|
# include place when using these delegate to avoid N+1
|
|
20
23
|
def display_name
|
|
21
24
|
place&.name
|
|
@@ -28,5 +31,11 @@ module SpreeCmCommissioner
|
|
|
28
31
|
def selected
|
|
29
32
|
vendor.selected_place_references.include?(place&.reference)
|
|
30
33
|
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def set_tenant_id
|
|
38
|
+
self.tenant_id = vendor&.tenant_id
|
|
39
|
+
end
|
|
31
40
|
end
|
|
32
41
|
end
|
|
@@ -37,6 +37,15 @@
|
|
|
37
37
|
<% end %>
|
|
38
38
|
</div>
|
|
39
39
|
|
|
40
|
+
<!-- Telegram New Vendor Alert Chat ID -->
|
|
41
|
+
<div class="col-6">
|
|
42
|
+
<%= f.field_container :preferred_telegram_new_vendor_alert_chat_id do %>
|
|
43
|
+
<%= f.label :preferred_telegram_new_vendor_alert_chat_id, Spree.t(:telegram_new_vendor_alert_chat_id) %>
|
|
44
|
+
<%= f.text_field :preferred_telegram_new_vendor_alert_chat_id, class: 'form-control' %>
|
|
45
|
+
<%= f.error_message_on :preferred_telegram_new_vendor_alert_chat_id %>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
40
49
|
<!-- Assetlinks -->
|
|
41
50
|
<div class="col-12">
|
|
42
51
|
<%= f.field_container :preferred_assetlinks do %>
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
class TripQuery
|
|
3
|
-
attr_reader :origin_id, :destination_id, :date, :vendor_id, :number_of_guests, :params
|
|
3
|
+
attr_reader :origin_id, :destination_id, :date, :vendor_id, :tenant_id, :number_of_guests, :route_type, :params
|
|
4
4
|
|
|
5
|
-
def initialize(origin_id:, destination_id:, date:, vendor_id: nil, number_of_guests: nil, params: {}) # rubocop:disable Metrics/ParameterLists
|
|
5
|
+
def initialize(origin_id:, destination_id:, date:, route_type: nil, vendor_id: nil, tenant_id: nil, number_of_guests: nil, params: {}) # rubocop:disable Metrics/ParameterLists
|
|
6
6
|
@origin_id = origin_id
|
|
7
7
|
@destination_id = destination_id
|
|
8
8
|
@date = date.to_date == Time.zone.now.to_date ? Time.zone.now : Time.zone.parse(date.to_s)
|
|
9
9
|
@vendor_id = vendor_id
|
|
10
|
+
@tenant_id = tenant_id
|
|
10
11
|
@number_of_guests = number_of_guests || 1
|
|
12
|
+
@route_type = route_type
|
|
11
13
|
@params = params
|
|
12
14
|
@page = (params[:page] || 1).to_i
|
|
13
15
|
@per_page = params[:per_page]
|
|
@@ -34,37 +36,11 @@ module SpreeCmCommissioner
|
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def direct_trips
|
|
37
|
-
|
|
38
|
-
result =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
boarding.stop_place_id AS boarding_stop_id, boarding.stop_name AS boarding_stop_name,
|
|
43
|
-
drop_off.stop_name AS drop_off_stop_name, drop_off.stop_place_id AS drop_off_stop_id,
|
|
44
|
-
drop_off.arrival_time AS drop_off_arrival_time,
|
|
45
|
-
COALESCE(iv.quantity_available, 0) AS quantity_available,
|
|
46
|
-
COALESCE(iv.max_capacity, 0) AS max_capacity,
|
|
47
|
-
origin_places.name AS origin_place_name, dest_places.name AS destination_place_name,
|
|
48
|
-
prices.amount AS amount, prices.compare_at_amount AS compare_at_amount,
|
|
49
|
-
prices.currency AS currency
|
|
50
|
-
SQL
|
|
51
|
-
.includes(vendor: :logo, vehicle: :option_values)
|
|
52
|
-
.joins(<<~SQL.squish)
|
|
53
|
-
INNER JOIN cm_trip_stops AS boarding ON boarding.trip_id = cm_trips.id AND boarding.stop_type = 0
|
|
54
|
-
INNER JOIN cm_trip_stops AS drop_off ON drop_off.trip_id = cm_trips.id AND drop_off.stop_type = 1
|
|
55
|
-
INNER JOIN cm_places AS origin_places ON origin_places.id = cm_trips.origin_place_id
|
|
56
|
-
INNER JOIN cm_places AS dest_places ON dest_places.id = cm_trips.destination_place_id
|
|
57
|
-
INNER JOIN spree_variants AS master ON master.product_id = cm_trips.product_id AND master.is_master = true
|
|
58
|
-
INNER JOIN spree_prices AS prices ON prices.variant_id = master.id AND prices.deleted_at IS NULL
|
|
59
|
-
SQL
|
|
60
|
-
.where('(boarding.location_place_id = ? OR boarding.stop_place_id = ? OR cm_trips.origin_place_id = ?)
|
|
61
|
-
AND (drop_off.location_place_id = ? OR drop_off.stop_place_id = ? OR cm_trips.destination_place_id = ?)',
|
|
62
|
-
origin_id, origin_id, origin_id, destination_id, destination_id, destination_id
|
|
63
|
-
)
|
|
64
|
-
.joins("INNER JOIN (#{@inventory_sql.to_sql}) iv ON cm_trips.product_id = iv.product_id")
|
|
65
|
-
|
|
66
|
-
result = result.where(vendor_id: vendor_id) if vendor_id.present?
|
|
67
|
-
@per_page.to_i.positive? ? result.page(@page).per(@per_page) : result.page(@page)
|
|
39
|
+
result = trip_scope
|
|
40
|
+
result = result.where({ vendor_id: vendor_id }.compact) if vendor_id.present?
|
|
41
|
+
result = result.where(route_type: route_type) if route_type.present?
|
|
42
|
+
result = result.where(spree_vendors: { tenant_id: tenant_id }) if tenant_id.present?
|
|
43
|
+
paginate(result)
|
|
68
44
|
end
|
|
69
45
|
|
|
70
46
|
def product_inventory_totals
|
|
@@ -81,6 +57,48 @@ module SpreeCmCommissioner
|
|
|
81
57
|
|
|
82
58
|
private
|
|
83
59
|
|
|
60
|
+
def trip_scope
|
|
61
|
+
scope = SpreeCmCommissioner::Trip
|
|
62
|
+
.select(<<~SQL.squish)
|
|
63
|
+
cm_trips.*,
|
|
64
|
+
boarding.departure_time AS boarding_departure_time,
|
|
65
|
+
boarding.stop_place_id AS boarding_stop_id, boarding.stop_name AS boarding_stop_name,
|
|
66
|
+
drop_off.stop_name AS drop_off_stop_name, drop_off.stop_place_id AS drop_off_stop_id,
|
|
67
|
+
drop_off.arrival_time AS drop_off_arrival_time,
|
|
68
|
+
COALESCE(iv.quantity_available, 0) AS quantity_available,
|
|
69
|
+
COALESCE(iv.max_capacity, 0) AS max_capacity,
|
|
70
|
+
origin_places.name AS origin_place_name, dest_places.name AS destination_place_name,
|
|
71
|
+
prices.amount AS amount, prices.compare_at_amount AS compare_at_amount,
|
|
72
|
+
prices.currency AS currency
|
|
73
|
+
SQL
|
|
74
|
+
.includes(vendor: :logo, vehicle: :option_values)
|
|
75
|
+
|
|
76
|
+
scope = scope.joins(:vendor) if tenant_id.present?
|
|
77
|
+
|
|
78
|
+
scope
|
|
79
|
+
.joins(<<~SQL.squish)
|
|
80
|
+
INNER JOIN cm_trip_stops AS boarding ON boarding.trip_id = cm_trips.id AND boarding.stop_type = 0
|
|
81
|
+
INNER JOIN cm_trip_stops AS drop_off ON drop_off.trip_id = cm_trips.id AND drop_off.stop_type = 1
|
|
82
|
+
INNER JOIN cm_places AS origin_places ON origin_places.id = cm_trips.origin_place_id
|
|
83
|
+
INNER JOIN cm_places AS dest_places ON dest_places.id = cm_trips.destination_place_id
|
|
84
|
+
INNER JOIN spree_variants AS master ON master.product_id = cm_trips.product_id AND master.is_master = true
|
|
85
|
+
INNER JOIN spree_prices AS prices ON prices.variant_id = master.id AND prices.deleted_at IS NULL
|
|
86
|
+
SQL
|
|
87
|
+
.where('(boarding.location_place_id = ? OR boarding.stop_place_id = ? OR cm_trips.origin_place_id = ?)
|
|
88
|
+
AND (drop_off.location_place_id = ? OR drop_off.stop_place_id = ? OR cm_trips.destination_place_id = ?)',
|
|
89
|
+
origin_id, origin_id, origin_id, destination_id, destination_id, destination_id
|
|
90
|
+
)
|
|
91
|
+
.joins("INNER JOIN (#{inventory_sql.to_sql}) iv ON cm_trips.product_id = iv.product_id")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def paginate(result)
|
|
95
|
+
@per_page.to_i.positive? ? result.page(@page).per(@per_page) : result.page(@page)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def inventory_sql
|
|
99
|
+
@inventory_sql ||= product_inventory_totals
|
|
100
|
+
end
|
|
101
|
+
|
|
84
102
|
def build_trip_result(trip)
|
|
85
103
|
vehicle = trip&.vehicle
|
|
86
104
|
vendor = trip&.vendor
|
|
@@ -4,7 +4,7 @@ module SpreeCmCommissioner
|
|
|
4
4
|
class TripVehicleSerializer < BaseSerializer
|
|
5
5
|
set_type :vehicle
|
|
6
6
|
|
|
7
|
-
attributes :id, :code, :vehicle_type
|
|
7
|
+
attributes :id, :code, :vehicle_type, :number_of_seats
|
|
8
8
|
|
|
9
9
|
has_many :vehicle_photos, serializer: ::SpreeCmCommissioner::V2::Storefront::AssetSerializer
|
|
10
10
|
has_many :amenities, serializer: ::SpreeCmCommissioner::V2::Storefront::AmenitySerializer
|
|
@@ -42,6 +42,7 @@ module SpreeCmCommissioner
|
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
build_guests_for!(line_item, quantity)
|
|
45
|
+
assign_saved_guests_to_line_item(line_item)
|
|
45
46
|
|
|
46
47
|
order.save!
|
|
47
48
|
order.update_with_updater!
|
|
@@ -62,6 +63,13 @@ module SpreeCmCommissioner
|
|
|
62
63
|
def build_guests_for!(line_item, qty)
|
|
63
64
|
Array.new(qty) { line_item.guests.new }
|
|
64
65
|
end
|
|
66
|
+
|
|
67
|
+
def assign_saved_guests_to_line_item(line_item)
|
|
68
|
+
line_item.guests.each do |guest|
|
|
69
|
+
saved_guest = SpreeCmCommissioner::SavedGuest.new
|
|
70
|
+
guest.saved_guest = saved_guest
|
|
71
|
+
end
|
|
72
|
+
end
|
|
65
73
|
end
|
|
66
74
|
end
|
|
67
75
|
end
|