spree_cm_commissioner 2.4.0 → 2.4.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f63b4eb2a25d5dff782d61eb3df12b639c261a418213940ed12d8b9b483b9a1
4
- data.tar.gz: 6682f9d70eb92effa0345aee38da3d483c8bf555a74da8c88fc52ddb7553a2e5
3
+ metadata.gz: 41a0cae2693223db96d5e06211c36bfccd036a7661b2b65dcf8aa96a51ec66a1
4
+ data.tar.gz: 8c64ffd38289f261b85873082b90b7295dd9d8e72157d48941d21d506f21b84d
5
5
  SHA512:
6
- metadata.gz: 7abea09634def1e05f839d075bb5fcd1cdfac7bc13ed0392ac6c5aa60e5f9aef3a54600d69afa63778aca0ddf62e09ffc1f5c0200dd88f728b7a1bcd0272faea
7
- data.tar.gz: f102b65bf2526916e47c728b0e50eebe7ce188b96bc0f206d92be0434499a734fe0c434f5d364554c20d5bcf0cec20f4b2bdb945d321c213ddb03c1678d9f135
6
+ metadata.gz: 935aa1894a546795e42928ff46c13b55b95043f3419dde0d842ad5f7a684eb71bcb8e52b1624e9a56766018cdf531e8e92305e4fcf0f1b805974508aa3012759
7
+ data.tar.gz: d91d61dfb4c16a1c4e5b5e31622c37a9b60e17f3c591e6f2e02f3c2a9b932012b3250e9d2f688af74126ce79c0bbecd3962d0b484d961bc5c9290ac7148b2874
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.4.0)
37
+ spree_cm_commissioner (2.4.2)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -18,7 +18,9 @@ module Spree
18
18
 
19
19
  # override
20
20
  def collection
21
- @collection ||= collection_finder.new.execute
21
+ @collection ||= collection_finder.new.execute(
22
+ route_type: params[:route_type]
23
+ )
22
24
  end
23
25
 
24
26
  # override
@@ -1,24 +1,42 @@
1
- # API endpoint for searching places that are used in routes by keyword.
1
+ # API endpoint for searching places connected to a specific place via routes.
2
2
  #
3
3
  # GET /api/v2/storefront/route_places
4
4
  #
5
- # Searches for places (origins or destinations) that are used in existing routes,
6
- # filtered by keyword search on place names.
5
+ # Finds places (origins or destinations) that are connected to a given place through existing routes.
6
+ # Optionally filters results by keyword or route_type. Supports two modes:
7
+ #
8
+ # Usage 1: Connected places (requires place_id)
9
+ # - Finds places connected to a specific place via routes
10
+ # - Returns origins/destinations that have routes with the specified place
11
+ #
12
+ # Usage 2: Keyword search (requires query, place_id optional)
13
+ # - Searches all route places by keyword
14
+ # - Returns all origins/destinations matching the keyword
7
15
  #
8
16
  # Query Parameters:
9
- # - query: [String] Required. Keyword to search in place names (case-insensitive)
10
17
  # - place_type: [String] Required. Type of route place: 'origin' or 'destination'
18
+ # * 'origin': returns origins that have routes TO the specified place (if place_id provided)
19
+ # * 'destination': returns destinations that have routes FROM the specified place (if place_id provided)
20
+ # - place_id: [Integer] Optional. The place ID to find connected places for
21
+ # - query: [String] Optional. Keyword to filter place names (case-insensitive)
11
22
  # - include: Optional comma-separated list (e.g., 'vendors,nearby_places')
12
23
  #
13
24
  # Response: Collection of places serialized with PlaceSerializer
14
25
  #
15
26
  # Behavior:
16
- # - Returns empty collection if query is blank or place_type is invalid
17
- # - UI should only call this endpoint when user has input a search term
18
- # - For initial/empty state, use GET /api/v2/storefront/popular_route_places instead
27
+ # - Returns empty collection if place_type is invalid
28
+ # - If place_id provided: returns places connected to that place
29
+ # - If place_id blank: returns all origins/destinations
30
+ # - Filters results by keyword or route_type if provided
31
+ #
32
+ # @example Mode 1: Find destinations connected to origin place ID 123
33
+ # GET /api/v2/storefront/route_places?place_id=123&place_type=destination
34
+ #
35
+ # @example Mode 2: Search all destination places by keyword
36
+ # GET /api/v2/storefront/route_places?place_type=destination&query=Phnom
19
37
  #
20
- # @example
21
- # GET /api/v2/storefront/route_places?query=Phnom&place_type=origin
38
+ # @example Combined: Find origins connected to place 456, filtered by keyword
39
+ # GET /api/v2/storefront/route_places?place_id=456&place_type=origin&query=Siem
22
40
  module Spree
23
41
  module Api
24
42
  module V2
@@ -28,7 +46,12 @@ module Spree
28
46
 
29
47
  # override
30
48
  def collection
31
- @collection ||= collection_finder.new(keyword: params[:query], place_type: params[:place_type]).execute
49
+ @collection ||= collection_finder.new(
50
+ place_type: params[:place_type],
51
+ place_id: params[:place_id],
52
+ keyword: params[:query],
53
+ route_type: params[:route_type]
54
+ ).execute
32
55
  end
33
56
 
34
57
  # override
@@ -1,43 +1,80 @@
1
- # Finds places that are used in routes (as origin or destination) by keyword search.
1
+ # Finds places connected via routes with optional filtering.
2
2
  #
3
- # @param keyword [String] Search term to match against place names (case-insensitive)
4
- # @param place_type [String] Type of route place: 'origin' or 'destination'
3
+ # @param place_type [String] Required. 'origin' or 'destination'
4
+ # @param place_id [Integer] Optional. Filter by connected place ID
5
+ # @param keyword [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)
5
7
  #
6
- # @return [ActiveRecord::Relation<SpreeCmCommissioner::Place>] Places matching the keyword
7
- # that are used as origins or destinations in existing routes. Returns empty relation if
8
- # keyword is blank or place_type is invalid.
8
+ # @return [ActiveRecord::Relation<SpreeCmCommissioner::Place>]
9
9
  #
10
- # @note For default/empty state display, use SpreeCmCommissioner::Routes::FindPopular instead
11
- # to show popular routes to users before they enter a search term.
10
+ # Modes:
11
+ # - place_id only: returns connected places (origins TO or destinations FROM given place)
12
+ # - keyword only: filters by name
13
+ # - route_type only: filters by trip route types on those routes
14
+ # - combinations: connected places filtered by name and/or route type
15
+ # - neither: all origins/destinations in routes
12
16
  #
13
- # @example
14
- # finder = SpreeCmCommissioner::Places::FindWithRoute.new(keyword: 'Phnom', place_type: 'origin')
15
- # finder.execute # => Returns places with "Phnom" in name that are used as route origins
17
+ # @example Origins with ferry trips to place 123
18
+ # FindWithRoute.new(place_type: 'origin', place_id: 123, route_type: :ferry).execute
19
+ #
20
+ # @example Destinations filtered by keyword and route type
21
+ # FindWithRoute.new(place_type: 'destination', keyword: 'Phnom', route_type: :bus).execute
16
22
  module SpreeCmCommissioner
17
23
  module Places
18
24
  class FindWithRoute
19
- attr_reader :keyword, :place_type
25
+ attr_reader :place_type, :place_id, :keyword, :route_type
20
26
 
21
- def initialize(keyword:, place_type:)
22
- @keyword = keyword
27
+ def initialize(place_type:, place_id: nil, keyword: nil, route_type: nil)
23
28
  @place_type = place_type
29
+ @place_id = place_id
30
+ @keyword = keyword
31
+ @route_type = route_type
24
32
  end
25
33
 
26
34
  def execute
27
- return SpreeCmCommissioner::Place.none if keyword.blank? || place_type.blank?
35
+ return SpreeCmCommissioner::Place.none if place_type.blank?
36
+ return SpreeCmCommissioner::Place.none if place_id.present? && !SpreeCmCommissioner::Place.exists?(place_id)
28
37
 
29
- scope.where('cm_places.name ILIKE ?', "%#{keyword}%")
38
+ result = scope
39
+ result = apply_keyword_filter(result) if keyword.present?
40
+ result = apply_route_type_filter(result) if route_type.present?
41
+ result
30
42
  end
31
43
 
32
44
  private
33
45
 
34
46
  def scope
35
- if place_type == 'origin'
36
- SpreeCmCommissioner::Place.with_routes_as_origin
47
+ return SpreeCmCommissioner::Place.none unless valid_place_type?
48
+
49
+ base_scope = origin? ? SpreeCmCommissioner::Place.with_routes_as_origin : SpreeCmCommissioner::Place.with_routes_as_destination
50
+
51
+ return base_scope if place_id.blank?
52
+
53
+ if origin?
54
+ base_scope.where(routes_as_origin: { destination_place_id: place_id })
37
55
  else
38
- SpreeCmCommissioner::Place.with_routes_as_destination
56
+ base_scope.where(routes_as_destination: { origin_place_id: place_id })
39
57
  end
40
58
  end
59
+
60
+ def apply_keyword_filter(result)
61
+ result.where('cm_places.name ILIKE ?', "%#{keyword}%")
62
+ end
63
+
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
69
+ end
70
+
71
+ def origin?
72
+ place_type == 'origin'
73
+ end
74
+
75
+ def valid_place_type?
76
+ %w[origin destination].include?(place_type)
77
+ end
41
78
  end
42
79
  end
43
80
  end
@@ -4,21 +4,46 @@
4
4
  # 1. fulfilled_order_count (completed orders) - DESC
5
5
  # 2. order_count (total orders including incomplete) - DESC
6
6
  #
7
+ # @param route_type [Symbol, String] Optional. Filter by route type from associated trips (e.g., :ferry, :bus)
8
+ #
7
9
  # @return [ActiveRecord::Relation<SpreeCmCommissioner::Route>] Routes with origin and destination places loaded,
8
10
  # ordered from most to least popular
9
11
  #
10
- # @example
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
11
21
  # finder = SpreeCmCommissioner::Routes::FindPopular.new
12
- # finder.execute # => Returns routes sorted by fulfillment and order counts
22
+ # finder.execute(route_type: :bus) # => Returns bus routes sorted by popularity
13
23
 
14
24
  module SpreeCmCommissioner
15
25
  module Routes
16
26
  class FindPopular
17
- def execute
18
- SpreeCmCommissioner::Route
19
- .includes(:origin_place, :destination_place)
20
- .order(Arel.sql('COALESCE(fulfilled_order_count, 0) DESC'))
21
- .order(Arel.sql('COALESCE(order_count, 0) DESC'))
27
+ def execute(route_type: nil)
28
+ scope(route_type: route_type)
29
+ end
30
+
31
+ private
32
+
33
+ def scope(route_type: nil)
34
+ result = SpreeCmCommissioner::Route
35
+ .includes(:origin_place, :destination_place)
36
+
37
+ if route_type.present?
38
+ # Filter routes that have trips with the specified route_type
39
+ route_scope = SpreeCmCommissioner::Route
40
+ .joins(:trips)
41
+ .where(cm_trips: { route_type: route_type.to_sym })
42
+ result = result.where(id: route_scope.select(:id))
43
+ end
44
+
45
+ result.order(Arel.sql('COALESCE(fulfilled_order_count, 0) DESC'))
46
+ .order(Arel.sql('COALESCE(order_count, 0) DESC'))
22
47
  end
23
48
  end
24
49
  end
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
2
2
  module VehicleType
3
3
  extend ActiveSupport::Concern
4
4
 
5
- VEHICLE_TYPES = %i[suv sedan minivan van sleeping_bus luxury_van air_bus bus].freeze
5
+ VEHICLE_TYPES = %i[suv sedan minivan van sleeping_bus luxury_van air_bus bus ferry].freeze
6
6
 
7
7
  included do
8
8
  enum vehicle_type: VEHICLE_TYPES if table_exists? && column_names.include?('vehicle_type')
@@ -24,6 +24,9 @@ module SpreeCmCommissioner
24
24
  has_many :routes_as_origin, class_name: 'SpreeCmCommissioner::Route', foreign_key: :origin_place_id
25
25
  has_many :routes_as_destination, class_name: 'SpreeCmCommissioner::Route', foreign_key: :destination_place_id
26
26
 
27
+ has_many :trips_as_origin, through: :routes_as_origin, source: :trips
28
+ has_many :trips_as_destination, through: :routes_as_destination, source: :trips
29
+
27
30
  # Scopes for route-based filtering
28
31
  scope :with_routes_as_origin, -> { joins(:routes_as_origin).distinct }
29
32
  scope :with_routes_as_destination, -> { joins(:routes_as_destination).distinct }
@@ -2,7 +2,9 @@ module SpreeCmCommissioner
2
2
  class RoutePlacesRequestSchema < ApplicationRequestSchema
3
3
  params do
4
4
  required(:place_type).filled(:string)
5
- required(:query).filled(:string)
5
+ optional(:query).maybe(:string)
6
+ optional(:place_id).maybe(:integer)
7
+ optional(:route_type).maybe(:string)
6
8
  end
7
9
 
8
10
  rule(:place_type) do
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.4.0'.freeze
2
+ VERSION = '2.4.2'.freeze
3
3
 
4
4
  module_function
5
5
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_cm_commissioner
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-08 00:00:00.000000000 Z
11
+ date: 2025-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree