spree_cm_commissioner 2.5.1.pre.pre3 → 2.5.1.pre.pre4

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_build_gem.yml +2 -2
  3. data/Gemfile.lock +1 -1
  4. data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +2 -2
  5. data/app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb +4 -4
  6. data/app/controllers/spree/api/v2/tenant/popular_route_places_controller.rb +60 -0
  7. data/app/controllers/spree/api/v2/tenant/routes_controller.rb +50 -0
  8. data/app/controllers/spree/api/v2/tenant/transit/draft_orders_controller.rb +46 -0
  9. data/app/controllers/spree/transit/trips_controller.rb +3 -3
  10. data/app/finders/spree_cm_commissioner/places/find_with_route.rb +12 -12
  11. data/app/finders/spree_cm_commissioner/route_metrics/find_popular.rb +44 -0
  12. data/app/finders/spree_cm_commissioner/routes/find.rb +94 -0
  13. data/app/finders/spree_cm_commissioner/routes/find_popular.rb +19 -35
  14. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +11 -4
  15. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +10 -1
  16. data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +4 -3
  17. data/app/interactors/spree_cm_commissioner/trip_stops_creator.rb +2 -2
  18. data/app/jobs/spree_cm_commissioner/route_metrics/decrease_trip_count_job.rb +10 -0
  19. data/app/jobs/spree_cm_commissioner/route_metrics/increase_fulfilled_order_count_job.rb +10 -0
  20. data/app/jobs/spree_cm_commissioner/route_metrics/increase_order_count_job.rb +10 -0
  21. data/app/jobs/spree_cm_commissioner/route_metrics/increase_trip_count_job.rb +10 -0
  22. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +2 -2
  23. data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +2 -2
  24. data/app/models/spree_cm_commissioner/place.rb +5 -8
  25. data/app/models/spree_cm_commissioner/product_decorator.rb +1 -0
  26. data/app/models/spree_cm_commissioner/route.rb +45 -5
  27. data/app/models/spree_cm_commissioner/route_metric.rb +21 -0
  28. data/app/models/spree_cm_commissioner/route_photo.rb +12 -0
  29. data/app/models/spree_cm_commissioner/trip.rb +8 -33
  30. data/app/models/spree_cm_commissioner/trip_stop.rb +16 -2
  31. data/app/models/spree_cm_commissioner/vendor_decorator.rb +3 -1
  32. data/app/queries/spree_cm_commissioner/trip_query.rb +2 -2
  33. data/app/serializers/spree/v2/tenant/transit_cart_serializer.rb +11 -0
  34. data/app/serializers/spree_cm_commissioner/v2/storefront/route_metric_serializer.rb +13 -0
  35. data/app/serializers/spree_cm_commissioner/v2/storefront/route_serializer.rb +2 -1
  36. data/app/serializers/spree_cm_commissioner/v2/storefront/transit_line_item_serializer.rb +17 -0
  37. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +1 -1
  38. data/app/services/spree_cm_commissioner/route_metrics/decrease_trip_count.rb +31 -0
  39. data/app/services/spree_cm_commissioner/{routes/increment_fulfilled_order_count.rb → route_metrics/increase_fulfilled_order_count.rb} +3 -3
  40. data/app/services/spree_cm_commissioner/{routes/increment_order_count.rb → route_metrics/increase_order_count.rb} +3 -3
  41. data/app/services/spree_cm_commissioner/route_metrics/increase_trip_count.rb +31 -0
  42. data/app/services/spree_cm_commissioner/{routes/base_update_order_metrics.rb → route_metrics/update_route_metrics.rb} +11 -16
  43. data/app/services/spree_cm_commissioner/routes/create.rb +51 -0
  44. data/app/services/spree_cm_commissioner/routes/update.rb +25 -0
  45. data/app/{interactors/spree_cm_commissioner/transit/draft_order_creator.rb → services/spree_cm_commissioner/transit_order/create.rb} +13 -16
  46. data/app/services/spree_cm_commissioner/trips/create_single_leg.rb +123 -0
  47. data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +54 -0
  48. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +88 -0
  49. data/app/services/spree_cm_commissioner/trips/variants/create.rb +103 -0
  50. data/app/views/spree/transit/trip_stops/index.html.erb +4 -2
  51. data/app/views/spree_cm_commissioner/guest_mailer/send_ticket_to_guest.html.erb +0 -1
  52. data/config/initializers/spree_permitted_attributes.rb +11 -0
  53. data/config/routes.rb +6 -0
  54. data/db/migrate/20251224033103_migrate_cm_routes_to_cm_route_metrics.rb +17 -0
  55. data/db/migrate/20251224033910_migrate_cm_vendor_routes_to_cm_routes.rb +30 -0
  56. data/db/migrate/20260105072450_migrate_cm_trip_stops_to_support_trip_connection.rb +12 -0
  57. data/db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb +5 -0
  58. data/lib/spree_cm_commissioner/test_helper/factories/route_factory.rb +7 -6
  59. data/lib/spree_cm_commissioner/test_helper/factories/route_metric_factory.rb +12 -0
  60. data/lib/spree_cm_commissioner/test_helper/factories/route_photo_factory.rb +5 -0
  61. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +4 -1
  62. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +3 -1
  63. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +2 -0
  64. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +22 -0
  65. data/lib/spree_cm_commissioner/transit/route_stop.rb +61 -0
  66. data/lib/spree_cm_commissioner/transit/route_stop_collection.rb +175 -0
  67. data/lib/spree_cm_commissioner/transit/trip_form.rb +81 -0
  68. data/lib/spree_cm_commissioner/transit/trip_stop_form.rb +61 -0
  69. data/lib/spree_cm_commissioner/version.rb +1 -1
  70. data/lib/spree_cm_commissioner.rb +4 -0
  71. metadata +37 -17
  72. data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +0 -10
  73. data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +0 -10
  74. data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +0 -13
  75. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +0 -10
  76. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +0 -10
  77. data/app/models/concerns/spree_cm_commissioner/route_trip_count_callbacks.rb +0 -48
  78. data/app/models/spree_cm_commissioner/trip_connection.rb +0 -36
  79. data/app/models/spree_cm_commissioner/vendor_route.rb +0 -9
  80. data/app/services/spree_cm_commissioner/routes/decrement_previous_trip_count.rb +0 -30
  81. data/app/services/spree_cm_commissioner/routes/decrement_trip_count.rb +0 -33
  82. data/app/services/spree_cm_commissioner/routes/increment_trip_count.rb +0 -33
  83. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +0 -6
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module RouteMetrics
3
+ class IncreaseTripCountJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ trip = SpreeCmCommissioner::Trip.find(options[:trip_id])
6
+ SpreeCmCommissioner::RouteMetrics::IncreaseTripCount.call(trip: trip)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,8 +1,8 @@
1
1
  module SpreeCmCommissioner
2
2
  module Stock
3
3
  class PermanentInventoryItemsGeneratorJob < ApplicationUniqueJob
4
- def perform
5
- SpreeCmCommissioner::Stock::PermanentInventoryItemsGenerator.call
4
+ def perform(options = {})
5
+ SpreeCmCommissioner::Stock::PermanentInventoryItemsGenerator.call(options)
6
6
  end
7
7
  end
8
8
  end
@@ -7,13 +7,13 @@ module SpreeCmCommissioner
7
7
  def increment_route_fulfilled_order_count
8
8
  return unless has_trip_ids?
9
9
 
10
- SpreeCmCommissioner::Transit::RouteFulfilledOrderCountIncrementerJob.perform_later(order_id: id)
10
+ SpreeCmCommissioner::RouteMetrics::IncreaseFulfilledOrderCountJob.perform_later(order_id: id)
11
11
  end
12
12
 
13
13
  def increment_route_order_count
14
14
  return unless has_trip_ids?
15
15
 
16
- SpreeCmCommissioner::Transit::RouteOrderCountIncrementerJob.perform_later(order_id: id)
16
+ SpreeCmCommissioner::RouteMetrics::IncreaseOrderCountJob.perform_later(order_id: id)
17
17
  end
18
18
  end
19
19
  end
@@ -21,15 +21,12 @@ module SpreeCmCommissioner
21
21
  has_many :taxon_places, class_name: 'SpreeCmCommissioner::TaxonPlace', dependent: :destroy
22
22
  has_many :taxons, through: :taxon_places
23
23
 
24
- has_many :routes_as_origin, class_name: 'SpreeCmCommissioner::Route', foreign_key: :origin_place_id
25
- has_many :routes_as_destination, class_name: 'SpreeCmCommissioner::Route', foreign_key: :destination_place_id
24
+ has_many :route_metrics_as_origin, class_name: 'SpreeCmCommissioner::RouteMetric', foreign_key: :origin_place_id
25
+ has_many :route_metrics_as_destination, class_name: 'SpreeCmCommissioner::RouteMetric', foreign_key: :destination_place_id
26
26
 
27
- has_many :trips_as_origin, through: :routes_as_origin, source: :trips
28
- has_many :trips_as_destination, through: :routes_as_destination, source: :trips
29
-
30
- # Scopes for route-based filtering
31
- scope :with_routes_as_origin, -> { joins(:routes_as_origin).distinct }
32
- scope :with_routes_as_destination, -> { joins(:routes_as_destination).distinct }
27
+ # Scopes for route metric filtering
28
+ scope :with_route_metrics_as_origin, -> { joins(:route_metrics_as_origin).distinct }
29
+ scope :with_route_metrics_as_destination, -> { joins(:route_metrics_as_destination).distinct }
33
30
 
34
31
  def self.ransackable_attributes(auth_object = nil)
35
32
  super & %w[name code]
@@ -48,6 +48,7 @@ module SpreeCmCommissioner
48
48
  base.accepts_nested_attributes_for :product_places, allow_destroy: true
49
49
 
50
50
  base.has_one :trip, class_name: 'SpreeCmCommissioner::Trip', dependent: :destroy
51
+ base.has_one :service_calendar, as: :calendarable, class_name: 'SpreeCmCommissioner::ServiceCalendar', dependent: :destroy
51
52
 
52
53
  base.belongs_to :event, class_name: 'Spree::Taxon', optional: true
53
54
 
@@ -1,12 +1,52 @@
1
1
  module SpreeCmCommissioner
2
2
  class Route < Base
3
- has_many :vendor_routes, class_name: 'SpreeCmCommissioner::VendorRoute', dependent: :destroy
4
- has_many :vendors, through: :vendor_routes
3
+ include SpreeCmCommissioner::RouteType
4
+
5
+ belongs_to :vendor, class_name: 'Spree::Vendor', optional: false
6
+ belongs_to :tenant, class_name: 'SpreeCmCommissioner::Tenant', optional: true
7
+
8
+ belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place', optional: false
9
+ belongs_to :destination_place, class_name: 'SpreeCmCommissioner::Place', optional: false
10
+
11
+ has_one :primary_photo, -> { order(position: :asc) }, class_name: 'SpreeCmCommissioner::RoutePhoto',
12
+ as: :viewable,
13
+ dependent: :destroy
14
+ has_many :route_photos, class_name: 'SpreeCmCommissioner::RoutePhoto', as: :viewable, dependent: :destroy
15
+
5
16
  has_many :trips, inverse_of: :route
6
17
 
7
- belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place'
8
- belongs_to :destination_place, class_name: 'SpreeCmCommissioner::Place'
18
+ validates :route_name, presence: true
19
+
20
+ validate :validate_route_stops
21
+
22
+ delegate :multi_leg?, to: :route_stops
23
+
24
+ def self.ransackable_attributes(_auth_object = nil)
25
+ %w[route_type short_name route_name]
26
+ end
27
+
28
+ def route_stops
29
+ Transit::RouteStopCollection.from_hash(read_attribute(:route_stops))
30
+ end
31
+
32
+ def route_stops=(value)
33
+ if value.is_a?(Transit::RouteStopCollection)
34
+ @route_stops = value
35
+ self[:route_stops] = value.to_h
36
+ elsif value.is_a?(Array)
37
+ @route_stops = Transit::RouteStopCollection.from_hash(value)
38
+ self[:route_stops] = @route_stops.to_h
39
+ else
40
+ raise ArgumentError, 'route_stops must be an Array or RouteStopCollection'
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def validate_route_stops
47
+ return if route_stops.valid?
9
48
 
10
- validates :origin_place_id, uniqueness: { scope: :destination_place_id }
49
+ route_stops.errors.each { |error| errors.add(:route_stops, error) }
50
+ end
11
51
  end
12
52
  end
@@ -0,0 +1,21 @@
1
+ module SpreeCmCommissioner
2
+ class RouteMetric < Base
3
+ include SpreeCmCommissioner::RouteType
4
+
5
+ belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place', optional: false
6
+ belongs_to :destination_place, class_name: 'SpreeCmCommissioner::Place', optional: false
7
+
8
+ validates :route_type, presence: true
9
+ validates :route_type, uniqueness: { scope: %i[origin_place_id destination_place_id] }
10
+
11
+ validate :origin_and_destination_cannot_be_the_same
12
+
13
+ private
14
+
15
+ def origin_and_destination_cannot_be_the_same
16
+ return unless origin_place_id == destination_place_id
17
+
18
+ errors.add(:base, 'Origin and destination cannot be the same')
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ module SpreeCmCommissioner
2
+ class RoutePhoto < Asset
3
+ # 16x9
4
+ def asset_styles
5
+ {
6
+ mini: '160x90>',
7
+ small: '480x270>',
8
+ medium: '960x540>'
9
+ }
10
+ end
11
+ end
12
+ end
@@ -2,7 +2,6 @@ module SpreeCmCommissioner
2
2
  class Trip < Base
3
3
  include SpreeCmCommissioner::StoreMetadata
4
4
  include SpreeCmCommissioner::RouteType
5
- include SpreeCmCommissioner::RouteTripCountCallbacks
6
5
 
7
6
  attr_accessor :hours, :minutes, :seconds
8
7
 
@@ -15,7 +14,9 @@ module SpreeCmCommissioner
15
14
 
16
15
  # before create, duplicate seat layout from vehicle_type if present
17
16
  before_validation :duplicate_seat_layout_from_vehicle, if: -> { seat_layout.nil? && vehicle_type&.seat_layout.present? }
18
- before_validation :assign_route_and_vendor_route, if: :changed_route_attributes?
17
+
18
+ after_create :increment_route_metric_trip_count
19
+ before_destroy :decrement_route_metric_trip_count
19
20
 
20
21
  belongs_to :vendor, class_name: 'Spree::Vendor', inverse_of: :trips, optional: false
21
22
  belongs_to :product, class_name: 'Spree::Product', inverse_of: :trip, optional: false
@@ -26,7 +27,7 @@ module SpreeCmCommissioner
26
27
 
27
28
  belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place', optional: false
28
29
  belongs_to :destination_place, class_name: 'SpreeCmCommissioner::Place', optional: false
29
- belongs_to :route, inverse_of: :trips
30
+ belongs_to :route, class_name: 'SpreeCmCommissioner::Route', inverse_of: :trips, optional: true
30
31
 
31
32
  has_many :trip_blazer_queries, as: :queryable, class_name: 'SpreeCmCommissioner::BlazerQueryable'
32
33
  has_many :blazer_queries, through: :trip_blazer_queries, source: :blazer_query, class_name: 'Blazer::Query'
@@ -48,10 +49,6 @@ module SpreeCmCommissioner
48
49
  self.whitelisted_ransackable_associations = %w[product vehicle vehicle_type]
49
50
  self.whitelisted_ransackable_attributes = %w[origin_place_id destination_place_id]
50
51
 
51
- def changed_route_attributes?
52
- origin_place_id_changed? || destination_place_id_changed? || vendor_id_changed?
53
- end
54
-
55
52
  def convert_duration_to_seconds
56
53
  return if hours.blank? && minutes.blank? && seconds.blank?
57
54
 
@@ -104,34 +101,12 @@ module SpreeCmCommissioner
104
101
  )
105
102
  end
106
103
 
107
- # Check if any of the route context attributes are changing
108
- def will_route_context_change?
109
- # Returns true if any of the route context attributes will change on save.
110
- will_save_change_to_route_id? ||
111
- will_save_change_to_origin_place_id? ||
112
- will_save_change_to_destination_place_id? ||
113
- will_save_change_to_vendor_id?
114
- end
115
-
116
- def changed_route_context?
117
- saved_change_to_route_id? ||
118
- saved_change_to_origin_place_id? ||
119
- saved_change_to_destination_place_id? ||
120
- saved_change_to_vendor_id?
104
+ def increment_route_metric_trip_count
105
+ SpreeCmCommissioner::RouteMetrics::IncreaseTripCountJob.perform_later(trip_id: id)
121
106
  end
122
107
 
123
- # Find or create the Route for this trip (by origin/destination) and
124
- # assign it to the trip. Also populate a default route_name from the
125
- # product when the route is new/blank.
126
- def assign_route_and_vendor_route
127
- route = SpreeCmCommissioner::Route.find_or_create_by!(
128
- origin_place_id: origin_place_id,
129
- destination_place_id: destination_place_id
130
- )
131
- route.route_name = product.name if route.route_name.blank?
132
- route.save! if route.changed?
133
-
134
- self.route_id = route.id
108
+ def decrement_route_metric_trip_count
109
+ SpreeCmCommissioner::RouteMetrics::DecreaseTripCountJob.perform_later(trip_id: id)
135
110
  end
136
111
  end
137
112
  end
@@ -1,25 +1,39 @@
1
1
  module SpreeCmCommissioner
2
2
  class TripStop < Base
3
3
  acts_as_list column: :sequence, scope: :trip_id
4
- enum :stop_type, { boarding: 0, drop_off: 1 }
5
4
 
6
5
  belongs_to :trip, class_name: 'SpreeCmCommissioner::Trip', optional: false
7
6
  belongs_to :stop_place, class_name: 'SpreeCmCommissioner::Place', optional: false
8
7
  belongs_to :location_place, class_name: 'SpreeCmCommissioner::Place', optional: false
9
8
 
9
+ belongs_to :board_to_trip, class_name: 'SpreeCmCommissioner::Trip', optional: true
10
+
10
11
  before_validation :set_stop_name
12
+
11
13
  validates :stop_place_id, uniqueness: { scope: :trip_id }
14
+ validate :boarding_trip_requires_allow_boarding
15
+
16
+ scope :boarding, -> { where(allow_boarding: true) }
17
+ scope :drop_off, -> { where(allow_drop_off: true) }
12
18
 
13
19
  def set_stop_name
14
20
  self.stop_name = stop_place.name if stop_place.present?
15
21
  end
16
22
 
17
23
  def self.ransackable_attributes(_auth_object = nil)
18
- %w[stop_name stop_type]
24
+ %w[stop_name allow_boarding allow_drop_off]
19
25
  end
20
26
 
21
27
  def self.ransackable_associations(_auth_object = nil)
22
28
  ['stop_place']
23
29
  end
30
+
31
+ private
32
+
33
+ def boarding_trip_requires_allow_boarding
34
+ return unless board_to_trip_id.present? && !allow_boarding?
35
+
36
+ errors.add(:board_to_trip, 'requires allow_boarding to be true')
37
+ end
24
38
  end
25
39
  end
@@ -82,7 +82,9 @@ module SpreeCmCommissioner
82
82
  base.has_many :subscriptions, through: :customers, class_name: 'SpreeCmCommissioner::Subscription'
83
83
  base.has_many :subscription_orders, through: :subscriptions, class_name: 'Spree::Order', source: :orders
84
84
  base.has_many :promotion_categories, class_name: 'Spree::PromotionCategory', foreign_key: :vendor_id, dependent: :destroy
85
- base.has_many :vendor_routes, class_name: 'SpreeCmCommissioner::VendorRoute'
85
+ base.has_many :routes, class_name: 'SpreeCmCommissioner::Route', dependent: :destroy
86
+ base.has_many :popular_routes, -> { order(fulfilled_order_count: :desc, order_count: :desc) },
87
+ class_name: 'SpreeCmCommissioner::Route'
86
88
 
87
89
  base.has_many :homepage_section_relatables,
88
90
  as: :relatable,
@@ -77,8 +77,8 @@ module SpreeCmCommissioner
77
77
 
78
78
  scope
79
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
80
+ INNER JOIN cm_trip_stops AS boarding ON boarding.trip_id = cm_trips.id AND boarding.allow_boarding = true
81
+ INNER JOIN cm_trip_stops AS drop_off ON drop_off.trip_id = cm_trips.id AND drop_off.allow_drop_off = true
82
82
  INNER JOIN cm_places AS origin_places ON origin_places.id = cm_trips.origin_place_id
83
83
  INNER JOIN cm_places AS dest_places ON dest_places.id = cm_trips.destination_place_id
84
84
  INNER JOIN spree_variants AS master ON master.product_id = cm_trips.product_id AND master.is_master = true
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module V2
3
+ module Tenant
4
+ class TransitCartSerializer < CartSerializer
5
+ has_many :line_items, serializer: SpreeCmCommissioner::V2::Storefront::TransitLineItemSerializer
6
+
7
+ cache_options store: nil
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module SpreeCmCommissioner
2
+ module V2
3
+ module Storefront
4
+ class RouteMetricSerializer < BaseSerializer
5
+ set_type :route
6
+ attributes :route_name
7
+
8
+ has_one :origin_place, serializer: ::Spree::V2::Storefront::PlaceSerializer
9
+ has_one :destination_place, serializer: ::Spree::V2::Storefront::PlaceSerializer
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,8 +2,9 @@ module SpreeCmCommissioner
2
2
  module V2
3
3
  module Storefront
4
4
  class RouteSerializer < BaseSerializer
5
- attributes :route_name
5
+ attributes :route_name, :route_type, :short_name
6
6
 
7
+ has_many :route_photos, serializer: ::SpreeCmCommissioner::V2::Storefront::AssetSerializer
7
8
  has_one :origin_place, serializer: ::Spree::V2::Storefront::PlaceSerializer
8
9
  has_one :destination_place, serializer: ::Spree::V2::Storefront::PlaceSerializer
9
10
  end
@@ -0,0 +1,17 @@
1
+ module SpreeCmCommissioner
2
+ module V2
3
+ module Storefront
4
+ class TransitLineItemSerializer < BaseSerializer
5
+ # Nested-only API shape
6
+ attributes :from_date,
7
+ :to_date,
8
+ :trip_id,
9
+ :direction,
10
+ :boarding_trip_stop_id,
11
+ :drop_off_trip_stop_id
12
+
13
+ cache_options store: nil
14
+ end
15
+ end
16
+ end
17
+ end
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
2
2
  module V2
3
3
  module Storefront
4
4
  class TripStopSerializer < BaseSerializer
5
- attributes :id, :stop_type, :stop_name, :arrival_time, :departure_time
5
+ attributes :id, :allow_boarding, :allow_drop_off, :stop_name, :arrival_time, :departure_time
6
6
 
7
7
  has_one :stop_place, serializer: ::SpreeCmCommissioner::V2::Storefront::TripPlaceSerializer
8
8
  end
@@ -0,0 +1,31 @@
1
+ module SpreeCmCommissioner
2
+ module RouteMetrics
3
+ class DecreaseTripCount
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(trip:)
7
+ return failure(nil, 'Trip not found') unless trip
8
+
9
+ route_metric = find_or_create_route_metric(trip)
10
+
11
+ route_metric.with_lock do
12
+ route_metric.update!(trip_count: [route_metric.trip_count - 1, 0].max)
13
+ end
14
+
15
+ success(trip: trip)
16
+ rescue StandardError => e
17
+ failure(nil, e.message)
18
+ end
19
+
20
+ private
21
+
22
+ def find_or_create_route_metric(trip)
23
+ SpreeCmCommissioner::RouteMetric.find_or_create_by!(
24
+ origin_place_id: trip.origin_place_id,
25
+ destination_place_id: trip.destination_place_id,
26
+ route_type: trip.route_type
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,12 +1,12 @@
1
1
  module SpreeCmCommissioner
2
- module Routes
3
- class IncrementFulfilledOrderCount
2
+ module RouteMetrics
3
+ class IncreaseFulfilledOrderCount
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::Routes::BaseUpdateOrderMetrics
9
+ SpreeCmCommissioner::RouteMetrics::UpdateRouteMetrics
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 Routes
3
- class IncrementOrderCount
2
+ module RouteMetrics
3
+ class IncreaseOrderCount
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::Routes::BaseUpdateOrderMetrics
9
+ SpreeCmCommissioner::RouteMetrics::UpdateRouteMetrics
10
10
  .new(order: order, attribute: :order_count)
11
11
  .call
12
12
 
@@ -0,0 +1,31 @@
1
+ module SpreeCmCommissioner
2
+ module RouteMetrics
3
+ class IncreaseTripCount
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(trip:)
7
+ return failure(nil, 'Trip not found') unless trip
8
+
9
+ route_metric = find_or_create_route_metric(trip)
10
+
11
+ route_metric.with_lock do
12
+ route_metric.update!(trip_count: route_metric.trip_count + 1)
13
+ end
14
+
15
+ success(trip: trip)
16
+ rescue StandardError => e
17
+ failure(nil, e.message)
18
+ end
19
+
20
+ private
21
+
22
+ def find_or_create_route_metric(trip)
23
+ SpreeCmCommissioner::RouteMetric.find_or_create_by!(
24
+ origin_place_id: trip.origin_place_id,
25
+ destination_place_id: trip.destination_place_id,
26
+ route_type: trip.route_type
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
- module Routes
3
- class BaseUpdateOrderMetrics
2
+ module RouteMetrics
3
+ class UpdateRouteMetrics
4
4
  attr_reader :order, :attribute
5
5
 
6
6
  SUPPORTED_ATTRIBUTES = %i[order_count fulfilled_order_count].freeze
@@ -31,26 +31,21 @@ module SpreeCmCommissioner
31
31
  trip = product&.trip
32
32
  return unless trip
33
33
 
34
- route = find_or_create_route_for_trip(trip)
35
- ensure_trip_route(trip, route)
36
- increment_route_by(route, line_item.quantity)
34
+ route_metric = find_or_create_route_metric_for_trip(trip)
35
+ increment_route_by(route_metric, line_item.quantity)
36
+
37
+ route = trip.route
38
+ increment_route_by(route, line_item.quantity) if route
37
39
  end
38
40
 
39
- def find_or_create_route_for_trip(trip)
40
- SpreeCmCommissioner::Route.find_or_create_by(
41
+ def find_or_create_route_metric_for_trip(trip)
42
+ SpreeCmCommissioner::RouteMetric.find_or_create_by(
41
43
  origin_place_id: trip.origin_place_id,
42
- destination_place_id: trip.destination_place_id
44
+ destination_place_id: trip.destination_place_id,
45
+ route_type: trip.route_type
43
46
  )
44
47
  end
45
48
 
46
- def ensure_trip_route(trip, route)
47
- trip_route_id = trip.respond_to?(:route_id) ? trip.route_id : nil
48
-
49
- return unless trip.respond_to?(:update_column) && trip_route_id != route.id
50
-
51
- trip.update_column(:route_id, route.id) # rubocop:disable Rails/SkipsModelValidations
52
- end
53
-
54
49
  def increment_route_by(route, qty)
55
50
  route.with_lock do
56
51
  current_value = route.public_send(attribute) || 0
@@ -0,0 +1,51 @@
1
+ module SpreeCmCommissioner
2
+ module Routes
3
+ class Create
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ # @param vendor [Spree::Vendor]
7
+ # @param route_params [Hash]
8
+ # @param route_stops [SpreeCmCommissioner::Transit::RouteStopCollection]
9
+ def call(vendor:, route_params:, route_stops:)
10
+ # vendor_id, tenant_id, origin_place_id, destination_place_id are excluded and set automatically.
11
+ attrs = ::Spree::PermittedAttributes.route_attributes
12
+ .index_with { |attr_key| route_params[attr_key] }
13
+ .except(:vendor_id, :tenant_id, :route_stops, :origin_place_id, :destination_place_id)
14
+ .compact
15
+
16
+ return failure(nil, 'Route stops are required') if route_stops.length < 2
17
+
18
+ origin_place = find_place_for(route_stops, route_stops.first)
19
+ destination_place = find_place_for(route_stops, route_stops.last)
20
+
21
+ return failure(nil, 'Origin or destination place could not be determined') if origin_place.nil? || destination_place.nil?
22
+ return failure(nil, 'Route with origin & destination already exists') if vendor.routes.exists?(
23
+ origin_place_id: origin_place.id,
24
+ destination_place_id: destination_place.id
25
+ )
26
+
27
+ attrs = attrs.merge(
28
+ tenant_id: vendor.tenant_id,
29
+ route_name: attrs[:route_name] || "#{origin_place.name} -> #{destination_place.name}",
30
+ origin_place_id: origin_place.id,
31
+ destination_place_id: destination_place.id,
32
+ route_stops: route_stops
33
+ )
34
+
35
+ route = vendor.routes.new(attrs)
36
+ if route.save
37
+ success(route: route)
38
+ else
39
+ failure(route.errors)
40
+ end
41
+ end
42
+
43
+ def find_place_for(route_stops, route_stop)
44
+ return nil if route_stop.nil?
45
+
46
+ @locations ||= SpreeCmCommissioner::VendorPlace.where(id: route_stops.map(&:location_id).uniq).index_by(&:id)
47
+ @locations[route_stop.location_id]&.place
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ module SpreeCmCommissioner
2
+ module Routes
3
+ class Update
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(vendor:, route:, route_params:)
7
+ # vendor_id, tenant_id, origin_place_id, destination_place_id are excluded and set automatically.
8
+ # route_stops is not updateable.
9
+ attrs = ::Spree::PermittedAttributes.route_attributes
10
+ .index_with { |attr_key| route_params[attr_key] }
11
+ .except(:vendor_id, :tenant_id, :route_stops, :origin_place_id, :destination_place_id)
12
+ .compact
13
+
14
+ route = vendor.routes.find_by(id: route.id)
15
+ return failure(nil, :route_not_found) if route.nil?
16
+
17
+ if route.update(attrs)
18
+ success(route: route)
19
+ else
20
+ failure(route.errors)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,4 @@
1
- # DraftOrderCreator creates a new order for a entire journey, including outbound and (if present) inbound directions.
1
+ # SpreeCmCommissioner::TransitOrder::Create creates a new order for a entire journey, including outbound and (if present) inbound directions.
2
2
  #
3
3
  # Attributes:
4
4
  # - outbound_date: [Date] The date of outbound transit
@@ -12,25 +12,22 @@
12
12
  #
13
13
  # Note: inbound_legs and inbound_date are optional; the order may represent a one-way (outbound only) journey.
14
14
  module SpreeCmCommissioner
15
- module Transit
16
- class DraftOrderCreator < BaseInteractor
17
- delegate :outbound_date,
18
- :inbound_date,
19
- :outbound_legs,
20
- :inbound_legs,
21
- :user, to: :context
15
+ module TransitOrder
16
+ class Create
17
+ prepend ::Spree::ServiceModule::Base
22
18
 
23
- def call
24
- return context.fail!(message: 'Outbound legs are missing') if outbound_legs.blank?
19
+ def call(outbound_date:, inbound_date:, outbound_legs:, inbound_legs: [], user: nil)
20
+ return failure(nil, 'Outbound legs are missing') if outbound_legs.blank?
25
21
 
26
22
  begin
27
- context.order = create_order!
23
+ order = create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user)
24
+ success(order: order)
28
25
  rescue StandardError => e
29
- context.fail!(message: e.message)
26
+ failure(nil, e.message)
30
27
  end
31
28
  end
32
29
 
33
- def create_order!
30
+ def create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user)
34
31
  order = Spree::Order.new(state: 'cart', use_billing: true, user: user)
35
32
 
36
33
  outbound_line_items = build_line_items_for_legs!(order: order, legs: outbound_legs, initial_date: outbound_date)
@@ -112,10 +109,10 @@ module SpreeCmCommissioner
112
109
 
113
110
  def insert_saved_guests_per_line_items_leg(line_items)
114
111
  line_items.flat_map(&:guests).each_with_index do |guest, index|
115
- context.saved_guests ||= []
116
- context.saved_guests << SpreeCmCommissioner::SavedGuest.new if context.saved_guests[index].blank?
112
+ @saved_guests ||= []
113
+ @saved_guests << SpreeCmCommissioner::SavedGuest.new if @saved_guests[index].blank?
117
114
 
118
- guest.saved_guest = context.saved_guests[index]
115
+ guest.saved_guest = @saved_guests[index]
119
116
  end
120
117
 
121
118
  line_items