spree_cm_commissioner 2.5.1.pre.pre1 → 2.5.1.pre.pre2
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 +4 -4
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/admin/guests_controller.rb +5 -5
- data/app/controllers/spree/api/chatrace/check_ins_controller.rb +6 -4
- data/app/controllers/spree/api/chatrace/guests_controller.rb +5 -18
- data/app/controllers/spree/api/v2/operator/check_in_bulks_controller.rb +6 -5
- data/app/controllers/spree/api/v2/operator/check_in_sessions_controller.rb +32 -0
- data/app/controllers/spree/api/v2/operator/check_ins_controller.rb +5 -5
- data/app/controllers/spree/api/v2/operator/event_qrs_controller.rb +2 -0
- data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +1 -1
- data/app/controllers/spree/api/v2/storefront/self_check_in_controller.rb +7 -7
- data/app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb +4 -4
- data/app/controllers/spree/api/v2/tenant/popular_route_places_controller.rb +60 -0
- data/app/controllers/spree/api/v2/tenant/routes_controller.rb +50 -0
- data/app/controllers/spree/api/v2/tenant/transit/draft_orders_controller.rb +46 -0
- data/app/controllers/spree/events/guests_controller.rb +9 -10
- data/app/controllers/spree/transit/trips_controller.rb +3 -3
- data/app/finders/spree_cm_commissioner/places/find_with_route.rb +12 -12
- data/app/finders/spree_cm_commissioner/route_metrics/find_popular.rb +44 -0
- data/app/finders/spree_cm_commissioner/routes/find.rb +94 -0
- data/app/finders/spree_cm_commissioner/routes/find_popular.rb +20 -35
- data/app/interactors/spree_cm_commissioner/event_qr_generator.rb +2 -1
- data/app/interactors/spree_cm_commissioner/stock/permanent_inventory_items_generator.rb +11 -4
- data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +10 -1
- data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +4 -3
- data/app/interactors/spree_cm_commissioner/trip_stops_creator.rb +2 -2
- data/app/jobs/spree_cm_commissioner/guests/preload_check_in_session_ids_job.rb +10 -0
- data/app/jobs/spree_cm_commissioner/route_metrics/decrease_trip_count_job.rb +10 -0
- data/app/jobs/spree_cm_commissioner/route_metrics/increase_fulfilled_order_count_job.rb +10 -0
- data/app/jobs/spree_cm_commissioner/route_metrics/increase_order_count_job.rb +10 -0
- data/app/jobs/spree_cm_commissioner/route_metrics/increase_trip_count_job.rb +10 -0
- data/app/jobs/spree_cm_commissioner/stock/permanent_inventory_items_generator_job.rb +2 -2
- data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +2 -2
- data/app/models/spree_cm_commissioner/check_in.rb +34 -7
- data/app/models/spree_cm_commissioner/check_in_session.rb +8 -3
- data/app/models/spree_cm_commissioner/guest.rb +60 -28
- data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +2 -2
- data/app/models/spree_cm_commissioner/place.rb +5 -8
- data/app/models/spree_cm_commissioner/pricing_actions/create_guest_adjustments.rb +52 -0
- data/app/models/spree_cm_commissioner/pricing_actions/create_line_item_adjustments.rb +6 -0
- data/app/models/spree_cm_commissioner/pricing_rule.rb +4 -0
- data/app/models/spree_cm_commissioner/pricing_rules/age_group.rb +45 -0
- data/app/models/spree_cm_commissioner/pricing_rules/nationality.rb +21 -2
- data/app/models/spree_cm_commissioner/pricing_rules/nationality_group.rb +41 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/route.rb +46 -5
- data/app/models/spree_cm_commissioner/route_metric.rb +21 -0
- data/app/models/spree_cm_commissioner/route_photo.rb +12 -0
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/trip.rb +8 -33
- data/app/models/spree_cm_commissioner/trip_stop.rb +16 -2
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +3 -1
- data/app/queries/spree_cm_commissioner/trip_query.rb +2 -2
- data/app/serializers/spree/v2/tenant/transit_cart_serializer.rb +11 -0
- data/app/serializers/spree_cm_commissioner/v2/operator/check_in_serializer.rb +1 -2
- data/app/serializers/spree_cm_commissioner/v2/operator/check_in_session_serializer.rb +9 -0
- data/app/serializers/spree_cm_commissioner/v2/operator/guest_serializer.rb +1 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +2 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/route_serializer.rb +3 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/transit_line_item_serializer.rb +17 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/trip_stop_serializer.rb +1 -1
- data/app/services/spree_cm_commissioner/check_ins/create_bulk.rb +65 -0
- data/app/services/spree_cm_commissioner/check_ins/destroy_bulk.rb +54 -0
- data/app/services/spree_cm_commissioner/guests/preload_check_in_session_ids.rb +22 -0
- data/app/services/spree_cm_commissioner/pricing_models/create_with_rule_groups.rb +56 -0
- data/app/services/spree_cm_commissioner/pricing_models/update_with_rule_groups.rb +69 -0
- data/app/services/spree_cm_commissioner/pricing_rules/build_params.rb +16 -7
- data/app/services/spree_cm_commissioner/route_metrics/decrease_trip_count.rb +31 -0
- data/app/services/spree_cm_commissioner/{routes/increment_fulfilled_order_count.rb → route_metrics/increase_fulfilled_order_count.rb} +3 -3
- data/app/services/spree_cm_commissioner/{routes/increment_order_count.rb → route_metrics/increase_order_count.rb} +3 -3
- data/app/services/spree_cm_commissioner/route_metrics/increase_trip_count.rb +31 -0
- data/app/services/spree_cm_commissioner/{routes/base_update_order_metrics.rb → route_metrics/update_route_metrics.rb} +11 -16
- data/app/services/spree_cm_commissioner/routes/create.rb +51 -0
- data/app/services/spree_cm_commissioner/routes/update.rb +25 -0
- data/app/{interactors/spree_cm_commissioner/transit/draft_order_creator.rb → services/spree_cm_commissioner/transit_order/create.rb} +13 -16
- data/app/services/spree_cm_commissioner/trips/create_single_leg.rb +123 -0
- data/app/services/spree_cm_commissioner/trips/service_calendars/create_or_update.rb +54 -0
- data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +88 -0
- data/app/services/spree_cm_commissioner/trips/variants/create.rb +103 -0
- data/app/views/spree/transit/trip_stops/index.html.erb +4 -2
- data/app/views/spree_cm_commissioner/guest_mailer/send_ticket_to_guest.html.erb +0 -1
- data/config/initializers/spree_permitted_attributes.rb +11 -0
- data/config/locales/en.yml +2 -0
- data/config/locales/km.yml +2 -0
- data/config/routes.rb +8 -0
- data/db/migrate/20251224033103_migrate_cm_routes_to_cm_route_metrics.rb +17 -0
- data/db/migrate/20251224033910_migrate_cm_vendor_routes_to_cm_routes.rb +30 -0
- data/db/migrate/20251225100000_add_age_group_to_cm_guests.rb +6 -0
- data/db/migrate/20260105024742_add_type_to_cm_pricing_actions.rb +11 -0
- data/db/migrate/20260105072450_migrate_cm_trip_stops_to_support_trip_connection.rb +12 -0
- data/db/migrate/20260108101406_add_allow_booking_to_cm_trips.rb +5 -0
- data/db/migrate/20260121024645_add_nationality_group_to_cm_guests.rb +5 -0
- data/docs/pricing_model/age_group.md +40 -0
- data/docs/pricing_model/nationality_group.md +35 -0
- data/lib/spree_cm_commissioner/test_helper/factories/check_in_factory.rb +3 -1
- data/lib/spree_cm_commissioner/test_helper/factories/check_in_session_factory.rb +26 -0
- data/lib/spree_cm_commissioner/test_helper/factories/guest_factory.rb +4 -0
- data/lib/spree_cm_commissioner/test_helper/factories/pricing_action_factory.rb +4 -0
- data/lib/spree_cm_commissioner/test_helper/factories/pricing_rule_factory.rb +45 -1
- data/lib/spree_cm_commissioner/test_helper/factories/route_factory.rb +7 -6
- data/lib/spree_cm_commissioner/test_helper/factories/route_metric_factory.rb +12 -0
- data/lib/spree_cm_commissioner/test_helper/factories/route_photo_factory.rb +5 -0
- data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +4 -1
- data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +3 -1
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_factory.rb +2 -0
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +22 -0
- data/lib/spree_cm_commissioner/transit/route_stop.rb +61 -0
- data/lib/spree_cm_commissioner/transit/route_stop_collection.rb +175 -0
- data/lib/spree_cm_commissioner/transit/trip_form.rb +81 -0
- data/lib/spree_cm_commissioner/transit/trip_stop_form.rb +61 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +4 -0
- metadata +54 -20
- data/app/interactors/spree_cm_commissioner/check_in_bulk_creator.rb +0 -71
- data/app/interactors/spree_cm_commissioner/check_in_destroyer.rb +0 -43
- data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +0 -10
- data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +0 -10
- data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +0 -13
- data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +0 -10
- data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +0 -10
- data/app/models/concerns/spree_cm_commissioner/route_trip_count_callbacks.rb +0 -48
- data/app/models/spree_cm_commissioner/trip_connection.rb +0 -36
- data/app/models/spree_cm_commissioner/vendor_route.rb +0 -9
- data/app/services/spree_cm_commissioner/routes/decrement_previous_trip_count.rb +0 -30
- data/app/services/spree_cm_commissioner/routes/decrement_trip_count.rb +0 -33
- data/app/services/spree_cm_commissioner/routes/increment_trip_count.rb +0 -33
- data/lib/spree_cm_commissioner/test_helper/factories/trip_connection_factory.rb +0 -6
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
FactoryBot.define do
|
|
2
2
|
factory :cm_transit_vendor, parent: :vendor do
|
|
3
|
+
from_email { FFaker::Internet.email } # required only when tenant exist.
|
|
3
4
|
state { :active }
|
|
4
5
|
primary_product_type { :transit }
|
|
5
6
|
end
|
|
6
7
|
|
|
7
8
|
factory :cm_vendor, parent: :vendor do
|
|
8
9
|
sequence(:name) { |n| "#{FFaker::Company.name} #{n}#{Kernel.rand(9999)}" }
|
|
10
|
+
from_email { FFaker::Internet.email } # required only when tenant exist.
|
|
9
11
|
state { :active }
|
|
10
12
|
default_state_id { Spree::State.first&.id }
|
|
11
13
|
primary_product_type { :ecommerce }
|
|
@@ -7,6 +7,17 @@ FactoryBot.define do
|
|
|
7
7
|
position { FFaker::Number.number }
|
|
8
8
|
|
|
9
9
|
place_type { :location }
|
|
10
|
+
|
|
11
|
+
transient do
|
|
12
|
+
place_name { nil }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
before :create do |vendor_place, evaluator|
|
|
16
|
+
if evaluator.place_name.present?
|
|
17
|
+
vendor_place.place.name = evaluator.place_name
|
|
18
|
+
vendor_place.place.save!
|
|
19
|
+
end
|
|
20
|
+
end
|
|
10
21
|
end
|
|
11
22
|
|
|
12
23
|
factory :cm_vendor_place, class: SpreeCmCommissioner::VendorPlace do
|
|
@@ -30,5 +41,16 @@ FactoryBot.define do
|
|
|
30
41
|
place_type { :location }
|
|
31
42
|
location { nil }
|
|
32
43
|
end
|
|
44
|
+
|
|
45
|
+
transient do
|
|
46
|
+
place_name { nil }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
before :create do |vendor_place, evaluator|
|
|
50
|
+
if evaluator.place_name.present?
|
|
51
|
+
vendor_place.place.name = evaluator.place_name
|
|
52
|
+
vendor_place.place.save!
|
|
53
|
+
end
|
|
54
|
+
end
|
|
33
55
|
end
|
|
34
56
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module SpreeCmCommissioner::Transit
|
|
2
|
+
class RouteStop
|
|
3
|
+
attr_accessor :location_id, :vendor_place_id, :type, :sequence, :vendor_place
|
|
4
|
+
|
|
5
|
+
STOP_TYPES = %i[branch stop].freeze
|
|
6
|
+
|
|
7
|
+
def initialize(options = {})
|
|
8
|
+
@location_id = options[:location_id]
|
|
9
|
+
@type = options[:type]&.to_sym
|
|
10
|
+
@sequence = options[:sequence]
|
|
11
|
+
|
|
12
|
+
# vendor place is either stop or branch
|
|
13
|
+
@vendor_place_id = options[:vendor_place_id]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.from_hash(hash)
|
|
17
|
+
hash = hash.symbolize_keys
|
|
18
|
+
|
|
19
|
+
new(
|
|
20
|
+
location_id: hash[:location_id],
|
|
21
|
+
vendor_place_id: hash[:vendor_place_id],
|
|
22
|
+
type: hash[:type],
|
|
23
|
+
sequence: hash[:sequence]
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_h
|
|
28
|
+
{
|
|
29
|
+
location_id: @location_id,
|
|
30
|
+
vendor_place_id: @vendor_place_id,
|
|
31
|
+
type: @type,
|
|
32
|
+
sequence: @sequence
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def branch?
|
|
37
|
+
@type == :branch
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def stop?
|
|
41
|
+
@type == :stop
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def valid?
|
|
45
|
+
errors.empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def errors
|
|
49
|
+
@errors ||= []
|
|
50
|
+
@errors.clear
|
|
51
|
+
|
|
52
|
+
@errors << 'location_id is required' if @location_id.blank?
|
|
53
|
+
@errors << 'vendor_place_id is required' if @vendor_place_id.blank?
|
|
54
|
+
@errors << 'type is required' if @type.blank?
|
|
55
|
+
@errors << 'type must be :branch or :stop' if @type.present? && STOP_TYPES.exclude?(@type)
|
|
56
|
+
@errors << 'sequence is required' if @sequence.nil?
|
|
57
|
+
|
|
58
|
+
@errors
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
# Groups route stops by their location in sequence order
|
|
101
|
+
# Returns an array of hashes where each hash contains:
|
|
102
|
+
# - location: the VendorPlace representing the location
|
|
103
|
+
# - stops: array of route stops at that location, sorted by sequence
|
|
104
|
+
# Example usage: @stops.group_by_sequence
|
|
105
|
+
# {
|
|
106
|
+
# location_object_1 => [stop1, stop2, stop3],
|
|
107
|
+
# location_object_2 => [stop4, stop5]
|
|
108
|
+
# }
|
|
109
|
+
# Stop 0: Location A
|
|
110
|
+
# Stop 1: Location A
|
|
111
|
+
# Stop 2: Location B
|
|
112
|
+
# Stop 3: Location A ← Same location again (For case when the bus has the same origin/destination)
|
|
113
|
+
def group_by_sequence
|
|
114
|
+
return [] if @stops.blank?
|
|
115
|
+
|
|
116
|
+
by_location = by_location_with_data || {}
|
|
117
|
+
|
|
118
|
+
# location_id => location_vendor_place (the "location" vendor_place in the hash key)
|
|
119
|
+
location_by_id = by_location.keys.index_by(&:id)
|
|
120
|
+
|
|
121
|
+
ordered_stops =
|
|
122
|
+
by_location.values
|
|
123
|
+
.flatten
|
|
124
|
+
.compact
|
|
125
|
+
.sort_by { |s| s.sequence.to_i }
|
|
126
|
+
|
|
127
|
+
grouped_stops = []
|
|
128
|
+
current_location_id = nil
|
|
129
|
+
current_group = nil
|
|
130
|
+
|
|
131
|
+
ordered_stops.each do |stop|
|
|
132
|
+
stop_location_id = stop.location_id
|
|
133
|
+
|
|
134
|
+
if stop_location_id != current_location_id
|
|
135
|
+
current_location_id = stop_location_id
|
|
136
|
+
current_group = {
|
|
137
|
+
location: location_by_id[stop_location_id],
|
|
138
|
+
stops: []
|
|
139
|
+
}
|
|
140
|
+
grouped_stops << current_group
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
current_group[:stops] << stop
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
grouped_stops
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def valid?
|
|
150
|
+
errors.empty?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def errors
|
|
154
|
+
@errors = []
|
|
155
|
+
|
|
156
|
+
# Check each stop is valid
|
|
157
|
+
@stops.each_with_index do |stop, index|
|
|
158
|
+
next if stop.valid?
|
|
159
|
+
|
|
160
|
+
stop.errors.each do |error|
|
|
161
|
+
@errors << "Stop #{index}: #{error}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Check for duplicate vendor_place_ids
|
|
166
|
+
vendor_place_ids = @stops.map(&:vendor_place_id)
|
|
167
|
+
duplicates = vendor_place_ids.tally.select { |_, count| count > 1 }.keys
|
|
168
|
+
duplicates.each do |dup_id|
|
|
169
|
+
@errors << "vendor_place_id #{dup_id} appears more than once"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
@errors
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
def multi_leg?
|
|
16
|
+
trip_stops.map(&:location_id).uniq.size > 2
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def total_duration_seconds
|
|
20
|
+
return 0 if trip_stops.size < 2
|
|
21
|
+
|
|
22
|
+
minutes = trip_stops[0...-1].sum { |s| s.duration_in_minutes || 0 }
|
|
23
|
+
minutes * 60
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def valid_data? # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
27
|
+
errors.clear
|
|
28
|
+
|
|
29
|
+
errors.add(:route_id, 'must be present') if route_id.blank?
|
|
30
|
+
errors.add(:trip_stops, 'must be present') if trip_stops.blank? || trip_stops.size < 2
|
|
31
|
+
|
|
32
|
+
first_stop = trip_stops&.first
|
|
33
|
+
last_stop = trip_stops&.last
|
|
34
|
+
|
|
35
|
+
errors.add(:trip_stops, 'first stop must allow boarding') unless first_stop&.allow_boarding?
|
|
36
|
+
errors.add(:trip_stops, 'last stop must allow drop off') unless last_stop&.allow_drop_off
|
|
37
|
+
errors.add(:departure_time, 'missing on first stop') if first_stop&.departure_time.blank?
|
|
38
|
+
|
|
39
|
+
errors.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Groups trip_stops into legs by location changes
|
|
43
|
+
# Separates legs when location_id changes (at branch stops)
|
|
44
|
+
# Break stops (stop_type = :stop) are included but don't trigger separation
|
|
45
|
+
#
|
|
46
|
+
# Example:
|
|
47
|
+
# [PP1, PP2, PTT1(break stop), BTB1, BTB2, PPT2(break stop), POIPET1, POIPET1]
|
|
48
|
+
#
|
|
49
|
+
# Output (legs):
|
|
50
|
+
# [
|
|
51
|
+
# [PP1, PP2, PTT, BTB1, BTB2],
|
|
52
|
+
# [BTB1, BTB2, PPT1, POIPET1, POIPET1]
|
|
53
|
+
# ]
|
|
54
|
+
def trip_stops_grouped_by_leg(stops)
|
|
55
|
+
return [] if trip_stops.empty?
|
|
56
|
+
|
|
57
|
+
legs = []
|
|
58
|
+
current_leg = []
|
|
59
|
+
last_location_id = nil
|
|
60
|
+
|
|
61
|
+
trip_stops.each do |trip_stop|
|
|
62
|
+
stop = stops[trip_stop.stop_id]
|
|
63
|
+
|
|
64
|
+
# If location changed and we have stops in current leg, start a new leg
|
|
65
|
+
current_leg << trip_stop
|
|
66
|
+
if last_location_id.present? && last_location_id != trip_stop.location_id && current_leg.any?
|
|
67
|
+
# Include the first stop of new location in previous leg
|
|
68
|
+
legs << current_leg
|
|
69
|
+
current_leg = [trip_stop]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Update location only for branch stops (not for "just stops")
|
|
73
|
+
last_location_id = trip_stop.location_id if stop.branch?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Add final leg if it has stops
|
|
77
|
+
legs << current_leg if current_leg.any?
|
|
78
|
+
legs
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
|
|
51
|
+
def allow_boarding?
|
|
52
|
+
ActiveModel::Type::Boolean.new.cast(allow_boarding)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def duration_in_minutes
|
|
56
|
+
return nil if duration_in_hours.blank?
|
|
57
|
+
|
|
58
|
+
(duration_in_hours.to_f * 60).to_i
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -23,6 +23,10 @@ require 'spree_cm_commissioner/trip_query_result'
|
|
|
23
23
|
require 'spree_cm_commissioner/cached_inventory_item'
|
|
24
24
|
require 'spree_cm_commissioner/transit/leg'
|
|
25
25
|
require 'spree_cm_commissioner/transit/seat_selection'
|
|
26
|
+
require 'spree_cm_commissioner/transit/route_stop'
|
|
27
|
+
require 'spree_cm_commissioner/transit/route_stop_collection'
|
|
28
|
+
require 'spree_cm_commissioner/transit/trip_form'
|
|
29
|
+
require 'spree_cm_commissioner/transit/trip_stop_form'
|
|
26
30
|
require 'spree_cm_commissioner/intercity_taxi/map_place'
|
|
27
31
|
require 'spree_cm_commissioner/distance'
|
|
28
32
|
|