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
@@ -1,203 +0,0 @@
1
- module SpreeCmCommissioner::Transit
2
- class RouteStopCollection
3
- include Enumerable
4
-
5
- attr_reader :stops
6
-
7
- def initialize(stops = [])
8
- @stops = stops
9
- @errors = []
10
- end
11
-
12
- def self.from_hash(array)
13
- # Handle nil, empty array, or string "[]" when loaded from database
14
- parsed_array = case array
15
- when nil
16
- []
17
- when String
18
- JSON.parse(array)
19
- else
20
- array
21
- end
22
-
23
- stops = Array(parsed_array).each_with_index.map do |raw, index|
24
- hash = raw.to_h.symbolize_keys
25
- RouteStop.from_hash(hash.merge(sequence: index))
26
- end
27
-
28
- new(stops)
29
- end
30
-
31
- def to_h
32
- @stops.map(&:to_h)
33
- end
34
-
35
- def each(&block)
36
- @stops.each(&block)
37
- end
38
-
39
- def [](index)
40
- @stops[index]
41
- end
42
-
43
- def first
44
- @stops.first
45
- end
46
-
47
- def last
48
- @stops.last
49
- end
50
-
51
- def length
52
- @stops.length
53
- end
54
-
55
- def empty?
56
- @stops.empty?
57
- end
58
-
59
- def multi_leg?
60
- branch_location_ids = @stops.select(&:branch?).map(&:location_id).uniq
61
- branch_location_ids.length >= 3
62
- end
63
-
64
- def by_location
65
- @stops.group_by(&:location_id)
66
- end
67
-
68
- # Fetches vendor places and groups route stops by their location object
69
- # Returns: { location_obj => [route_stop_a, route_stop_b], location_obj_2 => [...] }
70
- # Example: If you have stops for branches in "Bangkok" and "Chiang Mai",
71
- # this returns all stops grouped by their location object
72
- def by_location_with_data
73
- return {} if @stops.empty?
74
-
75
- # Extract unique vendor_place_ids from all stops
76
- vendor_place_ids = @stops.map(&:vendor_place_id).compact.uniq
77
- return {} if vendor_place_ids.empty?
78
-
79
- # Single query: fetch all vendor places with their locations
80
- # index_by(:id) creates a hash for O(1) lookup: { vendor_place_id => vendor_place_obj }
81
- vendor_places = SpreeCmCommissioner::VendorPlace.where(id: vendor_place_ids).includes(:location).index_by(&:id)
82
-
83
- grouped = {}
84
- @stops.each do |stop|
85
- # Set the vendor_place object on the stop for later use
86
- stop.vendor_place = vendor_places[stop.vendor_place_id]
87
-
88
- # Get the location from the vendor_place (e.g., "Bangkok" location)
89
- location = stop.vendor_place&.location
90
- next if location.nil?
91
-
92
- # Group stops by location object
93
- grouped[location] ||= []
94
- grouped[location] << stop
95
- end
96
-
97
- grouped
98
- end
99
-
100
- def by_location_id_with_data
101
- return {} if @stops.empty?
102
-
103
- # Extract unique vendor_place_ids from all stops
104
- vendor_place_ids = @stops.map(&:vendor_place_id).compact.uniq
105
- return {} if vendor_place_ids.empty?
106
-
107
- # Single query: fetch all vendor places with their locations
108
- # index_by(:id) creates a hash for O(1) lookup: { vendor_place_id => vendor_place_obj }
109
- vendor_places = SpreeCmCommissioner::VendorPlace.where(id: vendor_place_ids).includes(:location).index_by(&:id)
110
-
111
- grouped = {}
112
- @stops.each do |stop|
113
- # Set the vendor_place object on the stop for later use
114
- stop.vendor_place = vendor_places[stop.vendor_place_id]
115
-
116
- # Get the location from the vendor_place (e.g., "Bangkok" location)
117
- location = stop.vendor_place&.location
118
- next if location.nil?
119
-
120
- # Group stops by location id
121
- grouped[location.id] ||= []
122
- grouped[location.id] << stop
123
- end
124
-
125
- grouped
126
- end
127
-
128
- # Groups route stops by their location in sequence order
129
- # Returns an array of hashes where each hash contains:
130
- # - location: the VendorPlace representing the location
131
- # - stops: array of route stops at that location, sorted by sequence
132
- # Example usage: @stops.group_by_sequence
133
- # {
134
- # location_object_1 => [stop1, stop2, stop3],
135
- # location_object_2 => [stop4, stop5]
136
- # }
137
- # Stop 0: Location A
138
- # Stop 1: Location A
139
- # Stop 2: Location B
140
- # Stop 3: Location A ← Same location again (For case when the bus has the same origin/destination)
141
- def group_by_sequence
142
- return [] if @stops.blank?
143
-
144
- by_location = by_location_with_data || {}
145
-
146
- # location_id => location_vendor_place (the "location" vendor_place in the hash key)
147
- location_by_id = by_location.keys.index_by(&:id)
148
-
149
- ordered_stops =
150
- by_location.values
151
- .flatten
152
- .compact
153
- .sort_by { |s| s.sequence.to_i }
154
-
155
- grouped_stops = []
156
- current_location_id = nil
157
- current_group = nil
158
-
159
- ordered_stops.each do |stop|
160
- stop_location_id = stop.location_id
161
-
162
- if stop_location_id != current_location_id
163
- current_location_id = stop_location_id
164
- current_group = {
165
- location: location_by_id[stop_location_id],
166
- stops: []
167
- }
168
- grouped_stops << current_group
169
- end
170
-
171
- current_group[:stops] << stop
172
- end
173
-
174
- grouped_stops
175
- end
176
-
177
- def valid?
178
- errors.empty?
179
- end
180
-
181
- def errors
182
- @errors = []
183
-
184
- # Check each stop is valid
185
- @stops.each_with_index do |stop, index|
186
- next if stop.valid?
187
-
188
- stop.errors.each do |error|
189
- @errors << "Stop #{index}: #{error}"
190
- end
191
- end
192
-
193
- # Check for duplicate vendor_place_ids
194
- vendor_place_ids = @stops.map(&:vendor_place_id)
195
- duplicates = vendor_place_ids.tally.select { |_, count| count > 1 }.keys
196
- duplicates.each do |dup_id|
197
- @errors << "vendor_place_id #{dup_id} appears more than once"
198
- end
199
-
200
- @errors
201
- end
202
- end
203
- end
@@ -1,105 +0,0 @@
1
- module SpreeCmCommissioner::Transit
2
- class TripForm
3
- include ActiveModel::Model
4
-
5
- attr_accessor :route_id,
6
- :name,
7
- :short_name,
8
- :price,
9
- :allow_booking,
10
- :allow_seat_selection,
11
- :route_type,
12
- :trip_stops, # TripStopForm[]
13
- :service_calendar # monday, tuesday, wednesday, thursday, friday, saturday, sunday, start_date, end_date
14
-
15
- # Convert params hashes into TripStopForm objects
16
- def trip_stops_attributes=(arr)
17
- items = arr.is_a?(Hash) ? arr.values : Array(arr)
18
- @trip_stops = items.map do |h|
19
- h = h.to_h.symbolize_keys
20
- TripStopForm.new.tap do |ts|
21
- ts.location_id = h[:location_id]
22
- ts.stop_id = h[:stop_id]
23
- ts.departure_time = h[:departure_time]
24
- ts.duration_in_hours = h[:duration_in_hours]
25
- ts.route_type = h[:route_type]
26
- ts.vehicle_type_id = h[:vehicle_type_id]
27
- ts.vehicle_id = h[:vehicle_id]
28
- ts.board_to_trip_id = h[:board_to_trip_id]
29
- ts.allow_boarding = h[:allow_boarding]
30
- ts.allow_drop_off = h[:allow_drop_off]
31
- ts.allow_seat_selection = h[:allow_seat_selection]
32
- ts.allow_booking = h[:allow_booking]
33
- ts.sequence = h[:sequence]
34
- ts.vendor_id = h[:vendor_id]
35
- end
36
- end
37
- end
38
-
39
- def multi_leg?
40
- trip_stops.map(&:location_id).uniq.size > 2
41
- end
42
-
43
- def total_duration_seconds
44
- return 0 if trip_stops.size < 2
45
-
46
- minutes = trip_stops[0...-1].sum { |s| s.duration_in_minutes || 0 }
47
- minutes * 60
48
- end
49
-
50
- def valid_data? # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
51
- errors.clear
52
-
53
- errors.add(:route_id, 'must be present') if route_id.blank?
54
- errors.add(:trip_stops, 'must be present') if trip_stops.blank? || trip_stops.size < 2
55
-
56
- first_stop = trip_stops&.first
57
- last_stop = trip_stops&.last
58
-
59
- errors.add(:trip_stops, 'first stop must allow boarding') unless first_stop&.allow_boarding?
60
- errors.add(:trip_stops, 'last stop must allow drop off') unless last_stop&.allow_drop_off
61
- errors.add(:departure_time, 'missing on first stop') if first_stop&.departure_time.blank?
62
-
63
- errors.empty?
64
- end
65
-
66
- # Groups trip_stops into legs by location changes
67
- # Separates legs when location_id changes (at branch stops)
68
- # Break stops (stop_type = :stop) are included but don't trigger separation
69
- #
70
- # Example:
71
- # [PP1, PP2, PTT1(break stop), BTB1, BTB2, PPT2(break stop), POIPET1, POIPET1]
72
- #
73
- # Output (legs):
74
- # [
75
- # [PP1, PP2, PTT, BTB1, BTB2],
76
- # [BTB1, BTB2, PPT1, POIPET1, POIPET1]
77
- # ]
78
- def trip_stops_grouped_by_leg(stops)
79
- return [] if trip_stops.empty?
80
-
81
- legs = []
82
- current_leg = []
83
- last_location_id = nil
84
-
85
- trip_stops.each do |trip_stop|
86
- stop = stops[trip_stop.stop_id]
87
-
88
- # If location changed and we have stops in current leg, start a new leg
89
- current_leg << trip_stop
90
- if last_location_id.present? && last_location_id != trip_stop.location_id && current_leg.any?
91
- # Include the first stop of new location in previous leg
92
- legs << current_leg
93
- current_leg = [trip_stop]
94
- end
95
-
96
- # Update location only for branch stops (not for "just stops")
97
- last_location_id = trip_stop.location_id if stop.branch?
98
- end
99
-
100
- # Add final leg if it has stops
101
- legs << current_leg if current_leg.any?
102
- legs
103
- end
104
- end
105
- end
@@ -1,67 +0,0 @@
1
- module SpreeCmCommissioner::Transit
2
- class TripStopForm
3
- attr_accessor :location_id,
4
- :stop_id,
5
- :departure_time,
6
- :duration_in_hours,
7
- :route_type,
8
- #
9
- # when create, vehicle_type_id is required. But if board_to_trip_id present (from another route), we can copy from that trip.
10
- # when edit, vehicle_type_id can be changed since we already copied the seat layout.
11
- :vehicle_type_id,
12
- #
13
- # (optional), same as vehicle_type_id.
14
- :vehicle_id,
15
- #
16
- # when create:
17
- # if board_to_trip_id present, it mean it link to another trip to board.
18
- #
19
- # when edit:
20
- # board_to_trip_id can't be set to nil or changed to another trip id.
21
- :board_to_trip_id,
22
- #
23
- # for first stop, allow_boarding must be true.
24
- # for last stop, allow_boarding must be false.
25
- # for middle stops, both can be true/false.
26
- #
27
- # but when stop.type is not branch (eg. stop), allow_boarding must be false.
28
- :allow_boarding,
29
- #
30
- # for first stop, allow_drop_off must be false.
31
- # for last stop, allow_drop_off must be true.
32
- # for middle stops, both can be true/false.
33
- #
34
- # but when stop.type is not branch (eg. stop), allow_drop_off must be false.
35
- :allow_drop_off,
36
- #
37
- # when stop.type is branch, admin can toggle allow_seat_selection.
38
- # but for edit, if board_to_trip_id is from another route, allow_seat_selection can't be changed.
39
- # only allow changed from its own route.
40
- :allow_seat_selection,
41
- #
42
- # when create, allow_booking only when stop.type is branch, use it to assign to create sub-trip.
43
- # but for edit, if board_to_trip_id is from another route, allow_booking can't be changed.
44
- # only allow changed from its own route.
45
- :allow_booking,
46
- :sequence,
47
- #
48
- # set from create service
49
- :vendor_id,
50
- # branch or stop
51
- :stop_type
52
-
53
- def allow_boarding?
54
- ActiveModel::Type::Boolean.new.cast(allow_boarding)
55
- end
56
-
57
- def allow_drop_off?
58
- ActiveModel::Type::Boolean.new.cast(allow_drop_off)
59
- end
60
-
61
- def duration_in_minutes
62
- return nil if duration_in_hours.blank?
63
-
64
- (duration_in_hours.to_f * 60).to_i
65
- end
66
- end
67
- end