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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b7e70b6ad87db5a4bd6b3879754f7ce8a753e1323d07ac754adea1426ef74b4
4
- data.tar.gz: 699aa89f466074b9d86b8b0ddf39a7a8bf41f6edb4dda640f647dd33651bc9eb
3
+ metadata.gz: dbcd427457c152b80d8c5aa1f87a1633e29ba0c39b99c3ee856f88187e54ff6e
4
+ data.tar.gz: 372de4f3e2a8ab6df9fb2ba94287a5ae2f676633808c02593685ae93f5b201a8
5
5
  SHA512:
6
- metadata.gz: 9f60f4b8646f3537f616fe2d15b4f49b0bfa335acd1f61d4a97c6d25dc4a891ced787c0d2983f0dc327b2a125f643adebdc1a9ce59025436d74c507761a773f1
7
- data.tar.gz: 18584fa23c8784b390a5b076fd7a855fb101db7c6bd5ea60159e6fdf6072fcac9441a9daddb2f9bc3f89351faf03b2792b6ef7864b63b081429752ce98d1f11a
6
+ metadata.gz: 7d944b5af7b8fc624cce7d0ce5428060b7094429eb1619147790d606102d13dbf88702686d4723208feb531339eac39d87c9011320bb8f18b897a12267e699a3
7
+ data.tar.gz: 5a2896cb596c6bf3ed41c2a860e794ced42c3a317f7af71de69873fec33869a146cbc2cc4953eddce256d7a36d2a45f5b8e1319bc8d0836692b7a1a37dc1e397
@@ -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)
37
+ spree_cm_commissioner (2.5.2.pre.pre1)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -3,79 +3,59 @@ module Spree
3
3
  class InventoryItemsController < StockManagementsController
4
4
  before_action :load_parent
5
5
 
6
- # GET /products/:slug/variants/:variant_id/inventory_items/:id
7
- def show
8
- @inventory_item = @product.inventory_items.find(params[:id])
9
- @default_prices = @inventory_item.variant.prices.to_a.index_by(&:currency)
10
-
11
- @prices = @inventory_item.prices.to_a
12
- supported_currencies_for_all_stores.each do |currency|
13
- @prices << @inventory_item.prices.new(currency: currency) if @prices.none? { |price| price.currency == currency }
14
- end
6
+ # GET /products/:slug/inventory_items/prices?inventory_ids=1,2,3
7
+ def prices
8
+ inventory_ids = params[:inventory_ids].to_s.split(',').map(&:to_i)
9
+ @inventory_items = @product.inventory_items.where(id: inventory_ids)
10
+ end
15
11
 
16
- @cached_inventory_item = ::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new([@inventory_item])
17
- .call
18
- .index_by(&:inventory_item_id)[@inventory_item.id]
12
+ # GET /products/:slug/inventory_items/stocks?inventory_ids=1,2,3
13
+ def stocks
14
+ inventory_ids = params[:inventory_ids].to_s.split(',').map(&:to_i)
15
+ @inventory_items = @product.inventory_items.where(id: inventory_ids)
19
16
  end
20
17
 
21
- # POST /products/:slug/variants/:variant_id/inventory_items
22
- def create
23
- variant = @product.variants.find(params[:variant_id])
24
- inventory_item = variant.create_default_non_permanent_inventory_item!
18
+ # PATCH /products/:slug/inventory_items/bulk_update_prices
19
+ def bulk_update_prices # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
20
+ inventory_ids = params[:inventory_ids].to_s.split(',').map(&:to_i)
21
+ inventory_items = @product.inventory_items.where(id: inventory_ids)
25
22
 
26
- if inventory_item.present?
27
- result = SpreeCmCommissioner::Stock::InventoryItemResetter.call(inventory_item: inventory_item)
23
+ inventory_items.each do |inventory_item|
24
+ params[:prices]&.values&.each do |price_params|
25
+ next if price_params[:price].empty?
28
26
 
29
- if result.success?
30
- flash[:success] = flash_message_for(result.inventory_item, :successfully_created)
31
- else
32
- flash[:error] = result.message
27
+ price = inventory_item.prices.find_or_initialize_by(currency: price_params[:currency])
28
+ price.price = price_params[:price]
29
+ price.compare_at_price = price_params[:compare_at_price].empty? ? nil : price_params[:compare_at_price]
30
+ price.variant_id = inventory_item.variant_id
31
+ price.save! if (price.new_record? && price.price) || (!price.new_record? && price.changed?)
33
32
  end
34
33
  end
35
34
 
35
+ flash[:success] = "Successfully updated prices for #{inventory_items.count} items"
36
36
  redirect_back fallback_location: admin_product_stock_managements_path(@product)
37
37
  end
38
38
 
39
- # PATCH /products/:slug/variants/:variant_id/inventory_items/:id/reset
40
- def reset
41
- inventory_item = @product.inventory_items.find(params[:id])
42
- result = SpreeCmCommissioner::Stock::InventoryItemResetter.call(inventory_item: inventory_item)
43
-
44
- if result.success?
45
- flash[:success] = flash_message_for(result.inventory_item, :successfully_updated)
46
- else
47
- flash[:error] = result.message
48
- end
49
-
50
- redirect_back fallback_location: admin_product_stock_managements_path(@product)
51
- end
52
-
53
- # PATCH /products/:slug/variants/:variant_id/inventory_items/:id/update_prices
54
- def update_prices
55
- inventory_item = @product.inventory_items.find(params[:id])
39
+ # PATCH /products/:slug/inventory_items/bulk_update_stocks
40
+ def bulk_update_stocks
41
+ inventory_ids = params[:inventory_ids].to_s.split(',').map(&:to_i)
42
+ inventory_items = @product.inventory_items.where(id: inventory_ids)
43
+ target_quantity = params[:quantity].to_i
56
44
 
57
- params[:prices]&.values&.each do |price_params|
58
- price = inventory_item.prices.find_or_initialize_by(currency: price_params[:currency])
59
- price.price = price_params[:price].empty? ? nil : price_params[:price]
60
- price.compare_at_price = price_params[:compare_at_price].empty? ? nil : price_params[:compare_at_price]
61
- price.variant_id = inventory_item.variant_id
62
- price.save! if (price.new_record? && price.price) || (!price.new_record? && price.changed?)
45
+ if target_quantity.zero?
46
+ flash[:error] = "Target quantity (#{target_quantity}) must be greater than zero"
47
+ return redirect_back(fallback_location: admin_product_stock_managements_path(@product))
63
48
  end
64
49
 
65
- flash[:success] = flash_message_for(inventory_item, :successfully_updated)
66
- redirect_back fallback_location: admin_product_stock_managements_path(@product)
67
- end
68
-
69
- # PATCH /products/:slug/variants/:variant_id/inventory_items/:id/delete_prices
70
- def delete_prices
71
- inventory_item = @product.inventory_items.find(params[:id])
50
+ inventory_items.each do |inventory_item|
51
+ # Calculate the difference between target and current quantity
52
+ quantity_change = target_quantity - inventory_item.quantity_available
72
53
 
73
- if inventory_item.prices.destroy_all
74
- flash[:success] = flash_message_for(inventory_item, :successfully_removed)
75
- else
76
- flash[:error] = inventory_item.errors.full_messages.join(', ')
54
+ # Use adjust_quantity! to update max_capacity, quantity_available, and sync to Redis
55
+ inventory_item.adjust_quantity!(quantity_change) if quantity_change != 0
77
56
  end
78
57
 
58
+ flash[:success] = "Successfully updated stocks for #{inventory_items.count} items"
79
59
  redirect_back fallback_location: admin_product_stock_managements_path(@product)
80
60
  end
81
61
  end
@@ -12,7 +12,7 @@ module Spree
12
12
  end
13
13
 
14
14
  def index
15
- @variants = @product.variants.includes(:images, stock_items: :stock_location, option_values: :option_type)
15
+ @variants = @product.variants.includes(:images, :default_price, stock_items: :stock_location, option_values: :option_type)
16
16
  @variants = [@product.master] if @variants.empty?
17
17
  @stock_locations = (@variants.flat_map(&:stock_locations) + @product.vendor.stock_locations).uniq
18
18
 
@@ -36,14 +36,25 @@ module Spree
36
36
  redirect_back fallback_location: admin_product_stock_managements_path(@product)
37
37
  end
38
38
 
39
- # GET /products/:slug/stock_managements/calendar?year=
39
+ # GET /products/:slug/stock_managements/calendar?year=&selected_variant_id=
40
40
  def calendar
41
41
  @year = params[:year].present? ? params[:year].to_i : Time.zone.today.year
42
42
 
43
43
  from_date = Date.new(@year, 1, 1).beginning_of_year
44
44
  to_date = Date.new(@year, 1, 1).end_of_year
45
45
 
46
- @inventory_items = @product.inventory_items.includes(:variant, :prices).where(inventory_date: from_date..to_date).to_a
46
+ @variants = @product.variants
47
+ @inventory_items = if params[:selected_variant_id].present?
48
+ @product.inventory_items.includes(:variant, :prices).where(
49
+ inventory_date: from_date..to_date,
50
+ variant_id: params[:selected_variant_id]
51
+ ).to_a
52
+ else
53
+ @product.inventory_items.includes(:variant, :prices).where(
54
+ inventory_date: from_date..to_date
55
+ ).to_a
56
+ end
57
+
47
58
  @cached_inventory_items = ::SpreeCmCommissioner::RedisStock::CachedInventoryItemsBuilder.new(@inventory_items)
48
59
  .call
49
60
  .index_by(&:inventory_item_id)
@@ -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