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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/spree/admin/product_completion_steps_controller.rb +17 -1
  4. data/app/controllers/spree/admin/tenants_controller.rb +1 -1
  5. data/app/controllers/spree/api/v2/storefront/intercity_taxi/draft_orders_controller.rb +0 -1
  6. data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +3 -0
  7. data/app/controllers/spree/api/v2/tenant/intercity_taxi/draft_orders_controller.rb +41 -0
  8. data/app/controllers/spree/api/v2/tenant/trip_places_controller.rb +48 -0
  9. data/app/controllers/spree/api/v2/tenant/trip_search_controller.rb +103 -0
  10. data/app/controllers/spree/api/v2/tenant/trips_controller.rb +32 -0
  11. data/app/interactors/spree_cm_commissioner/create_ticket.rb +2 -0
  12. data/app/interactors/spree_cm_commissioner/google_routes_distance_calculator.rb +211 -0
  13. data/app/interactors/spree_cm_commissioner/host_matcher.rb +1 -1
  14. data/app/interactors/spree_cm_commissioner/intercity_taxi_order_creator.rb +52 -12
  15. data/app/interactors/spree_cm_commissioner/trip_distance_calculator.rb +165 -0
  16. data/app/interactors/spree_cm_commissioner/vendor_creation_telegram_alert_sender.rb +1 -1
  17. data/app/mailers/spree/order_mailer_decorator.rb +8 -9
  18. data/app/models/concerns/spree_cm_commissioner/order_scopes.rb +4 -5
  19. data/app/models/concerns/spree_cm_commissioner/service_recommendations.rb +2 -2
  20. data/app/models/concerns/spree_cm_commissioner/store_preference.rb +1 -0
  21. data/app/models/spree_cm_commissioner/product_completion_step.rb +4 -1
  22. data/app/models/spree_cm_commissioner/product_completion_step_banner.rb +12 -0
  23. data/app/models/spree_cm_commissioner/product_completion_steps/social_entry_url.rb +86 -19
  24. data/app/models/spree_cm_commissioner/tenant.rb +10 -1
  25. data/app/models/spree_cm_commissioner/vendor_place.rb +10 -1
  26. data/app/overrides/spree/admin/stores/_form/store_preferences.html.erb.deface +9 -0
  27. data/app/queries/spree_cm_commissioner/trip_query.rb +51 -33
  28. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vehicle_serializer.rb +1 -1
  29. data/app/services/spree_cm_commissioner/intercity_taxi_order/create.rb +8 -0
  30. data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +35 -45
  31. data/app/services/spree_cm_commissioner/orders/bulk_archive_inactive_orders.rb +10 -2
  32. data/app/services/spree_cm_commissioner/orders/daily_archive_inactive_orders.rb +8 -0
  33. data/app/services/spree_cm_commissioner/trips/search.rb +7 -11
  34. data/app/views/shared/_asset_field.html.erb +13 -0
  35. data/app/views/spree/admin/product_completion_steps/_form.html.erb +28 -2
  36. data/app/views/spree/admin/product_completion_steps/_supported_fields.html.erb +33 -0
  37. data/app/views/spree/admin/tenants/_form.html.erb +1 -1
  38. data/config/initializers/spree_permitted_attributes.rb +1 -0
  39. data/config/locales/en.yml +4 -0
  40. data/config/locales/km.yml +65 -61
  41. data/config/routes.rb +16 -0
  42. data/db/migrate/20251119093000_add_tenant_id_to_cm_vendor_places.rb +7 -0
  43. data/lib/spree_cm_commissioner/test_helper/factories/product_completion_step_banner_factory.rb +7 -0
  44. data/lib/spree_cm_commissioner/version.rb +1 -1
  45. 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
@@ -22,7 +22,7 @@ module SpreeCmCommissioner
22
22
  end
23
23
 
24
24
  def admin_chat_id
25
- ENV.fetch('EXCEPTION_NOTIFIER_TELEGRAM_CHANNEL_ID', nil)
25
+ Spree::Store.default&.preferred_telegram_new_vendor_alert_chat_id
26
26
  end
27
27
  end
28
28
  end
@@ -37,22 +37,21 @@ module Spree
37
37
 
38
38
  def setup_tenant_and_store
39
39
  @tenant = @order.tenant
40
- if @tenant.present?
41
- @brand_color = @tenant.preferences[:brand_primary_color]
42
- @vendor_logo_url = @tenant.active_vendor&.logo&.original_url
43
- @current_store = @tenant
44
- else
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
- "#{prefix}#{@current_store&.name} Booking Confirmation ##{@order.number}"
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? ? @current_store.host : @current_store.url
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(archived_at: nil, completed_at: nil)
21
- .where('updated_at < ?', threshold)
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(archived_at: nil, completed_at: nil)
28
- .where('updated_at >= ?', threshold - window)
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?(:host)
53
+ return default_link unless tenant.respond_to?(:url)
54
54
 
55
- site = tenant.host.to_s.strip
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
 
@@ -0,0 +1,12 @@
1
+ module SpreeCmCommissioner
2
+ class ProductCompletionStepBanner < Asset
3
+ # 16x9 aspect ratio
4
+ def asset_styles
5
+ {
6
+ mini: '160x90>',
7
+ small: '480x270>',
8
+ medium: '960x540>'
9
+ }
10
+ end
11
+ end
12
+ end
@@ -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
- # Replace order placeholders: {order.field_name}
77
- line_item.order.attributes.each do |field, value|
78
- url.gsub!("{order.#{field}}", value.to_s) if ORDER_ALLOWED_FIELDS.include?(field)
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
- # Replace line_item placeholders: {line_item.field_name}
82
- line_item.attributes.each do |field, value|
83
- url.gsub!("{line_item.#{field}}", value.to_s) if LINE_ITEM_ALLOWED_FIELDS.include?(field)
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
- # Replace guest placeholders: {guests[index].field_name}
87
- line_item.guests.each_with_index do |guest, index|
88
- guest.attributes.each do |field, value|
89
- url.gsub!("{guests[#{index}].#{field}}", value.to_s) if GUEST_ALLOWED_FIELDS.include?(field)
90
- end
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 completed?(line_item)
97
- return false if line_item.completion_steps.blank?
157
+ def replace_guest_placeholders(url, line_item)
158
+ return url if line_item.guests.blank?
98
159
 
99
- step_data = line_item.completion_steps.find { |step| step['position'].to_s == position.to_s }
100
- step_data&.dig('completed_at').present?
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, uniqueness: true, presence: true
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: { location: 0, branch: 1, stop: 2 }
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
- @inventory_sql ||= product_inventory_totals
38
- result = SpreeCmCommissioner::Trip
39
- .select(<<~SQL.squish)
40
- cm_trips.*,
41
- boarding.departure_time AS boarding_departure_time,
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