spree_cm_commissioner 2.5.1 → 2.5.2.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 (90) 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/admin/inventory_items_controller.rb +36 -56
  5. data/app/controllers/spree/admin/stock_managements_controller.rb +14 -3
  6. data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +2 -2
  7. data/app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb +4 -4
  8. data/app/controllers/spree/api/v2/tenant/popular_route_places_controller.rb +60 -0
  9. data/app/controllers/spree/api/v2/tenant/routes_controller.rb +50 -0
  10. data/app/controllers/spree/api/v2/tenant/transit/draft_orders_controller.rb +46 -0
  11. data/app/controllers/spree/transit/trips_controller.rb +3 -3
  12. data/app/finders/spree_cm_commissioner/places/find_with_route.rb +12 -12
  13. data/app/finders/spree_cm_commissioner/route_metrics/find_popular.rb +44 -0
  14. data/app/finders/spree_cm_commissioner/routes/find.rb +94 -0
  15. data/app/finders/spree_cm_commissioner/routes/find_popular.rb +19 -35
  16. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +11 -4
  17. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +10 -1
  18. data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +4 -3
  19. data/app/interactors/spree_cm_commissioner/trip_stops_creator.rb +2 -2
  20. data/app/jobs/spree_cm_commissioner/route_metrics/decrease_trip_count_job.rb +10 -0
  21. data/app/jobs/spree_cm_commissioner/route_metrics/increase_fulfilled_order_count_job.rb +10 -0
  22. data/app/jobs/spree_cm_commissioner/route_metrics/increase_order_count_job.rb +10 -0
  23. data/app/jobs/spree_cm_commissioner/route_metrics/increase_trip_count_job.rb +10 -0
  24. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +2 -2
  25. data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +2 -2
  26. data/app/models/spree_cm_commissioner/place.rb +5 -8
  27. data/app/models/spree_cm_commissioner/product_decorator.rb +1 -0
  28. data/app/models/spree_cm_commissioner/route.rb +45 -5
  29. data/app/models/spree_cm_commissioner/route_metric.rb +21 -0
  30. data/app/models/spree_cm_commissioner/route_photo.rb +12 -0
  31. data/app/models/spree_cm_commissioner/trip.rb +8 -33
  32. data/app/models/spree_cm_commissioner/trip_stop.rb +16 -2
  33. data/app/models/spree_cm_commissioner/vendor_decorator.rb +3 -1
  34. data/app/queries/spree_cm_commissioner/trip_query.rb +2 -2
  35. data/app/serializers/spree/v2/tenant/transit_cart_serializer.rb +11 -0
  36. data/app/serializers/spree_cm_commissioner/v2/storefront/route_metric_serializer.rb +13 -0
  37. data/app/serializers/spree_cm_commissioner/v2/storefront/route_serializer.rb +2 -1
  38. data/app/serializers/spree_cm_commissioner/v2/storefront/transit_line_item_serializer.rb +17 -0
  39. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +1 -1
  40. data/app/services/spree_cm_commissioner/route_metrics/decrease_trip_count.rb +31 -0
  41. data/app/services/spree_cm_commissioner/{routes/increment_fulfilled_order_count.rb → route_metrics/increase_fulfilled_order_count.rb} +3 -3
  42. data/app/services/spree_cm_commissioner/{routes/increment_order_count.rb → route_metrics/increase_order_count.rb} +3 -3
  43. data/app/services/spree_cm_commissioner/route_metrics/increase_trip_count.rb +31 -0
  44. data/app/services/spree_cm_commissioner/{routes/base_update_order_metrics.rb → route_metrics/update_route_metrics.rb} +11 -16
  45. data/app/services/spree_cm_commissioner/routes/create.rb +51 -0
  46. data/app/services/spree_cm_commissioner/routes/update.rb +25 -0
  47. data/app/{interactors/spree_cm_commissioner/transit/draft_order_creator.rb → services/spree_cm_commissioner/transit_order/create.rb} +13 -16
  48. data/app/services/spree_cm_commissioner/trips/create_single_leg.rb +123 -0
  49. data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +54 -0
  50. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +88 -0
  51. data/app/services/spree_cm_commissioner/trips/variants/create.rb +103 -0
  52. data/app/views/spree/admin/inventory_items/prices.html.erb +45 -0
  53. data/app/views/spree/admin/inventory_items/stocks.html.erb +36 -0
  54. data/app/views/spree/admin/stock_managements/calendar.html.erb +105 -12
  55. data/app/views/spree/admin/stock_managements/index.html.erb +9 -8
  56. data/app/views/spree/transit/trip_stops/index.html.erb +4 -2
  57. data/app/views/spree_cm_commissioner/guest_mailer/send_ticket_to_guest.html.erb +0 -1
  58. data/config/initializers/spree_permitted_attributes.rb +11 -0
  59. data/config/routes.rb +12 -7
  60. data/db/migrate/20251224033103_migrate_cm_routes_to_cm_route_metrics.rb +17 -0
  61. data/db/migrate/20251224033910_migrate_cm_vendor_routes_to_cm_routes.rb +30 -0
  62. data/db/migrate/20260105072450_migrate_cm_trip_stops_to_support_trip_connection.rb +12 -0
  63. data/db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb +5 -0
  64. data/lib/spree_cm_commissioner/test_helper/factories/route_factory.rb +7 -6
  65. data/lib/spree_cm_commissioner/test_helper/factories/route_metric_factory.rb +12 -0
  66. data/lib/spree_cm_commissioner/test_helper/factories/route_photo_factory.rb +5 -0
  67. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +4 -1
  68. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +3 -1
  69. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +2 -0
  70. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +22 -0
  71. data/lib/spree_cm_commissioner/transit/route_stop.rb +61 -0
  72. data/lib/spree_cm_commissioner/transit/route_stop_collection.rb +175 -0
  73. data/lib/spree_cm_commissioner/transit/trip_form.rb +81 -0
  74. data/lib/spree_cm_commissioner/transit/trip_stop_form.rb +65 -0
  75. data/lib/spree_cm_commissioner/version.rb +1 -1
  76. data/lib/spree_cm_commissioner.rb +4 -0
  77. metadata +42 -21
  78. data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +0 -10
  79. data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +0 -10
  80. data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +0 -13
  81. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +0 -10
  82. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +0 -10
  83. data/app/models/concerns/spree_cm_commissioner/route_trip_count_callbacks.rb +0 -48
  84. data/app/models/spree_cm_commissioner/trip_connection.rb +0 -36
  85. data/app/models/spree_cm_commissioner/vendor_route.rb +0 -9
  86. data/app/services/spree_cm_commissioner/routes/decrement_previous_trip_count.rb +0 -30
  87. data/app/services/spree_cm_commissioner/routes/decrement_trip_count.rb +0 -33
  88. data/app/services/spree_cm_commissioner/routes/increment_trip_count.rb +0 -33
  89. data/app/views/spree/admin/inventory_items/show.html.erb +0 -72
  90. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +0 -6
@@ -1,46 +1,30 @@
1
- # Finds routes ordered by popularity based on order metrics.
2
- #
3
- # Routes are sorted by:
4
- # 1. fulfilled_order_count (completed orders) - DESC
5
- # 2. order_count (total orders including incomplete) - DESC
6
- #
7
- # @param route_type [Symbol, String] Optional. Filter by route type from associated trips (e.g., :ferry, :bus)
8
- #
9
- # @return [ActiveRecord::Relation<SpreeCmCommissioner::Route>] Routes with origin and destination places loaded,
10
- # ordered from most to least popular
11
- #
12
- # @example Get all popular routes
13
- # finder = SpreeCmCommissioner::Routes::FindPopular.new
14
- # finder.execute # => Returns all routes sorted by fulfillment and order counts
15
- #
16
- # @example Get popular ferry routes
17
- # finder = SpreeCmCommissioner::Routes::FindPopular.new
18
- # finder.execute(route_type: :ferry) # => Returns ferry routes sorted by popularity
19
- #
20
- # @example Get popular bus routes
21
- # finder = SpreeCmCommissioner::Routes::FindPopular.new
22
- # finder.execute(route_type: :bus) # => Returns bus routes sorted by popularity
1
+ # Finder to get popular route metrics scoped to a vendor or tenant.
2
+ # Orders by fulfilled_order_count DESC, then order_count DESC.
3
+ # Usage:
4
+ # finder = SpreeCmCommissioner::Routes::FindPopular.new(vendor: vendor, route_type: :bus, limit: 10)
5
+ # finder.execute
23
6
 
24
7
  module SpreeCmCommissioner
25
8
  module Routes
26
9
  class FindPopular
27
- def execute(route_type: nil, query: nil)
28
- scope(route_type: route_type, query: query)
10
+ def initialize(vendor: nil, tenant: nil, route_type: nil, limit: 20)
11
+ @vendor = vendor
12
+ @tenant = tenant
13
+ @route_type = route_type
14
+ @limit = limit.to_i
29
15
  end
30
16
 
31
- private
32
-
33
- def scope(route_type:, query:)
34
- includes_associations = %i[origin_place destination_place]
35
- includes_associations << :trips if route_type.present?
36
- result = SpreeCmCommissioner::Route.includes(*includes_associations)
37
-
38
- result = result.joins(:trips).where(cm_trips: { route_type: route_type.to_sym }) if route_type.present?
17
+ def execute
18
+ scope = SpreeCmCommissioner::Route.includes(:origin_place, :destination_place, :route_photos)
19
+ scope = scope.where(vendor_id: vendor.id) if vendor.present?
20
+ scope = scope.where(tenant_id: tenant.id) if tenant.present?
21
+ scope = scope.where(route_type: route_type.to_s) if route_type.present?
22
+ scope.order(fulfilled_order_count: :desc, order_count: :desc).limit(limit)
23
+ end
39
24
 
40
- result = result.where('cm_routes.route_name ILIKE ?', "%#{query}%") if query.present?
25
+ private
41
26
 
42
- result.distinct.order(fulfilled_order_count: :desc, order_count: :desc)
43
- end
27
+ attr_reader :vendor, :tenant, :route_type, :limit
44
28
  end
45
29
  end
46
30
  end
@@ -33,11 +33,18 @@ module SpreeCmCommissioner
33
33
  end
34
34
  end
35
35
 
36
+ # Returns dates for which inventory should be generated, based on calendar or default period.
36
37
  def inventory_dates_for(variant)
37
- start_date = Time.zone.tomorrow
38
- end_date = Time.zone.today + pre_inventory_days_for(variant)
38
+ calendar = variant.product&.service_calendar
39
39
 
40
- (start_date..end_date)
40
+ if calendar.present?
41
+ calendar.start_date.upto(calendar.end_date).select { |date| calendar.service_available?(date) }
42
+ else
43
+ start_date = Time.zone.tomorrow
44
+ end_date = Time.zone.today + pre_inventory_days_for(variant)
45
+
46
+ start_date.upto(end_date).to_a
47
+ end
41
48
  end
42
49
 
43
50
  def inventory_exist?(variant, inventory_date)
@@ -68,7 +75,7 @@ module SpreeCmCommissioner
68
75
  def variants
69
76
  scope = Spree::Variant.active.with_permanent_stock.where(is_master: false)
70
77
  scope = scope.where(id: variant_ids) if variant_ids.present?
71
- scope
78
+ scope.includes(product: :service_calendar)
72
79
  end
73
80
  end
74
81
  end
@@ -8,7 +8,8 @@ module SpreeCmCommissioner
8
8
 
9
9
  return context.fail!(message: Spree.t(:doesnt_track_inventory)) unless variant.track_inventory?
10
10
 
11
- stock_location = Spree::StockLocation.find(stock_location_id)
11
+ stock_location = resolve_stock_location(variant, stock_location_id)
12
+
12
13
  stock_movement = stock_location.stock_movements.build(stock_movement_params)
13
14
  stock_movement.stock_item = stock_location.set_up_stock_item(variant)
14
15
 
@@ -22,6 +23,14 @@ module SpreeCmCommissioner
22
23
 
23
24
  private
24
25
 
26
+ def resolve_stock_location(variant, stock_location_id = nil)
27
+ if stock_location_id.present?
28
+ Spree::StockLocation.find(stock_location_id)
29
+ else
30
+ variant.vendor.stock_locations.first || variant.vendor.send(:create_stock_location)
31
+ end
32
+ end
33
+
25
34
  def adjust_inventory_items_async(variant_id, quantity)
26
35
  args = { variant_id: variant_id, quantity: quantity }
27
36
  CmAppLogger.log(label: "#{self.class.name}#adjust_inventory_items_async", data: args) do
@@ -91,9 +91,10 @@ module SpreeCmCommissioner
91
91
  stop_params = params.dig(:trip_stops_attributes, index.to_s)
92
92
  return unless stop_params
93
93
 
94
- cloned_stop.departure_time = parse_time(stop_params[:departure_time])
95
- cloned_stop.arrival_time = parse_time(stop_params[:arrival_time])
96
- cloned_stop.stop_type = stop_params[:stop_type].presence
94
+ cloned_stop.departure_time = parse_time(stop_params[:departure_time])
95
+ cloned_stop.arrival_time = parse_time(stop_params[:arrival_time])
96
+ cloned_stop.allow_boarding = stop_params[:allow_boarding] if stop_params.key?(:allow_boarding)
97
+ cloned_stop.allow_drop_off = stop_params[:allow_drop_off] if stop_params.key?(:allow_drop_off)
97
98
  cloned_stop.location_place_id = stop_params[:location_place_id].presence
98
99
  end
99
100
 
@@ -26,7 +26,7 @@ module SpreeCmCommissioner
26
26
  trip: trip,
27
27
  stop_place: origin_place,
28
28
  location_place: origin_place,
29
- stop_type: :boarding
29
+ allow_boarding: true
30
30
  }
31
31
 
32
32
  attributes[:departure_time] = departure_time
@@ -42,7 +42,7 @@ module SpreeCmCommissioner
42
42
  trip: trip,
43
43
  stop_place: destination_place,
44
44
  location_place: destination_place,
45
- stop_type: :drop_off
45
+ allow_drop_off: true
46
46
  }
47
47
 
48
48
  attributes[:arrival_time] = departure_time + duration_seconds.seconds
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module RouteMetrics
3
+ class DecreaseTripCountJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ trip = SpreeCmCommissioner::Trip.find(options[:trip_id])
6
+ SpreeCmCommissioner::RouteMetrics::DecreaseTripCount.call(trip: trip)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module RouteMetrics
3
+ class IncreaseFulfilledOrderCountJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ order = Spree::Order.find(options[:order_id])
6
+ SpreeCmCommissioner::RouteMetrics::IncreaseFulfilledOrderCount.call(order: order)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module RouteMetrics
3
+ class IncreaseOrderCountJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ order = Spree::Order.find(options[:order_id])
6
+ SpreeCmCommissioner::RouteMetrics::IncreaseOrderCount.call(order: order)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -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