spree_cm_commissioner 2.3.0.pre.pre16 → 2.3.0.pre.pre18

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +33 -0
  4. data/app/controllers/blazer/base_controller_decorator.rb +20 -1
  5. data/app/controllers/spree/api/v2/storefront/intercity_taxi/draft_orders_controller.rb +40 -0
  6. data/app/controllers/spree/api/v2/storefront/line_items_controller.rb +1 -0
  7. data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +47 -9
  8. data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +1 -1
  9. data/app/factory/spree_cm_commissioner/vendor_telegram_message_factory.rb +65 -0
  10. data/app/helpers/spree/base_helper_decorator.rb +2 -19
  11. data/app/interactors/spree_cm_commissioner/user_registration_with_id_token.rb +72 -36
  12. data/app/interactors/spree_cm_commissioner/vendor_creation_telegram_alert_sender.rb +28 -0
  13. data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +1 -1
  14. data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +1 -1
  15. data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +1 -1
  16. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +1 -1
  17. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +1 -1
  18. data/app/jobs/spree_cm_commissioner/vendor_creation_telegram_alert_sender_job.rb +10 -0
  19. data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +51 -0
  20. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +7 -0
  21. data/app/models/concerns/spree_cm_commissioner/option_value_attr_type.rb +25 -0
  22. data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +3 -14
  23. data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +18 -4
  24. data/app/models/spree_cm_commissioner/block.rb +19 -0
  25. data/app/models/spree_cm_commissioner/line_item_decorator.rb +5 -0
  26. data/app/models/spree_cm_commissioner/product_decorator.rb +1 -0
  27. data/app/models/spree_cm_commissioner/variant_decorator.rb +0 -5
  28. data/app/models/spree_cm_commissioner/variant_options.rb +8 -0
  29. data/app/models/spree_cm_commissioner/vendor_decorator.rb +6 -0
  30. data/app/overrides/spree/admin/variants/_form/add_permanent_stock.html.erb.deface +2 -1
  31. data/app/serializers/spree/v2/storefront/line_item_serializer_decorator.rb +2 -0
  32. data/app/serializers/spree_cm_commissioner/v2/storefront/intercity_taxi_cart_serializer.rb +12 -0
  33. data/app/serializers/spree_cm_commissioner/v2/storefront/intercity_taxi_line_item_serializer.rb +31 -0
  34. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_result_serializer.rb +11 -0
  35. data/app/services/spree_cm_commissioner/intercity_taxi_order/create.rb +67 -0
  36. data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +79 -0
  37. data/app/services/spree_cm_commissioner/{transit/base_route_order_metrics_updater.rb → routes/base_update_order_metrics.rb} +2 -2
  38. data/app/services/spree_cm_commissioner/{transit/route_previous_trip_count_decrementer_service.rb → routes/decrement_previous_trip_count.rb} +3 -3
  39. data/app/services/spree_cm_commissioner/{transit/route_trip_count_decrementer_service.rb → routes/decrement_trip_count.rb} +2 -2
  40. data/app/services/spree_cm_commissioner/{transit/route_fulfilled_order_count_incrementer_service.rb → routes/increment_fulfilled_order_count.rb} +3 -3
  41. data/app/services/spree_cm_commissioner/{transit/route_order_count_incrementer_service.rb → routes/increment_order_count.rb} +3 -3
  42. data/app/services/spree_cm_commissioner/{transit/route_trip_count_incrementer_service.rb → routes/increment_trip_count.rb} +2 -2
  43. data/app/services/spree_cm_commissioner/trips/search.rb +65 -0
  44. data/app/views/blazer/queries/embed/_content.html.erb +51 -2
  45. data/app/views/spree/admin/option_types/_color_field.html.erb +21 -0
  46. data/app/views/spree/admin/option_types/_option_value_fields.html.erb +2 -0
  47. data/app/views/spree/admin/variants/_color_field.html.erb +28 -0
  48. data/app/views/spree/admin/variants/_date_field.html.erb +11 -1
  49. data/app/views/spree/admin/variants/_default_field.html.erb +7 -3
  50. data/app/views/spree/admin/variants/_option_values.html.erb +5 -1
  51. data/app/views/spree/admin/variants/_time_field.html.erb +7 -1
  52. data/app/views/spree/order_mailer/_adjustment.html.erb +7 -0
  53. data/app/views/spree_cm_commissioner/event_transactional_mailer/_event_banner.html.erb +9 -3
  54. data/config/locales/km.yml +64 -0
  55. data/config/routes.rb +4 -1
  56. data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +18 -0
  57. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +30 -0
  58. data/lib/spree_cm_commissioner/trip_result.rb +15 -0
  59. data/lib/spree_cm_commissioner/version.rb +1 -1
  60. metadata +20 -8
@@ -13,6 +13,8 @@ module SpreeCmCommissioner
13
13
 
14
14
  before_validation :construct_time, if: :attr_type_time?
15
15
  before_validation :construct_date, if: :attr_type_date?
16
+ before_validation :construct_color, if: :attr_type_color?
17
+ before_validation :construct_ticket_type, if: :ticket_type?
16
18
  before_validation :normalize_items, if: :attr_type_array?
17
19
 
18
20
  after_save :update_variants_metadata, if: :saved_change_to_name?
@@ -27,6 +29,10 @@ module SpreeCmCommissioner
27
29
  end
28
30
  end
29
31
 
32
+ def ticket_type?
33
+ option_type.present? && option_type&.ticket_type?
34
+ end
35
+
30
36
  def items
31
37
  return nil unless attr_type_array?
32
38
  return nil if name.nil?
@@ -92,6 +98,13 @@ module SpreeCmCommissioner
92
98
  self.name = name.split(',').map(&:strip).join(',')
93
99
  end
94
100
 
101
+ # Ticket types are case-sensitive: "STANDARD" and "Standard" are distinct ticket types.
102
+ # Only strip whitespace; preserve the exact case entered by the user.
103
+ def construct_ticket_type
104
+ self.name = name&.strip&.presence
105
+ self.presentation = name&.strip&.presence
106
+ end
107
+
95
108
  def construct_time
96
109
  hour, minute = extract_time_from_time_select
97
110
  hour, minute = extract_time_from_default_format if hour.nil? || minute.nil?
@@ -141,5 +154,17 @@ module SpreeCmCommissioner
141
154
  self.name = nil if parse_date(name).blank?
142
155
  self.presentation = name if parse_date(presentation) != parse_date(name)
143
156
  end
157
+
158
+ def construct_color
159
+ self.name = parse_hex_color(name)
160
+ self.presentation = name if parse_hex_color(presentation) != parse_hex_color(name)
161
+ end
162
+
163
+ def parse_hex_color(value)
164
+ return nil if value.nil?
165
+ return nil unless value.match?(/^#[0-9A-Fa-f]{6}$/)
166
+
167
+ value.upcase
168
+ end
144
169
  end
145
170
  end
@@ -1,30 +1,19 @@
1
1
  module SpreeCmCommissioner
2
2
  module RouteOrderCountable
3
- def should_update_count?
3
+ def has_trip_ids? # rubocop:disable Naming/PredicateName
4
4
  preload_trip_ids.any?
5
5
  end
6
6
 
7
7
  def increment_route_fulfilled_order_count
8
- return unless should_update_count?
8
+ return unless has_trip_ids?
9
9
 
10
10
  SpreeCmCommissioner::Transit::RouteFulfilledOrderCountIncrementerJob.perform_later(order_id: id)
11
11
  end
12
12
 
13
13
  def increment_route_order_count
14
- return unless should_update_count?
14
+ return unless has_trip_ids?
15
15
 
16
16
  SpreeCmCommissioner::Transit::RouteOrderCountIncrementerJob.perform_later(order_id: id)
17
17
  end
18
-
19
- # Calling `.trip_ids` directly can cause many slow database queries (N+1 problem)
20
- # every time `.should_update_trip_ids?` or `.preload_trip_ids` runs.
21
- # To avoid this, we store a precomputed list of trip IDs in `private_metadata`.
22
- def preload_trip_ids=(preload_trip_ids = [])
23
- self.private_metadata = (private_metadata || {}).merge('preload_trip_ids' => preload_trip_ids)
24
- end
25
-
26
- def preload_trip_ids
27
- private_metadata&.fetch('preload_trip_ids', []) || []
28
- end
29
18
  end
30
19
  end
@@ -29,10 +29,18 @@ module SpreeCmCommissioner
29
29
  :bib_pre_generation_on_create?,
30
30
  :seat_number_positions,
31
31
  :seat_number_layouts,
32
+ :color,
33
+ :ticket_type,
32
34
  :seat_type,
33
35
  to: :options
34
36
  end
35
37
 
38
+ # Override variant.rb to return cached formatted options text, avoiding repeated database queries.
39
+ # Falls back to database queries & computing the format if preload data is unavailable.
40
+ def options_text
41
+ @options_text ||= public_metadata[:preload_options_text].presence || Spree::Variants::VisableOptionsPresenter.new(self).to_sentence
42
+ end
43
+
36
44
  def options
37
45
  @options ||= VariantOptions.new(self)
38
46
  end
@@ -87,17 +95,23 @@ module SpreeCmCommissioner
87
95
  options.payment_option == 'post-paid'
88
96
  end
89
97
 
90
- # save optins to public_metadata so we don't have to query option types & option values when needed them.
91
- # once variant changed, we update metadata.
92
98
  def set_options_to_public_metadata
93
99
  self.public_metadata ||= {}
94
100
 
95
- latest_options_in_hash = option_values.each_with_object({}) do |option_value, hash|
101
+ # Cache option values as a hash to avoid N+1 queries when accessing options.
102
+ # Stores {option_type_name => option_value_name} pairs that can be retrieved
103
+ # without loading option_types and option_values associations.
104
+ # Example: "Red, 256GB" - formatted options for quick display
105
+ self.public_metadata[:cm_options] = option_values.each_with_object({}) do |option_value, hash|
96
106
  option_type_name = option_value.option_type.name
97
107
  hash[option_type_name] = find_option_value_name_for(option_type_name: option_type_name)
98
108
  end
99
109
 
100
- self.public_metadata[:cm_options] = latest_options_in_hash
110
+ # Cache formatted options text for quick retrieval via #options_text.
111
+ # Precomputes the human-readable format (e.g., "Red, 256GB") to avoid
112
+ # repeated formatting and association queries.
113
+ # Example: {"color" => "red", "storage" => "256GB"} - option_type_name => option_value_name pairs
114
+ self.public_metadata[:preload_options_text] = Spree::Variants::VisableOptionsPresenter.new(self).to_sentence
101
115
  end
102
116
 
103
117
  def set_options_to_public_metadata!
@@ -25,6 +25,8 @@ module SpreeCmCommissioner
25
25
  validates :y, presence: true, numericality: true
26
26
  validates :rotation, presence: true, numericality: { greater_than_or_equal_to: -360, less_than_or_equal_to: 360 }
27
27
 
28
+ validate :cannot_unassign_variant_with_active_blocks
29
+
28
30
  before_validation :assign_layout_from_section, if: -> { seat_layout.nil? && seat_section.present? }
29
31
 
30
32
  def label_required?
@@ -50,5 +52,22 @@ module SpreeCmCommissioner
50
52
  def assign_layout_from_section
51
53
  self.seat_layout = seat_section.seat_layout
52
54
  end
55
+
56
+ private
57
+
58
+ def cannot_unassign_variant_with_active_blocks
59
+ return if variant_id_was.blank? || variant_id.present?
60
+
61
+ active_blocks = all_reserved_blocks.reserved_or_on_hold
62
+ return unless active_blocks.exists?
63
+
64
+ block_count = active_blocks.count
65
+ block_statuses = active_blocks.pluck(:status).uniq.map { |s| s.humanize.downcase }
66
+
67
+ errors.add(
68
+ :variant,
69
+ "cannot be unassigned because #{block_count} #{block_count == 1 ? 'block is' : 'blocks are'} #{block_statuses.join(' and ')} by guests"
70
+ )
71
+ end
53
72
  end
54
73
  end
@@ -22,6 +22,11 @@ module SpreeCmCommissioner
22
22
  base.has_many :pending_guests, pending_guests_query, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
23
23
  base.has_many :product_completion_steps, class_name: 'SpreeCmCommissioner::ProductCompletionStep', through: :product
24
24
 
25
+ base.has_many :saved_guests,
26
+ through: :guests,
27
+ source: :saved_guest,
28
+ class_name: 'SpreeCmCommissioner::SavedGuest'
29
+
25
30
  base.before_validation -> { self.product_type = variant.product_type || product.product_type }, if: -> { product_type.nil? }
26
31
 
27
32
  base.before_save :update_vendor_id
@@ -7,6 +7,7 @@ module SpreeCmCommissioner
7
7
  base.include SpreeCmCommissioner::Metafield
8
8
  base.include SpreeCmCommissioner::TenantUpdatable
9
9
  base.include SpreeCmCommissioner::ServiceType
10
+ base.include SpreeCmCommissioner::ServiceRecommendations
10
11
 
11
12
  base.has_many :variant_kind_option_types, -> { where(kind: :variant).order(:position) },
12
13
  through: :product_option_types, source: :option_type
@@ -68,11 +68,6 @@ module SpreeCmCommissioner
68
68
  )
69
69
  end
70
70
 
71
- # override
72
- def options_text
73
- @options_text ||= Spree::Variants::VisableOptionsPresenter.new(self).to_sentence
74
- end
75
-
76
71
  def selected_option_value_ids
77
72
  option_value_variants.pluck(:option_value_id)
78
73
  end
@@ -139,6 +139,14 @@ module SpreeCmCommissioner
139
139
  @seat_number_layouts ||= option_value_name_for(option_type_name: 'seat-number-layouts')&.split(',')
140
140
  end
141
141
 
142
+ def color
143
+ @color ||= option_value_name_for(option_type_name: 'color')
144
+ end
145
+
146
+ def ticket_type
147
+ @ticket_type ||= option_value_name_for(option_type_name: 'ticket-type')
148
+ end
149
+
142
150
  def seat_type
143
151
  @seat_type ||= option_value_name_for(option_type_name: 'seat-type')
144
152
  end
@@ -16,6 +16,8 @@ module SpreeCmCommissioner
16
16
 
17
17
  base.before_save :generate_code, if: :code.nil?
18
18
 
19
+ base.after_create :send_telegram_vendor_creation_alert
20
+
19
21
  base.has_many :photos, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::VendorPhoto'
20
22
  base.has_many :option_values, through: :products
21
23
  base.has_many :vendor_option_types, class_name: 'SpreeCmCommissioner::VendorOptionType'
@@ -201,6 +203,10 @@ module SpreeCmCommissioner
201
203
  vendor_kind_option_values.where(option_type_id: rules_option_type.id)
202
204
  end
203
205
 
206
+ def send_telegram_vendor_creation_alert
207
+ VendorCreationTelegramAlertSenderJob.perform_later(id)
208
+ end
209
+
204
210
  delegate :present?, to: :tenant, prefix: true
205
211
  end
206
212
  end
@@ -1,6 +1,7 @@
1
1
  <!-- insert_bottom "[data-hook='admin_variant_form_additional_fields']" -->
2
2
 
3
- <div class="form-group">
3
+ <%# deprecated no longer used, should be removed entirely along with table column, just hide for now. %>
4
+ <div class="form-group d-none">
4
5
  <%= f.field_container :permanent_stock do %>
5
6
  <%= f.label :permanent_stock, Spree.t(:permanent_stock) %>
6
7
  <%= f.number_field :permanent_stock, class: 'form-control' %>
@@ -25,6 +25,8 @@ module Spree
25
25
 
26
26
  base.has_many :guests, serializer: SpreeCmCommissioner::V2::Storefront::GuestSerializer
27
27
 
28
+ base.has_many :saved_guests, serializer: SpreeCmCommissioner::V2::Storefront::SavedGuestSerializer
29
+
28
30
  base.has_many :pending_guests, serializer: SpreeCmCommissioner::V2::Storefront::GuestSerializer
29
31
 
30
32
  base.has_one :product, serializer: Spree::V2::Storefront::ProductSerializer
@@ -0,0 +1,12 @@
1
+ module SpreeCmCommissioner
2
+ module V2
3
+ module Storefront
4
+ class IntercityTaxiCartSerializer < Spree::V2::Storefront::CartSerializer
5
+ has_many :line_items,
6
+ serializer: SpreeCmCommissioner::V2::Storefront::IntercityTaxiLineItemSerializer
7
+
8
+ cache_options store: nil
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module SpreeCmCommissioner
2
+ module V2
3
+ module Storefront
4
+ class IntercityTaxiLineItemSerializer < BaseSerializer
5
+ attributes :pickup_place_name,
6
+ :drop_off_place_name,
7
+ :pickup_lat,
8
+ :pickup_lng,
9
+ :drop_off_lat,
10
+ :drop_off_lng,
11
+ :passenger_count,
12
+ :distance_km,
13
+ :ordered_points,
14
+ :base_km,
15
+ :detour_pickup_km,
16
+ :detour_dropoff_km,
17
+ :extra_pickup_km,
18
+ :extra_dropoff_km,
19
+ :extra_pickup_charge_usd,
20
+ :extra_dropoff_charge_usd,
21
+ :estimated_time_minutes,
22
+ :from_date,
23
+ :to_date,
24
+ :direction,
25
+ :trip_id
26
+
27
+ cache_options store: nil
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,6 +4,17 @@ module SpreeCmCommissioner
4
4
  class TripResultSerializer < BaseSerializer
5
5
  set_type :trip_result
6
6
 
7
+ # Enable serializer-level caching
8
+ # Longer TTL than controller cache (5min) because:
9
+ # - Individual trips change less frequently
10
+ # - Same trip can be reused across multiple searches
11
+ # - Cache invalidation handled by cache_key_with_version
12
+ cache_options(
13
+ store: Rails.cache,
14
+ namespace: 'trip-result-serializer',
15
+ expires_in: 30.minutes
16
+ )
17
+
7
18
  attributes :origin_place, :destination_place, :boarding, :drop_off, :price, :compare_at_amount, :currency, :quantity_available, :max_capacity,
8
19
  :allow_seat_selection, :departure_time, :route_type, :distance
9
20
  attribute :duration_in_hms, &:duration_in_hms
@@ -0,0 +1,67 @@
1
+ module SpreeCmCommissioner
2
+ module IntercityTaxiOrder
3
+ class Create
4
+ attr_reader :trip_id, :from_date, :to_date, :user_id, :quantity
5
+
6
+ def self.call(trip_id:, from_date:, to_date:, user_id: nil, quantity: 1)
7
+ new(
8
+ trip_id: trip_id,
9
+ from_date: from_date,
10
+ to_date: to_date,
11
+ user_id: user_id,
12
+ quantity: quantity
13
+ ).call
14
+ end
15
+
16
+ def initialize(trip_id:, from_date:, to_date:, user_id:, quantity:)
17
+ @trip_id = trip_id
18
+ @from_date = from_date
19
+ @to_date = to_date
20
+ @user_id = user_id
21
+ @quantity = quantity.to_i <= 0 ? 1 : quantity.to_i
22
+ end
23
+
24
+ def call
25
+ validate!
26
+
27
+ ActiveRecord::Base.transaction do
28
+ trip = SpreeCmCommissioner::Trip.find(trip_id)
29
+ variant = trip.product.variants.first
30
+ raise ArgumentError, 'No variant found for trip' if variant.blank?
31
+
32
+ order = Spree::Order.new(state: 'cart', use_billing: true, user_id: user_id)
33
+
34
+ line_item = order.line_items.new(
35
+ variant_id: variant.id,
36
+ quantity: quantity,
37
+ from_date: from_date,
38
+ to_date: to_date,
39
+ private_metadata: {
40
+ trip_id: trip.id
41
+ }
42
+ )
43
+
44
+ build_guests_for!(line_item, quantity)
45
+
46
+ order.save!
47
+ order.update_with_updater!
48
+
49
+ order
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def validate!
56
+ raise ArgumentError, 'trip_id is required' if trip_id.blank?
57
+ raise ArgumentError, 'from_date is required' if from_date.blank?
58
+ raise ArgumentError, 'to_date is required' if to_date.blank?
59
+ raise ArgumentError, 'to_date must be after from_date' if from_date.present? && to_date.present? && to_date < from_date
60
+ end
61
+
62
+ def build_guests_for!(line_item, qty)
63
+ Array.new(qty) { line_item.guests.new }
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,79 @@
1
+ module SpreeCmCommissioner
2
+ module IntercityTaxiOrder
3
+ class Update
4
+ attr_reader :order_number, :params
5
+
6
+ def self.call(order_number:, params:)
7
+ new(order_number: order_number, params: params).call
8
+ end
9
+
10
+ def initialize(order_number:, params:)
11
+ @order_number = order_number
12
+ @params = ensure_parameters(params)
13
+ end
14
+
15
+ def call
16
+ order = Spree::Order.find_by!(number: order_number)
17
+ order.update!(order_params)
18
+
19
+ create_billing_address_if_missing(order)
20
+
21
+ order
22
+ end
23
+
24
+ private
25
+
26
+ def ensure_parameters(params)
27
+ return params if params.is_a?(::ActionController::Parameters)
28
+
29
+ ::ActionController::Parameters.new(params)
30
+ end
31
+
32
+ def create_billing_address_if_missing(order)
33
+ return if order.bill_address.present?
34
+
35
+ SpreeCmCommissioner::BillingAddressCreator.call(order: order)
36
+ end
37
+
38
+ def order_params
39
+ params.require(:order).permit(
40
+ :intel_phone_number,
41
+ :email,
42
+ line_items_attributes: [
43
+ :id,
44
+ :pickup_place_name,
45
+ :drop_off_place_name,
46
+ :from_date,
47
+ :pickup_lat,
48
+ :pickup_lng,
49
+ :pickup_oob_confirmed,
50
+ :drop_off_lat,
51
+ :drop_off_lng,
52
+ :drop_off_oob_confirmed,
53
+ :distance_km,
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,
63
+ :remark,
64
+ :passenger_count,
65
+ { guests_attributes: %i[
66
+ id
67
+ first_name
68
+ last_name
69
+ gender
70
+ nationality_id
71
+ age
72
+ ]
73
+ }
74
+ ]
75
+ )
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
- module Transit
3
- class BaseRouteOrderMetricsUpdater
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
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
- module Transit
3
- class RoutePreviousTripCountDecrementerService
2
+ module Routes
3
+ class DecrementPreviousTripCount
4
4
  prepend ::Spree::ServiceModule::Base
5
5
 
6
6
  def call(previous_route_id:)
@@ -12,7 +12,7 @@ module SpreeCmCommissioner
12
12
  # Build a trip-like object that responds to :route and :vendor so the delegated
13
13
  # service can locate vendor routes correctly.
14
14
  trip_like = Struct.new(:route, :vendor).new(previous_route, previous_route.vendors.first)
15
- result = SpreeCmCommissioner::Transit::RouteTripCountDecrementerService.call(trip: trip_like)
15
+ result = SpreeCmCommissioner::Routes::DecrementTripCount.call(trip: trip_like)
16
16
  return result if result.failure?
17
17
  else
18
18
  previous_route.with_lock do
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
- module Transit
3
- class RouteTripCountDecrementerService
2
+ module Routes
3
+ class DecrementTripCount
4
4
  prepend ::Spree::ServiceModule::Base
5
5
 
6
6
  def call(trip:)
@@ -1,12 +1,12 @@
1
1
  module SpreeCmCommissioner
2
- module Transit
3
- class RouteFulfilledOrderCountIncrementerService
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::Transit::BaseRouteOrderMetricsUpdater
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 Transit
3
- class RouteOrderCountIncrementerService
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::Transit::BaseRouteOrderMetricsUpdater
9
+ SpreeCmCommissioner::Routes::BaseUpdateOrderMetrics
10
10
  .new(order: order, attribute: :order_count)
11
11
  .call
12
12
 
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
- module Transit
3
- class RouteTripCountIncrementerService
2
+ module Routes
3
+ class IncrementTripCount
4
4
  prepend ::Spree::ServiceModule::Base
5
5
 
6
6
  def call(trip:)
@@ -0,0 +1,65 @@
1
+ module SpreeCmCommissioner
2
+ module Trips
3
+ class Search
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ REFRESH_RATE_LIMIT_INTERVAL = 30.seconds
7
+ SEARCH_CACHE_TTL = 5.minutes
8
+
9
+ def self.cache_key_for(params, vendor)
10
+ new.send(:cache_key, params, vendor)
11
+ end
12
+
13
+ def call(params:, vendor:, is_intercity_taxi: false)
14
+ return success(results: []) unless valid_search?(params)
15
+
16
+ cache_key = cache_key(params, vendor)
17
+
18
+ results = Rails.cache.fetch(cache_key, expires_in: SEARCH_CACHE_TTL) do
19
+ trips = SpreeCmCommissioner::TripQuery.new(
20
+ origin_id: origin_id(params),
21
+ destination_id: destination_id(params),
22
+ vendor_id: vendor.id,
23
+ number_of_guests: params[:number_of_guests] || 1,
24
+ date: search_date(params).to_date
25
+ ).call
26
+
27
+ is_intercity_taxi || taxi?(params) ? filter_intercity(trips) : trips
28
+ end
29
+
30
+ success(results: results)
31
+ rescue StandardError => e
32
+ failure(nil, e.message)
33
+ end
34
+
35
+ private
36
+
37
+ def valid_search?(params)
38
+ params[:origin].present? && params[:destination].present? && params[:outbound_date].present?
39
+ end
40
+
41
+ def filter_intercity(results)
42
+ Array(results).select do |res|
43
+ res.respond_to?(:trips) && res.trips.any? { |t| t.route_type.to_s == 'intercity_taxi' }
44
+ end
45
+ end
46
+
47
+ def origin_id(params) = outbound?(params) ? params[:origin] : params[:destination]
48
+ def destination_id(params) = outbound?(params) ? params[:destination] : params[:origin]
49
+ def search_date(params) = outbound?(params) ? params[:outbound_date] : params[:inbound_date]
50
+ def outbound?(params) = params[:direction] == 'outbound' || params[:direction].blank?
51
+ def taxi?(params) = params[:service_type] == 'intercity_taxi'
52
+
53
+ def cache_key(params, vendor)
54
+ [
55
+ 'search_trips',
56
+ "vendor:#{vendor.id}",
57
+ "dir:#{params[:direction] || 'outbound'}",
58
+ "o:#{origin_id(params)}",
59
+ "d:#{destination_id(params)}",
60
+ "date:#{search_date(params)}"
61
+ ].join(':')
62
+ end
63
+ end
64
+ end
65
+ end