spree_cm_commissioner 2.5.1.pre.pre5 → 2.5.1

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/transit/trips_controller.rb +3 -3
  7. data/app/finders/spree_cm_commissioner/places/find_with_route.rb +12 -12
  8. data/app/finders/spree_cm_commissioner/routes/find_popular.rb +35 -19
  9. data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +4 -11
  10. data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +1 -10
  11. data/app/{services/spree_cm_commissioner/transit_order/create.rb → interactors/spree_cm_commissioner/transit/draft_order_creator.rb} +16 -13
  12. data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +3 -4
  13. data/app/interactors/spree_cm_commissioner/trip_stops_creator.rb +2 -2
  14. data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +2 -2
  15. data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +10 -0
  16. data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +10 -0
  17. data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +13 -0
  18. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +10 -0
  19. data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +10 -0
  20. data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +2 -2
  21. data/app/models/concerns/spree_cm_commissioner/route_trip_count_callbacks.rb +48 -0
  22. data/app/models/spree_cm_commissioner/place.rb +8 -5
  23. data/app/models/spree_cm_commissioner/product_decorator.rb +0 -1
  24. data/app/models/spree_cm_commissioner/route.rb +5 -45
  25. data/app/models/spree_cm_commissioner/trip.rb +33 -8
  26. data/app/models/spree_cm_commissioner/trip_connection.rb +36 -0
  27. data/app/models/spree_cm_commissioner/trip_stop.rb +2 -16
  28. data/app/models/spree_cm_commissioner/vendor_decorator.rb +1 -3
  29. data/app/models/spree_cm_commissioner/vendor_route.rb +9 -0
  30. data/app/queries/spree_cm_commissioner/trip_query.rb +2 -2
  31. data/app/serializers/spree_cm_commissioner/v2/storefront/route_serializer.rb +1 -2
  32. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +1 -1
  33. data/app/services/spree_cm_commissioner/{route_metrics/update_route_metrics.rb → routes/base_update_order_metrics.rb} +16 -11
  34. data/app/services/spree_cm_commissioner/routes/decrement_previous_trip_count.rb +30 -0
  35. data/app/services/spree_cm_commissioner/routes/decrement_trip_count.rb +33 -0
  36. data/app/services/spree_cm_commissioner/{route_metrics/increase_fulfilled_order_count.rb → routes/increment_fulfilled_order_count.rb} +3 -3
  37. data/app/services/spree_cm_commissioner/{route_metrics/increase_order_count.rb → routes/increment_order_count.rb} +3 -3
  38. data/app/services/spree_cm_commissioner/routes/increment_trip_count.rb +33 -0
  39. data/app/views/spree/transit/trip_stops/index.html.erb +2 -4
  40. data/app/views/spree_cm_commissioner/guest_mailer/send_ticket_to_guest.html.erb +1 -0
  41. data/config/initializers/spree_permitted_attributes.rb +0 -11
  42. data/config/routes.rb +0 -6
  43. data/lib/spree_cm_commissioner/test_helper/factories/route_factory.rb +6 -7
  44. data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +6 -0
  45. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +1 -4
  46. data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +1 -3
  47. data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +0 -2
  48. data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +0 -22
  49. data/lib/spree_cm_commissioner/version.rb +1 -1
  50. data/lib/spree_cm_commissioner.rb +0 -4
  51. metadata +19 -39
  52. data/app/controllers/spree/api/v2/tenant/popular_route_places_controller.rb +0 -60
  53. data/app/controllers/spree/api/v2/tenant/routes_controller.rb +0 -50
  54. data/app/controllers/spree/api/v2/tenant/transit/draft_orders_controller.rb +0 -46
  55. data/app/finders/spree_cm_commissioner/route_metrics/find_popular.rb +0 -44
  56. data/app/finders/spree_cm_commissioner/routes/find.rb +0 -94
  57. data/app/jobs/spree_cm_commissioner/route_metrics/decrease_trip_count_job.rb +0 -10
  58. data/app/jobs/spree_cm_commissioner/route_metrics/increase_fulfilled_order_count_job.rb +0 -10
  59. data/app/jobs/spree_cm_commissioner/route_metrics/increase_order_count_job.rb +0 -10
  60. data/app/jobs/spree_cm_commissioner/route_metrics/increase_trip_count_job.rb +0 -10
  61. data/app/models/spree_cm_commissioner/route_metric.rb +0 -21
  62. data/app/models/spree_cm_commissioner/route_photo.rb +0 -12
  63. data/app/serializers/spree/v2/tenant/transit_cart_serializer.rb +0 -11
  64. data/app/serializers/spree_cm_commissioner/v2/storefront/route_metric_serializer.rb +0 -13
  65. data/app/serializers/spree_cm_commissioner/v2/storefront/transit_line_item_serializer.rb +0 -17
  66. data/app/services/spree_cm_commissioner/route_metrics/decrease_trip_count.rb +0 -31
  67. data/app/services/spree_cm_commissioner/route_metrics/increase_trip_count.rb +0 -31
  68. data/app/services/spree_cm_commissioner/routes/create.rb +0 -51
  69. data/app/services/spree_cm_commissioner/routes/update.rb +0 -25
  70. data/app/services/spree_cm_commissioner/trips/create_single_leg.rb +0 -128
  71. data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +0 -54
  72. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +0 -88
  73. data/app/services/spree_cm_commissioner/trips/variants/create.rb +0 -103
  74. data/db/migrate/20251224033103_migrate_cm_routes_to_cm_route_metrics.rb +0 -17
  75. data/db/migrate/20251224033910_migrate_cm_vendor_routes_to_cm_routes.rb +0 -30
  76. data/db/migrate/20260105072450_migrate_cm_trip_stops_to_support_trip_connection.rb +0 -12
  77. data/db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb +0 -5
  78. data/lib/spree_cm_commissioner/test_helper/factories/route_metric_factory.rb +0 -12
  79. data/lib/spree_cm_commissioner/test_helper/factories/route_photo_factory.rb +0 -5
  80. data/lib/spree_cm_commissioner/transit/route_stop.rb +0 -61
  81. data/lib/spree_cm_commissioner/transit/route_stop_collection.rb +0 -203
  82. data/lib/spree_cm_commissioner/transit/trip_form.rb +0 -105
  83. data/lib/spree_cm_commissioner/transit/trip_stop_form.rb +0 -67
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c04c1030a47ee44d31fbec31880fae41a769bcf41e7b700d9b35cbdceb71d36d
4
- data.tar.gz: 2d729e1df109b0e69ad397b3b80a8212162772caaf6bdaf19b25e06f55876ab5
3
+ metadata.gz: 5b7e70b6ad87db5a4bd6b3879754f7ce8a753e1323d07ac754adea1426ef74b4
4
+ data.tar.gz: 699aa89f466074b9d86b8b0ddf39a7a8bf41f6edb4dda640f647dd33651bc9eb
5
5
  SHA512:
6
- metadata.gz: 1d176ff6f8ab61f5b1e57983f864da966b0e90200bf1abc8cc6f57f69b25fdf8aa2d6a9eabfb8a23da96075a05082491e9be97611ba034675f91faf7c42a32d9
7
- data.tar.gz: 5e569ceb12a0d2e533957ca83679e3738720cdb48e64e25d584bb4bac3ef2f9ab9bfab3ae89d325a1e070e4dc53d1ff068843a99ed08e6867d4e45d851257092
6
+ metadata.gz: 9f60f4b8646f3537f616fe2d15b4f49b0bfa335acd1f61d4a97c6d25dc4a891ced787c0d2983f0dc327b2a125f643adebdc1a9ce59025436d74c507761a773f1
7
+ data.tar.gz: 18584fa23c8784b390a5b076fd7a855fb101db7c6bd5ea60159e6fdf6072fcac9441a9daddb2f9bc3f89351faf03b2792b6ef7864b63b081429752ce98d1f11a
@@ -45,7 +45,7 @@ jobs:
45
45
  const commits_url = pr.commits_url;
46
46
 
47
47
  const commits = await github.request(commits_url);
48
- const pattern = /^(Close flow|Close\s+#\d+\s.+|Merge.*)/;
48
+ const pattern = /^Close\s+#\d+\s.+/;
49
49
 
50
50
  let invalidCommits = [];
51
51
 
@@ -63,7 +63,7 @@ jobs:
63
63
  core.setFailed(
64
64
  `The following commit messages are not in the correct format:\n\n${invalidCommits.join(
65
65
  '\n'
66
- )}\n\nEach commit message must start with "Close #<issue_number> <message>" or "Merge pull request #<number>"`
66
+ )}\n\nEach commit message must start with "Close #<issue_number> <message>"`
67
67
  );
68
68
  } else {
69
69
  console.log("All commit messages are correctly formatted.");
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.5.1.pre.pre5)
37
+ spree_cm_commissioner (2.5.1)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -31,7 +31,7 @@ module Spree
31
31
 
32
32
  # override
33
33
  def collection_finder
34
- SpreeCmCommissioner::RouteMetrics::FindPopular
34
+ SpreeCmCommissioner::Routes::FindPopular
35
35
  end
36
36
 
37
37
  # override
@@ -41,7 +41,7 @@ module Spree
41
41
 
42
42
  # override
43
43
  def resource_serializer
44
- SpreeCmCommissioner::V2::Storefront::RouteMetricSerializer
44
+ SpreeCmCommissioner::V2::Storefront::RouteSerializer
45
45
  end
46
46
 
47
47
  # override
@@ -15,7 +15,7 @@ module Spree
15
15
  @outbound_legs = params[:outbound_legs].is_a?(Array) && params[:outbound_legs].any? ? build_legs(:outbound, params[:outbound_legs]) : []
16
16
  @inbound_legs = params[:inbound_legs].is_a?(Array) && params[:inbound_legs].any? ? build_legs(:inbound, params[:inbound_legs]) : []
17
17
 
18
- result = SpreeCmCommissioner::TransitOrder::Create.call(
18
+ context = SpreeCmCommissioner::Transit::DraftOrderCreator.call(
19
19
  outbound_date: params[:outbound_date]&.to_date,
20
20
  inbound_date: params[:inbound_date]&.to_date,
21
21
  outbound_legs: @outbound_legs,
@@ -23,10 +23,10 @@ module Spree
23
23
  user: spree_current_user
24
24
  )
25
25
 
26
- if result.success?
27
- render_serialized_payload { serialize_resource(result.value[:order]) }
26
+ if context.success?
27
+ render_serialized_payload { serialize_resource(context.order) }
28
28
  else
29
- render_error_payload(result.error)
29
+ render_error_payload(context.message)
30
30
  end
31
31
  end
32
32
 
@@ -48,13 +48,13 @@ module Spree
48
48
  boarding_points_attributes = (cm_params.delete('boarding_points') || []).compact_blank
49
49
  .map do |point_id|
50
50
  trip_stop = trip_stops.delete(point_id.to_i)
51
- { stop_id: point_id, allow_boarding: true, id: trip_stop.try(:id) }
51
+ { stop_id: point_id, stop_type: 'boarding', id: trip_stop.try(:id) }
52
52
  end
53
53
 
54
54
  drop_off_points_attributes = (cm_params.delete('drop_off_points') || []).compact_blank
55
55
  .map do |point_id|
56
56
  trip_stop = trip_stops.delete(point_id.to_i)
57
- { stop_id: point_id, allow_drop_off: true, id: trip_stop.try(:id) }
57
+ { stop_id: point_id, stop_type: 'drop_off', id: trip_stop.try(:id) }
58
58
  end
59
59
 
60
60
  trip_stops.each_value do |trip_stop|
@@ -74,7 +74,7 @@ module Spree
74
74
  :origin_id, :destination_id, :vehicle_id, :hours, :minutes, :seconds,
75
75
  'departure_time(1i)', 'departure_time(2i)', 'departure_time(3i)',
76
76
  'departure_time(4i)', 'departure_time(5i)', :product_id,
77
- trip_stops_attributes: %i[stop_id allow_boarding allow_drop_off _destroy id]
77
+ trip_stops_attributes: %i[stop_id stop_type _destroy id]
78
78
  )
79
79
  end
80
80
 
@@ -1,20 +1,20 @@
1
- # Finds places connected via route metrics with optional filtering.
1
+ # Finds places connected via routes with optional filtering.
2
2
  #
3
3
  # @param place_type [String] Required. 'origin' or 'destination'
4
4
  # @param place_id [Integer] Optional. Filter by connected place ID
5
5
  # @param query [String] Optional. Filter by place name (case-insensitive)
6
- # @param route_type [Symbol, String] Optional. Filter by route type from route metrics (e.g., :ferry, :bus)
6
+ # @param route_type [Symbol, String] Optional. Filter by route type from associated trips (e.g., :ferry, :bus)
7
7
  #
8
8
  # @return [ActiveRecord::Relation<SpreeCmCommissioner::Place>]
9
9
  #
10
10
  # Modes:
11
11
  # - place_id only: returns connected places (origins TO or destinations FROM given place)
12
12
  # - query only: filters by name
13
- # - route_type only: filters by route type on route metrics
13
+ # - route_type only: filters by trip route types on those routes
14
14
  # - combinations: connected places filtered by name and/or route type
15
- # - neither: all origins/destinations in route metrics
15
+ # - neither: all origins/destinations in routes
16
16
  #
17
- # @example Origins with ferry route metrics to place 123
17
+ # @example Origins with ferry trips to place 123
18
18
  # FindWithRoute.new(place_type: 'origin', place_id: 123, route_type: :ferry).execute
19
19
  #
20
20
  # @example Destinations filtered by query and route type
@@ -46,14 +46,14 @@ module SpreeCmCommissioner
46
46
  def scope
47
47
  return SpreeCmCommissioner::Place.none unless valid_place_type?
48
48
 
49
- base_scope = origin? ? SpreeCmCommissioner::Place.with_route_metrics_as_origin : SpreeCmCommissioner::Place.with_route_metrics_as_destination
49
+ base_scope = origin? ? SpreeCmCommissioner::Place.with_routes_as_origin : SpreeCmCommissioner::Place.with_routes_as_destination
50
50
 
51
51
  return base_scope if place_id.blank?
52
52
 
53
53
  if origin?
54
- base_scope.where(route_metrics_as_origin: { destination_place_id: place_id })
54
+ base_scope.where(routes_as_origin: { destination_place_id: place_id })
55
55
  else
56
- base_scope.where(route_metrics_as_destination: { origin_place_id: place_id })
56
+ base_scope.where(routes_as_destination: { origin_place_id: place_id })
57
57
  end
58
58
  end
59
59
 
@@ -62,10 +62,10 @@ module SpreeCmCommissioner
62
62
  end
63
63
 
64
64
  def apply_route_type_filter(result)
65
- # Use the association name as the table alias in the WHERE clause
66
- # This works whether the association is already joined or not
67
- association_name = origin? ? :route_metrics_as_origin : :route_metrics_as_destination
68
- result.where(association_name => { route_type: route_type.to_sym })
65
+ trips_association = origin? ? :trips_as_origin : :trips_as_destination
66
+ result.joins(trips_association)
67
+ .where(cm_trips: { route_type: route_type.to_sym })
68
+ .distinct
69
69
  end
70
70
 
71
71
  def origin?
@@ -1,30 +1,46 @@
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
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
6
23
 
7
24
  module SpreeCmCommissioner
8
25
  module Routes
9
26
  class FindPopular
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
15
- end
16
-
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)
27
+ def execute(route_type: nil, query: nil)
28
+ scope(route_type: route_type, query: query)
23
29
  end
24
30
 
25
31
  private
26
32
 
27
- attr_reader :vendor, :tenant, :route_type, :limit
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?
39
+
40
+ result = result.where('cm_routes.route_name ILIKE ?', "%#{query}%") if query.present?
41
+
42
+ result.distinct.order(fulfilled_order_count: :desc, order_count: :desc)
43
+ end
28
44
  end
29
45
  end
30
46
  end
@@ -33,18 +33,11 @@ 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.
37
36
  def inventory_dates_for(variant)
38
- calendar = variant.product&.service_calendar
37
+ start_date = Time.zone.tomorrow
38
+ end_date = Time.zone.today + pre_inventory_days_for(variant)
39
39
 
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
40
+ (start_date..end_date)
48
41
  end
49
42
 
50
43
  def inventory_exist?(variant, inventory_date)
@@ -75,7 +68,7 @@ module SpreeCmCommissioner
75
68
  def variants
76
69
  scope = Spree::Variant.active.with_permanent_stock.where(is_master: false)
77
70
  scope = scope.where(id: variant_ids) if variant_ids.present?
78
- scope.includes(product: :service_calendar)
71
+ scope
79
72
  end
80
73
  end
81
74
  end
@@ -8,8 +8,7 @@ module SpreeCmCommissioner
8
8
 
9
9
  return context.fail!(message: Spree.t(:doesnt_track_inventory)) unless variant.track_inventory?
10
10
 
11
- stock_location = resolve_stock_location(variant, stock_location_id)
12
-
11
+ stock_location = Spree::StockLocation.find(stock_location_id)
13
12
  stock_movement = stock_location.stock_movements.build(stock_movement_params)
14
13
  stock_movement.stock_item = stock_location.set_up_stock_item(variant)
15
14
 
@@ -23,14 +22,6 @@ module SpreeCmCommissioner
23
22
 
24
23
  private
25
24
 
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
-
34
25
  def adjust_inventory_items_async(variant_id, quantity)
35
26
  args = { variant_id: variant_id, quantity: quantity }
36
27
  CmAppLogger.log(label: "#{self.class.name}#adjust_inventory_items_async", data: args) do
@@ -1,4 +1,4 @@
1
- # SpreeCmCommissioner::TransitOrder::Create creates a new order for a entire journey, including outbound and (if present) inbound directions.
1
+ # DraftOrderCreator 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,22 +12,25 @@
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 TransitOrder
16
- class Create
17
- prepend ::Spree::ServiceModule::Base
15
+ module Transit
16
+ class DraftOrderCreator < BaseInteractor
17
+ delegate :outbound_date,
18
+ :inbound_date,
19
+ :outbound_legs,
20
+ :inbound_legs,
21
+ :user, to: :context
18
22
 
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?
23
+ def call
24
+ return context.fail!(message: 'Outbound legs are missing') if outbound_legs.blank?
21
25
 
22
26
  begin
23
- order = create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user)
24
- success(order: order)
27
+ context.order = create_order!
25
28
  rescue StandardError => e
26
- failure(nil, e.message)
29
+ context.fail!(message: e.message)
27
30
  end
28
31
  end
29
32
 
30
- def create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user)
33
+ def create_order!
31
34
  order = Spree::Order.new(state: 'cart', use_billing: true, user: user)
32
35
 
33
36
  outbound_line_items = build_line_items_for_legs!(order: order, legs: outbound_legs, initial_date: outbound_date)
@@ -109,10 +112,10 @@ module SpreeCmCommissioner
109
112
 
110
113
  def insert_saved_guests_per_line_items_leg(line_items)
111
114
  line_items.flat_map(&:guests).each_with_index do |guest, index|
112
- @saved_guests ||= []
113
- @saved_guests << SpreeCmCommissioner::SavedGuest.new if @saved_guests[index].blank?
115
+ context.saved_guests ||= []
116
+ context.saved_guests << SpreeCmCommissioner::SavedGuest.new if context.saved_guests[index].blank?
114
117
 
115
- guest.saved_guest = @saved_guests[index]
118
+ guest.saved_guest = context.saved_guests[index]
116
119
  end
117
120
 
118
121
  line_items
@@ -91,10 +91,9 @@ 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.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)
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
98
97
  cloned_stop.location_place_id = stop_params[:location_place_id].presence
99
98
  end
100
99
 
@@ -26,7 +26,7 @@ module SpreeCmCommissioner
26
26
  trip: trip,
27
27
  stop_place: origin_place,
28
28
  location_place: origin_place,
29
- allow_boarding: true
29
+ stop_type: :boarding
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
- allow_drop_off: true
45
+ stop_type: :drop_off
46
46
  }
47
47
 
48
48
  attributes[:arrival_time] = departure_time + duration_seconds.seconds
@@ -1,8 +1,8 @@
1
1
  module SpreeCmCommissioner
2
2
  module Stock
3
3
  class PermanentInventoryItemsGeneratorJob < ApplicationUniqueJob
4
- def perform(options = {})
5
- SpreeCmCommissioner::Stock::PermanentInventoryItemsGenerator.call(options)
4
+ def perform
5
+ SpreeCmCommissioner::Stock::PermanentInventoryItemsGenerator.call
6
6
  end
7
7
  end
8
8
  end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module Transit
3
+ class RouteFulfilledOrderCountIncrementerJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ order = Spree::Order.find(options[:order_id])
6
+ SpreeCmCommissioner::Routes::IncrementFulfilledOrderCount.call(order: order)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module Transit
3
+ class RouteOrderCountIncrementerJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ order = Spree::Order.find(options[:order_id])
6
+ SpreeCmCommissioner::Routes::IncrementOrderCount.call(order: order)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ # Perform the previous-route decrement outside the Trip transaction.
2
+
3
+ module SpreeCmCommissioner
4
+ module Transit
5
+ class RoutePreviousTripCountDecrementerJob < ApplicationUniqueJob
6
+ queue_as :default
7
+
8
+ def perform(options = {})
9
+ SpreeCmCommissioner::Routes::DecrementPreviousTripCount.call(previous_route_id: options[:previous_route_id])
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module Transit
3
+ class RouteTripCountDecrementerJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ trip = SpreeCmCommissioner::Trip.find(options[:trip_id])
6
+ SpreeCmCommissioner::Routes::DecrementTripCount.call(trip: trip)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeCmCommissioner
2
+ module Transit
3
+ class RouteTripCountIncrementerJob < ApplicationUniqueJob
4
+ def perform(options = {})
5
+ trip = SpreeCmCommissioner::Trip.find(options[:trip_id])
6
+ SpreeCmCommissioner::Routes::IncrementTripCount.call(trip: trip)
7
+ end
8
+ end
9
+ end
10
+ 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::RouteMetrics::IncreaseFulfilledOrderCountJob.perform_later(order_id: id)
10
+ SpreeCmCommissioner::Transit::RouteFulfilledOrderCountIncrementerJob.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::RouteMetrics::IncreaseOrderCountJob.perform_later(order_id: id)
16
+ SpreeCmCommissioner::Transit::RouteOrderCountIncrementerJob.perform_later(order_id: id)
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,48 @@
1
+ module SpreeCmCommissioner
2
+ module RouteTripCountCallbacks
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ after_create :increase_trip_count
7
+ before_destroy :decrease_trip_count
8
+
9
+ # When route context may change, enqueue an async job after commit that
10
+ # reads the previous values from `previous_changes` to decrement counts
11
+ # on the old route/vendor. This avoids keeping in-memory state and keeps
12
+ # the Trip update fast.
13
+ after_commit :enqueue_decrement_previous_route_trip_counts, on: :update
14
+
15
+ after_commit :increment_new_route_trip_counts, on: :update, if: :changed_route_context?
16
+ end
17
+
18
+ private
19
+
20
+ def decrease_trip_count
21
+ SpreeCmCommissioner::Transit::RouteTripCountDecrementerJob.perform_now(trip_id: id)
22
+ end
23
+
24
+ def increase_trip_count
25
+ SpreeCmCommissioner::Transit::RouteTripCountIncrementerJob.perform_later(trip_id: id)
26
+ end
27
+
28
+ # Read previous_changes to find prior route context and enqueue the job.
29
+ def enqueue_decrement_previous_route_trip_counts
30
+ prev_route_id = previous_changes['route_id']&.first
31
+ prev_origin_id = previous_changes['origin_place_id']&.first
32
+ prev_destination_id = previous_changes['destination_place_id']&.first
33
+
34
+ return unless prev_route_id.present? && prev_origin_id.present? && prev_destination_id.present?
35
+
36
+ SpreeCmCommissioner::Transit::RoutePreviousTripCountDecrementerJob.perform_later(
37
+ previous_route_id: prev_route_id
38
+ )
39
+ end
40
+
41
+ # Safely increment the trip_count for the trip's (new) route after update.
42
+ def increment_new_route_trip_counts
43
+ return if origin_place_id.blank? || destination_place_id.blank? || route_id.blank?
44
+
45
+ SpreeCmCommissioner::Transit::RouteTripCountIncrementerJob.perform_later(trip_id: id)
46
+ end
47
+ end
48
+ end
@@ -21,12 +21,15 @@ 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 :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
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
26
26
 
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 }
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 }
30
33
 
31
34
  def self.ransackable_attributes(auth_object = nil)
32
35
  super & %w[name code]
@@ -48,7 +48,6 @@ 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
52
51
 
53
52
  base.belongs_to :event, class_name: 'Spree::Taxon', optional: true
54
53
 
@@ -1,52 +1,12 @@
1
1
  module SpreeCmCommissioner
2
2
  class Route < Base
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
-
3
+ has_many :vendor_routes, class_name: 'SpreeCmCommissioner::VendorRoute', dependent: :destroy
4
+ has_many :vendors, through: :vendor_routes
16
5
  has_many :trips, inverse_of: :route
17
6
 
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?
7
+ belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place'
8
+ belongs_to :destination_place, class_name: 'SpreeCmCommissioner::Place'
48
9
 
49
- route_stops.errors.each { |error| errors.add(:route_stops, error) }
50
- end
10
+ validates :origin_place_id, uniqueness: { scope: :destination_place_id }
51
11
  end
52
12
  end