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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ee953cf63ab26a979ddaa919500a3f7da95e812f8dc20557cb54411047821b6
4
- data.tar.gz: bb72deade157e1dde753706bc7a9ad4dbfd329ebb0fc7290074983bdc3d17eca
3
+ metadata.gz: 40605f05e2eabdbbf47121bbbfde8d0662afe833e7fb91803cadf1977c88fc8d
4
+ data.tar.gz: 025106d9fa95be2de1f1347b42b243a548c81ccc61efb5abec369fac2dd4bab1
5
5
  SHA512:
6
- metadata.gz: e4db3651d96a7e492cc13d3755ba0ec2f8474f49cbe5918e1af02855dba9d74786feacf7215806f3fb744919a710378ff25551829319ec96740d1d42f3d19d02
7
- data.tar.gz: 5f28bd001f3cc004058ccbdb253dd9b7614aa6ecdef0673da30ba03492abd900dfe3c48b2af18cfccd2c8f228c9753e0c9c3c51f896be6e13b5d6e23cc478c54
6
+ metadata.gz: 3c9d477987819fc303567b1320b5c9c6cf1653ede16caf754ae5a60f3795e2d977de9f57ca82d13cde43de9bddfece7815fcad6f330a656202f67458c501f17f
7
+ data.tar.gz: de1d93feeb60fc4ed36f11baf3f013a260c1c6d16a2322b4b7674f77d58844060b33b6b76d2e1d2698ffdfa1f28d47ee45c1863059f9fa7fd5e221fade8e414a
@@ -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\s+#\d+\s.+/;
48
+ const pattern = /^(Close flow|Close\s+#\d+\s.+|Merge.*)/;
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>"`
66
+ )}\n\nEach commit message must start with "Close #<issue_number> <message>" or "Merge pull request #<number>"`
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.pre3)
37
+ spree_cm_commissioner (2.5.1.pre.pre4)
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::Routes::FindPopular
34
+ SpreeCmCommissioner::RouteMetrics::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::RouteSerializer
44
+ SpreeCmCommissioner::V2::Storefront::RouteMetricSerializer
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
- context = SpreeCmCommissioner::Transit::DraftOrderCreator.call(
18
+ result = SpreeCmCommissioner::TransitOrder::Create.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 context.success?
27
- render_serialized_payload { serialize_resource(context.order) }
26
+ if result.success?
27
+ render_serialized_payload { serialize_resource(result.value[:order]) }
28
28
  else
29
- render_error_payload(context.message)
29
+ render_error_payload(result.error)
30
30
  end
31
31
  end
32
32
 
@@ -0,0 +1,60 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class PopularRoutePlacesController < BaseController
6
+ private
7
+
8
+ # override
9
+ def collection
10
+ @collection ||= collection_finder.new(
11
+ tenant: @tenant,
12
+ route_type: params[:route_type]
13
+ ).execute
14
+ end
15
+
16
+ # override
17
+ def default_resource_includes
18
+ %w[origin_place destination_place]
19
+ end
20
+
21
+ # override
22
+ def collection_finder
23
+ SpreeCmCommissioner::Routes::FindPopular
24
+ end
25
+
26
+ # override
27
+ def collection_serializer
28
+ resource_serializer
29
+ end
30
+
31
+ # override
32
+ def resource_serializer
33
+ SpreeCmCommissioner::V2::Storefront::RouteSerializer
34
+ end
35
+
36
+ # override
37
+ def serializer_params
38
+ super.merge(
39
+ include_vendors: include_vendors?,
40
+ include_nearby_places: include_nearby_places?
41
+ )
42
+ end
43
+
44
+ # override
45
+ def required_schema
46
+ SpreeCmCommissioner::PopularRoutePlacesRequestSchema
47
+ end
48
+
49
+ def include_vendors?
50
+ resource_includes.include?(:vendors) || false
51
+ end
52
+
53
+ def include_nearby_places?
54
+ resource_includes.include?(:nearby_places) || false
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,50 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class RoutesController < BaseController
6
+ private
7
+
8
+ # override
9
+ def collection
10
+ @collection ||= collection_finder.new(
11
+ tenant: @tenant,
12
+ params: params
13
+ ).execute
14
+ end
15
+
16
+ # override
17
+ def collection_finder
18
+ SpreeCmCommissioner::Routes::Find
19
+ end
20
+
21
+ # override
22
+ def collection_serializer
23
+ SpreeCmCommissioner::V2::Storefront::RouteSerializer
24
+ end
25
+
26
+ # override
27
+ def serializer_params
28
+ super.merge(
29
+ include_vendors: include_vendors?,
30
+ include_nearby_places: include_nearby_places?
31
+ )
32
+ end
33
+
34
+ # override
35
+ def required_schema
36
+ SpreeCmCommissioner::PopularRoutePlacesRequestSchema
37
+ end
38
+
39
+ def include_vendors?
40
+ resource_includes.include?(:vendors) || false
41
+ end
42
+
43
+ def include_nearby_places?
44
+ resource_includes.include?(:nearby_places) || false
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ module Transit
6
+ class DraftOrdersController < BaseController
7
+ # Creates a draft transit order for tenant.
8
+ # Endpoint: POST /api/v2/tenant/transit/draft_orders
9
+ # Params:
10
+ # - outbound_date: Date of outbound transit (optional)
11
+ # - inbound_date: Date of inbound transit (optional)
12
+ # - outbound_legs: Array of outbound leg details (required)
13
+ # - inbound_legs: Array of inbound leg details (optional)
14
+ def create
15
+ @outbound_legs = params[:outbound_legs].is_a?(Array) && params[:outbound_legs].any? ? build_legs(:outbound, params[:outbound_legs]) : []
16
+ @inbound_legs = params[:inbound_legs].is_a?(Array) && params[:inbound_legs].any? ? build_legs(:inbound, params[:inbound_legs]) : []
17
+
18
+ result = SpreeCmCommissioner::TransitOrder::Create.call(
19
+ outbound_date: params[:outbound_date]&.to_date,
20
+ inbound_date: params[:inbound_date]&.to_date,
21
+ outbound_legs: @outbound_legs,
22
+ inbound_legs: @inbound_legs,
23
+ user: spree_current_user
24
+ )
25
+
26
+ if result.success?
27
+ render_serialized_payload { serialize_resource(result.value[:order]) }
28
+ else
29
+ render_error_payload(result.error)
30
+ end
31
+ end
32
+
33
+ def build_legs(direction, legs_params)
34
+ SpreeCmCommissioner::Transit::LegsBuilderService.new(direction: direction, legs_params: legs_params).call
35
+ end
36
+
37
+ # override
38
+ def resource_serializer
39
+ Spree::V2::Tenant::TransitCartSerializer
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -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, stop_type: 'boarding', id: trip_stop.try(:id) }
51
+ { stop_id: point_id, allow_boarding: true, 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, stop_type: 'drop_off', id: trip_stop.try(:id) }
57
+ { stop_id: point_id, allow_drop_off: true, 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 stop_type _destroy id]
77
+ trip_stops_attributes: %i[stop_id allow_boarding allow_drop_off _destroy id]
78
78
  )
79
79
  end
80
80
 
@@ -1,20 +1,20 @@
1
- # Finds places connected via routes with optional filtering.
1
+ # Finds places connected via route metrics 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 associated trips (e.g., :ferry, :bus)
6
+ # @param route_type [Symbol, String] Optional. Filter by route type from route metrics (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 trip route types on those routes
13
+ # - route_type only: filters by route type on route metrics
14
14
  # - combinations: connected places filtered by name and/or route type
15
- # - neither: all origins/destinations in routes
15
+ # - neither: all origins/destinations in route metrics
16
16
  #
17
- # @example Origins with ferry trips to place 123
17
+ # @example Origins with ferry route metrics 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_routes_as_origin : SpreeCmCommissioner::Place.with_routes_as_destination
49
+ base_scope = origin? ? SpreeCmCommissioner::Place.with_route_metrics_as_origin : SpreeCmCommissioner::Place.with_route_metrics_as_destination
50
50
 
51
51
  return base_scope if place_id.blank?
52
52
 
53
53
  if origin?
54
- base_scope.where(routes_as_origin: { destination_place_id: place_id })
54
+ base_scope.where(route_metrics_as_origin: { destination_place_id: place_id })
55
55
  else
56
- base_scope.where(routes_as_destination: { origin_place_id: place_id })
56
+ base_scope.where(route_metrics_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
- 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
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 })
69
69
  end
70
70
 
71
71
  def origin?
@@ -0,0 +1,44 @@
1
+ # Finds route metrics ordered by popularity based on order metrics.
2
+ #
3
+ # Route metrics 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 (e.g., :ferry, :bus)
8
+ #
9
+ # @return [ActiveRecord::Relation<SpreeCmCommissioner::RouteMetric>] Route metrics with origin and destination places loaded,
10
+ # ordered from most to least popular
11
+ #
12
+ # @example Get all popular route metrics
13
+ # finder = SpreeCmCommissioner::RouteMetrics::FindPopular.new
14
+ # finder.execute # => Returns all route metrics sorted by fulfillment and order counts
15
+ #
16
+ # @example Get popular ferry route metrics
17
+ # finder = SpreeCmCommissioner::RouteMetrics::FindPopular.new
18
+ # finder.execute(route_type: :ferry) # => Returns ferry route metrics sorted by popularity
19
+ #
20
+ # @example Get popular bus route metrics
21
+ # finder = SpreeCmCommissioner::RouteMetrics::FindPopular.new
22
+ # finder.execute(route_type: :bus) # => Returns bus route metrics sorted by popularity
23
+
24
+ module SpreeCmCommissioner
25
+ module RouteMetrics
26
+ class FindPopular
27
+ def execute(route_type: nil, query: nil)
28
+ scope(route_type: route_type, query: query)
29
+ end
30
+
31
+ private
32
+
33
+ def scope(route_type:, query:)
34
+ result = SpreeCmCommissioner::RouteMetric.all
35
+
36
+ result = result.where(route_type: route_type.to_sym) if route_type.present?
37
+ result = result.where('cm_route_metrics.route_name ILIKE ?', "%#{query}%") if query.present?
38
+
39
+ result.includes(:origin_place, :destination_place)
40
+ .order(fulfilled_order_count: :desc, order_count: :desc)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,94 @@
1
+ # Finder for Route records with common filtering options.
2
+ # Usage:
3
+ # finder = SpreeCmCommissioner::Routes::Find.new(vendor: vendor, params: params)
4
+ # finder = SpreeCmCommissioner::Routes::Find.new(tenant: tenant, params: params)
5
+ # finder.execute
6
+ # returns ActiveRecord::Relation of SpreeCmCommissioner::Route
7
+
8
+ module SpreeCmCommissioner
9
+ module Routes
10
+ class Find
11
+ def initialize(vendor: nil, tenant: nil, params: {})
12
+ @vendor = vendor
13
+ @tenant = tenant
14
+ @params = params || {}
15
+
16
+ @ids = Array(@params[:ids]).map(&:to_s)
17
+ @name = @params.dig(:filter, :name) || @params[:name]
18
+ @route_type = @params.dig(:filter, :route_type) || @params[:route_type]
19
+ @origin_place_id = @params.dig(:filter, :origin_place_id)
20
+ @destination_place_id = @params.dig(:filter, :destination_place_id)
21
+ @query = @params.dig(:filter, :query) || @params[:query]
22
+ @limit = (@params[:limit] || @params.dig(:page, :limit) || 50).to_i
23
+ @order = @params[:order] || 'created_at DESC'
24
+ end
25
+
26
+ def execute
27
+ results = base_scope
28
+ results = by_ids(results) if ids?
29
+ results = by_name(results) if name?
30
+ results = by_route_type(results) if route_type?
31
+ results = by_origin_place(results) if origin_place_id.present?
32
+ results = by_destination_place(results) if destination_place_id.present?
33
+ results = by_query(results) if query?
34
+
35
+ results.order(order_clause).limit(limit)
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :vendor, :tenant, :params, :ids, :name, :route_type, :origin_place_id, :destination_place_id, :query, :limit, :order
41
+
42
+ def base_scope
43
+ scope = SpreeCmCommissioner::Route.includes(:origin_place, :destination_place, :route_photos)
44
+ scope = scope.where(vendor_id: vendor.id) if vendor.present?
45
+ scope = scope.where(tenant_id: tenant.id) if tenant.present?
46
+ scope
47
+ end
48
+
49
+ def ids?
50
+ ids.present?
51
+ end
52
+
53
+ def name?
54
+ name.present?
55
+ end
56
+
57
+ def route_type?
58
+ route_type.present?
59
+ end
60
+
61
+ def query?
62
+ query.present?
63
+ end
64
+
65
+ def by_ids(scope)
66
+ scope.where(id: ids)
67
+ end
68
+
69
+ def by_name(scope)
70
+ scope.where('cm_routes.route_name ILIKE ?', "%#{name}%")
71
+ end
72
+
73
+ def by_route_type(scope)
74
+ scope.where(route_type: route_type.to_s)
75
+ end
76
+
77
+ def by_origin_place(scope)
78
+ scope.where(origin_place_id: origin_place_id)
79
+ end
80
+
81
+ def by_destination_place(scope)
82
+ scope.where(destination_place_id: destination_place_id)
83
+ end
84
+
85
+ def by_query(scope)
86
+ scope.where('cm_routes.route_name ILIKE :q OR cm_routes.short_name ILIKE :q', q: "%#{query}%")
87
+ end
88
+
89
+ def order_clause
90
+ order
91
+ end
92
+ end
93
+ end
94
+ end
@@ -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