spree_cm_commissioner 2.0.3.pre.pre3 → 2.0.3.pre.pre5
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/interactors/spree_cm_commissioner/transit/draft_order_creator.rb +119 -0
- data/app/models/concerns/spree_cm_commissioner/line_item_durationable.rb +13 -4
- data/app/models/concerns/spree_cm_commissioner/line_item_guests_concern.rb +29 -15
- data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +89 -0
- data/app/models/spree_cm_commissioner/address_decorator.rb +22 -2
- data/app/models/spree_cm_commissioner/guest.rb +17 -0
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +3 -2
- data/app/models/spree_cm_commissioner/order_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/saved_guest.rb +13 -0
- data/app/models/spree_cm_commissioner/trip.rb +5 -0
- data/app/models/spree_cm_commissioner/variant_block.rb +8 -0
- data/db/migrate/20250807033035_create_spree_cm_commissioner_saved_guests.rb +23 -0
- data/db/migrate/20250808054835_add_saved_guest_reference_to_cm_blocks.rb +5 -0
- data/lib/spree_cm_commissioner/test_helper/factories/trip_stop_factory.rb +3 -0
- data/lib/spree_cm_commissioner/transit/leg.rb +23 -0
- data/lib/spree_cm_commissioner/transit/seat_selection.rb +19 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +2 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de104e725d2f24613de706e3246bdc7d316520fac6eb9b21789a6b2f2c45ab06
|
4
|
+
data.tar.gz: df640f714603d5d38bb33ad1b7ded9f66ca5126610bf2d8bdeba79890e7ac090
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 782d5091a568ece086d739de4c0e8b9e1a00a97302b68d1cc982b8f9a5718fb5c4cfa928f9310427e69e11fd34211c54893104fbacbc8293d8033bf4d2213ad0
|
7
|
+
data.tar.gz: 8aeca4178f0bf6ce687b9cd2dffd5d648db14f1c2a5537964e6e728a73801362495d370d32509029408ec7d9d5d8fe88094fbd3da392b61e68446f4ad58b5e68
|
data/Gemfile.lock
CHANGED
@@ -0,0 +1,119 @@
|
|
1
|
+
# DraftOrderCreator creates a new order for a entire journey, including outbound and (if present) inbound directions.
|
2
|
+
#
|
3
|
+
# Attributes:
|
4
|
+
# - outbound_date: [Date] The date of outbound transit
|
5
|
+
# - inbound_date: [Date] The date of inbound transit
|
6
|
+
# - outbound_legs: [Array<SpreeCmCommissioner::Transit::Leg>] Outbound transit legs (one for direct, multiple for connecting)
|
7
|
+
# - inbound_legs: [Array<SpreeCmCommissioner::Transit::Leg>] Inbound transit legs (one for direct, multiple for connecting)
|
8
|
+
# - user: [User] The user for whom the order is being created
|
9
|
+
#
|
10
|
+
# Each line item is built with precise from_date and to_date based on legs and trip stop times.
|
11
|
+
# Each line item has guests built corresponding to seat selection blocks.
|
12
|
+
#
|
13
|
+
# Note: inbound_legs and inbound_date are optional; the order may represent a one-way (outbound only) journey.
|
14
|
+
module SpreeCmCommissioner
|
15
|
+
module Transit
|
16
|
+
class DraftOrderCreator < BaseInteractor
|
17
|
+
delegate :outbound_date,
|
18
|
+
:inbound_date,
|
19
|
+
:outbound_legs,
|
20
|
+
:inbound_legs,
|
21
|
+
:user, to: :context
|
22
|
+
|
23
|
+
def call
|
24
|
+
return context.fail!(message: 'Outbound legs are missing') if outbound_legs.blank?
|
25
|
+
|
26
|
+
begin
|
27
|
+
context.order = create_order!
|
28
|
+
rescue StandardError => e
|
29
|
+
context.fail!(message: e.message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_order!
|
34
|
+
order = Spree::Order.new(state: 'cart', use_billing: true, user: user)
|
35
|
+
|
36
|
+
outbound_line_items = build_line_items_for_legs!(order: order, legs: outbound_legs, initial_date: outbound_date)
|
37
|
+
inbound_line_items = inbound_legs.blank? ? [] : build_line_items_for_legs!(order: order, legs: inbound_legs, initial_date: inbound_date)
|
38
|
+
|
39
|
+
outbound_qty = outbound_line_items.sum(&:quantity)
|
40
|
+
inbound_qty = inbound_line_items.sum(&:quantity)
|
41
|
+
|
42
|
+
raise StandardError, 'Outbound & inbound line item quantities do not match' if inbound_line_items.any? && outbound_qty != inbound_qty
|
43
|
+
raise StandardError, order.errors.full_messages.to_sentence unless order.save
|
44
|
+
|
45
|
+
order
|
46
|
+
end
|
47
|
+
|
48
|
+
# Build line items for each leg, using the previous leg's to_date as the next leg's date,
|
49
|
+
# or the initial date for the first leg.
|
50
|
+
def build_line_items_for_legs!(order:, legs:, initial_date:)
|
51
|
+
all_line_items = []
|
52
|
+
current_leg_date = initial_date
|
53
|
+
|
54
|
+
legs.each do |leg|
|
55
|
+
leg_line_items = build_line_items_for!(leg, order, current_leg_date)
|
56
|
+
all_line_items.concat(leg_line_items)
|
57
|
+
current_leg_date = leg_line_items.last.to_date.to_date if leg_line_items.any?
|
58
|
+
end
|
59
|
+
|
60
|
+
all_line_items
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_line_items_for!(leg, order, date)
|
64
|
+
trip = SpreeCmCommissioner::Trip.find(leg.trip_id)
|
65
|
+
trip_stops = trip.trip_stops.where(id: [leg.boarding_trip_stop_id, leg.drop_off_trip_stop_id]).index_by(&:id)
|
66
|
+
|
67
|
+
from_date, to_date = calculate_line_item_duration!(
|
68
|
+
date: date,
|
69
|
+
departure_time: trip_stops[leg.boarding_trip_stop_id].departure_time,
|
70
|
+
arrival_time: trip_stops[leg.drop_off_trip_stop_id].arrival_time
|
71
|
+
)
|
72
|
+
|
73
|
+
leg.seat_selections.group_by(&:variant_id).map do |variant_id, seat_selections|
|
74
|
+
line_item = order.line_items.new(
|
75
|
+
product_type: :transit,
|
76
|
+
from_date: from_date,
|
77
|
+
to_date: to_date,
|
78
|
+
variant_id: variant_id,
|
79
|
+
quantity: seat_selections.sum(&:quantity),
|
80
|
+
private_metadata: {
|
81
|
+
direction: leg.direction,
|
82
|
+
trip_id: leg.trip_id.to_s,
|
83
|
+
boarding_trip_stop_id: leg.boarding_trip_stop_id.to_s,
|
84
|
+
drop_off_trip_stop_id: leg.drop_off_trip_stop_id.to_s
|
85
|
+
}
|
86
|
+
)
|
87
|
+
|
88
|
+
build_guests!(line_item, seat_selections)
|
89
|
+
|
90
|
+
line_item
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def build_guests!(line_item, seat_selections)
|
95
|
+
block_ids = seat_selections.flat_map(&:block_ids)
|
96
|
+
|
97
|
+
if block_ids.any? && block_ids.size != line_item.quantity
|
98
|
+
raise StandardError, "Number of blocks (#{block_ids.size}) does not match quantity (#{line_item.quantity})"
|
99
|
+
end
|
100
|
+
|
101
|
+
Array.new(line_item.quantity) do |index|
|
102
|
+
line_item.guests.new(block_id: block_ids[index])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def calculate_line_item_duration!(date:, departure_time:, arrival_time:)
|
107
|
+
raise StandardError, 'Departure or arrival time in trip stop is missing' if departure_time.blank? || arrival_time.blank?
|
108
|
+
raise StandardError, 'Arrival time cannot be before departure time' if arrival_time < departure_time
|
109
|
+
|
110
|
+
trip_duration_in_seconds = arrival_time - departure_time
|
111
|
+
|
112
|
+
from_date = date.to_datetime.change(hour: departure_time.hour, min: departure_time.min, sec: departure_time.sec)
|
113
|
+
to_date = from_date + trip_duration_in_seconds.seconds
|
114
|
+
|
115
|
+
[from_date, to_date]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -23,18 +23,27 @@ module SpreeCmCommissioner
|
|
23
23
|
|
24
24
|
def date_range
|
25
25
|
return [] unless date_present?
|
26
|
+
return [] unless permanent_stock?
|
26
27
|
|
27
|
-
(
|
28
|
+
# For transit products (e.g., bus, train), only return the departure day,
|
29
|
+
# as the booking represents a single trip rather than a duration.
|
30
|
+
return [from_date.to_date] if transit?
|
31
|
+
|
32
|
+
# For products with a stay or usage period (e.g., hotel, car rental, activities),
|
33
|
+
# return the full date range from start to end, inclusive.
|
34
|
+
return (from_date.to_date..to_date.to_date).to_a if service? || accommodation?
|
35
|
+
|
36
|
+
[]
|
28
37
|
end
|
29
38
|
|
30
39
|
def checkin_date
|
31
|
-
from_date&.
|
40
|
+
from_date&.to_datetime
|
32
41
|
end
|
33
42
|
|
34
43
|
def checkout_date
|
35
|
-
return to_date ? to_date.
|
44
|
+
return to_date ? to_date.to_datetime + 1.day : nil if accommodation?
|
36
45
|
|
37
|
-
to_date&.
|
46
|
+
to_date&.to_datetime
|
38
47
|
end
|
39
48
|
|
40
49
|
private
|
@@ -1,13 +1,21 @@
|
|
1
|
+
# Number of guests are depend on the variant, but user is allowed to input custom number of guests
|
2
|
+
# which will be stored in public_metadata.
|
3
|
+
#
|
4
|
+
# metadata {
|
5
|
+
# 'number_of_adults': 2,
|
6
|
+
# 'number_of_kids': 1,
|
7
|
+
# }
|
1
8
|
module SpreeCmCommissioner
|
2
9
|
module LineItemGuestsConcern
|
3
10
|
extend ActiveSupport::Concern
|
4
11
|
|
5
12
|
included do
|
6
|
-
validate :validate_total_adults, if: -> {
|
7
|
-
validate :validate_total_kids, if: -> {
|
13
|
+
validate :validate_total_adults, if: -> { valid_integer_metadata?('number_of_adults') }
|
14
|
+
validate :validate_total_kids, if: -> { valid_integer_metadata?('number_of_kids') }
|
8
15
|
end
|
9
16
|
|
10
|
-
def
|
17
|
+
def number_of_adults = public_metadata['number_of_adults']&.to_i || (variant.number_of_adults * quantity)
|
18
|
+
def number_of_kids = public_metadata['number_of_kids']&.to_i || (variant.number_of_kids * quantity)
|
11
19
|
def number_of_guests = number_of_adults + number_of_kids
|
12
20
|
|
13
21
|
def allowed_extra_adults = variant.allowed_extra_adults * quantity
|
@@ -16,8 +24,7 @@ module SpreeCmCommissioner
|
|
16
24
|
def allowed_total_adults = (variant.number_of_adults * quantity) + allowed_extra_adults
|
17
25
|
def allowed_total_kids = (variant.number_of_kids * quantity) + allowed_extra_kids
|
18
26
|
|
19
|
-
def
|
20
|
-
def number_of_kids = public_metadata[:number_of_kids] || (variant.number_of_kids * quantity)
|
27
|
+
def remaining_total_guests = [number_of_guests - guests.size, 0].max
|
21
28
|
|
22
29
|
def generate_remaining_guests
|
23
30
|
return if remaining_total_guests.zero?
|
@@ -28,15 +35,17 @@ module SpreeCmCommissioner
|
|
28
35
|
end
|
29
36
|
|
30
37
|
def extra_adults
|
38
|
+
return 0 unless valid_integer_metadata?('number_of_adults')
|
31
39
|
return 0 unless extra_adults?
|
32
40
|
|
33
|
-
public_metadata[
|
41
|
+
public_metadata['number_of_adults'].to_i - (variant.number_of_adults * quantity)
|
34
42
|
end
|
35
43
|
|
36
44
|
def extra_kids
|
45
|
+
return 0 unless valid_integer_metadata?('number_of_kids')
|
37
46
|
return 0 unless extra_kids?
|
38
47
|
|
39
|
-
public_metadata[
|
48
|
+
public_metadata['number_of_kids'].to_i - (variant.number_of_kids * quantity)
|
40
49
|
end
|
41
50
|
|
42
51
|
def guest_options
|
@@ -55,25 +64,30 @@ module SpreeCmCommissioner
|
|
55
64
|
end
|
56
65
|
|
57
66
|
def extra_adults?
|
58
|
-
|
67
|
+
return false unless valid_integer_metadata?('number_of_adults')
|
68
|
+
|
69
|
+
public_metadata['number_of_adults'].to_i > variant.number_of_adults * quantity
|
59
70
|
end
|
60
71
|
|
61
72
|
def extra_kids?
|
62
|
-
|
73
|
+
return false unless valid_integer_metadata?('number_of_kids')
|
74
|
+
|
75
|
+
public_metadata['number_of_kids'].to_i > variant.number_of_kids * quantity
|
63
76
|
end
|
64
77
|
|
65
78
|
private
|
66
79
|
|
67
|
-
def
|
68
|
-
|
80
|
+
def valid_integer_metadata?(key)
|
81
|
+
value = public_metadata[key.to_s]
|
82
|
+
value.present? && value.to_s.match?(/\A-?\d+\z/)
|
83
|
+
end
|
69
84
|
|
70
|
-
|
85
|
+
def validate_total_adults
|
86
|
+
errors.add(:quantity, 'exceed_total_adults') if public_metadata['number_of_adults'].to_i > allowed_total_adults
|
71
87
|
end
|
72
88
|
|
73
89
|
def validate_total_kids
|
74
|
-
|
75
|
-
|
76
|
-
errors.add(:quantity, 'exceed_total_kids')
|
90
|
+
errors.add(:quantity, 'exceed_total_kids') if public_metadata['number_of_kids'].to_i > allowed_total_kids
|
77
91
|
end
|
78
92
|
end
|
79
93
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# This concern allow line item to have trip-related metadata.
|
2
|
+
#
|
3
|
+
# Metadata structure:
|
4
|
+
# {
|
5
|
+
# 'direction': 'outbound' or 'inbound',
|
6
|
+
# 'trip_id': 123,
|
7
|
+
# 'boarding_trip_stop_id': 456,
|
8
|
+
# 'drop_off_trip_stop_id': 789
|
9
|
+
# }
|
10
|
+
#
|
11
|
+
# Direction:
|
12
|
+
# - "outbound": The trip is moving away from the origin (e.g., A → B → C → D).
|
13
|
+
# This term is more flexible than "departure" and applies to both direct and connected trips.
|
14
|
+
# - "inbound": The trip is returning toward the origin (e.g., D → C → B → A).
|
15
|
+
# This term is more flexible than "return" and applies to both direct and connected trips.
|
16
|
+
module SpreeCmCommissioner
|
17
|
+
module LineItemTransitable
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
DIRECTION = %w[outbound inbound].freeze
|
21
|
+
|
22
|
+
TripKey = Struct.new(:trip_id, :direction, :boarding_trip_stop_id, :drop_off_trip_stop_id)
|
23
|
+
|
24
|
+
included do
|
25
|
+
validates :direction, inclusion: { in: DIRECTION }, allow_nil: true
|
26
|
+
validates :trip_id, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
|
27
|
+
validates :boarding_trip_stop_id, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
|
28
|
+
validates :drop_off_trip_stop_id, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
|
29
|
+
|
30
|
+
# Groups LineItems by trip fields. Some fields can be null,
|
31
|
+
# so use it with caution. Output:
|
32
|
+
# {
|
33
|
+
# $<TripKey:> => [<LineItem id:1>]
|
34
|
+
# $<TripKey:> => [<LineItem id:2>]
|
35
|
+
# $<TripKey:> => [<LineItem id:3>, <LineItem id:4>]
|
36
|
+
# }
|
37
|
+
def self.group_by_trip_key
|
38
|
+
all.group_by do |line_item|
|
39
|
+
TripKey.new(
|
40
|
+
line_item.trip_id,
|
41
|
+
line_item.direction,
|
42
|
+
line_item.boarding_trip_stop_id,
|
43
|
+
line_item.drop_off_trip_stop_id
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def outbound? = direction == 'outbound'
|
50
|
+
def inbound? = direction == 'inbound'
|
51
|
+
|
52
|
+
def direction = private_metadata['direction']
|
53
|
+
def trip_id = private_metadata['trip_id']&.to_i
|
54
|
+
def boarding_trip_stop_id = private_metadata['boarding_trip_stop_id']&.to_i
|
55
|
+
def drop_off_trip_stop_id = private_metadata['drop_off_trip_stop_id']&.to_i
|
56
|
+
|
57
|
+
def direction=(value)
|
58
|
+
if value.nil?
|
59
|
+
private_metadata.delete('direction')
|
60
|
+
else
|
61
|
+
private_metadata['direction'] = value.to_s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def trip_id=(value)
|
66
|
+
if value.nil?
|
67
|
+
private_metadata.delete('trip_id')
|
68
|
+
else
|
69
|
+
private_metadata['trip_id'] = value.to_s
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def boarding_trip_stop_id=(value)
|
74
|
+
if value.nil?
|
75
|
+
private_metadata.delete('boarding_trip_stop_id')
|
76
|
+
else
|
77
|
+
private_metadata['boarding_trip_stop_id'] = value.to_s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def drop_off_trip_stop_id=(value)
|
82
|
+
if value.nil?
|
83
|
+
private_metadata.delete('drop_off_trip_stop_id')
|
84
|
+
else
|
85
|
+
private_metadata['drop_off_trip_stop_id'] = value.to_s
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -1,10 +1,30 @@
|
|
1
1
|
module SpreeCmCommissioner
|
2
2
|
module AddressDecorator
|
3
3
|
def self.prepended(base)
|
4
|
-
# Field is not mandatory
|
5
4
|
base.enum gender: { :not_selected => 0, :male => 1, :female => 2, :other => 3 }
|
5
|
+
|
6
|
+
base.validates :address1, presence: true, if: :require_address1?
|
7
|
+
base.validates :city, presence: true, if: :require_city?
|
8
|
+
base.validates :country, presence: true, if: :require_country?
|
9
|
+
end
|
10
|
+
|
11
|
+
def require_address1?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def require_city?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def require_country?
|
20
|
+
false
|
6
21
|
end
|
7
22
|
end
|
8
23
|
end
|
9
24
|
|
10
|
-
|
25
|
+
unless Spree::Address.included_modules.include?(SpreeCmCommissioner::AddressDecorator)
|
26
|
+
# remove validators for the specified attributes
|
27
|
+
%i[address1 city country].each { |attr| Spree::Address._validators.delete(attr) }
|
28
|
+
|
29
|
+
Spree::Address.prepend(SpreeCmCommissioner::AddressDecorator)
|
30
|
+
end
|
@@ -31,6 +31,7 @@ module SpreeCmCommissioner
|
|
31
31
|
belongs_to :occupation, class_name: 'Spree::Taxon'
|
32
32
|
belongs_to :nationality, class_name: 'Spree::Taxon'
|
33
33
|
belongs_to :block, class_name: 'SpreeCmCommissioner::Block', optional: true
|
34
|
+
belongs_to :saved_guest, class_name: 'SpreeCmCommissioner::SavedGuest', optional: true
|
34
35
|
|
35
36
|
has_one :reserved_block, class_name: 'SpreeCmCommissioner::ReservedBlock'
|
36
37
|
|
@@ -61,6 +62,8 @@ module SpreeCmCommissioner
|
|
61
62
|
validates :seat_number, uniqueness: { scope: :event_id }, allow_nil: true, if: -> { event_id.present? }
|
62
63
|
validates :bib_index, uniqueness: true, allow_nil: true
|
63
64
|
|
65
|
+
validate :validate_block, if: :should_validate_block?
|
66
|
+
|
64
67
|
self.whitelisted_ransackable_associations = %w[id_card event line_item]
|
65
68
|
self.whitelisted_ransackable_attributes = %w[first_name last_name phone_number gender contact occupation_id card_type created_at check_in_status]
|
66
69
|
|
@@ -294,6 +297,20 @@ module SpreeCmCommissioner
|
|
294
297
|
end
|
295
298
|
end
|
296
299
|
|
300
|
+
# validate only if block_id is present AND (new record OR block_id is changing)
|
301
|
+
# goal: don't bother re-validating when block_id stays the same
|
302
|
+
def should_validate_block?
|
303
|
+
block_id.present? && (new_record? || will_save_change_to_block_id?)
|
304
|
+
end
|
305
|
+
|
306
|
+
def validate_block
|
307
|
+
return if block_id.blank?
|
308
|
+
return if line_item.blank? || line_item.variant.blank?
|
309
|
+
return if line_item.variant.variant_blocks.exists?(block_id: block_id)
|
310
|
+
|
311
|
+
errors.add(:block, "does not exist in variant #{line_item.variant_id}")
|
312
|
+
end
|
313
|
+
|
297
314
|
def preload_order_block_ids
|
298
315
|
return if line_item.blank?
|
299
316
|
return if line_item.order.blank?
|
@@ -65,6 +65,7 @@ module SpreeCmCommissioner
|
|
65
65
|
base.include SpreeCmCommissioner::LineItemDurationable
|
66
66
|
base.include SpreeCmCommissioner::LineItemsFilterScope
|
67
67
|
base.include SpreeCmCommissioner::LineItemGuestsConcern
|
68
|
+
base.include SpreeCmCommissioner::LineItemTransitable
|
68
69
|
base.include SpreeCmCommissioner::ProductType
|
69
70
|
base.include SpreeCmCommissioner::ProductDelegation
|
70
71
|
base.include SpreeCmCommissioner::KycBitwise
|
@@ -88,8 +89,8 @@ module SpreeCmCommissioner
|
|
88
89
|
# these fields can be null during this process below.
|
89
90
|
# it is needed for permanent_stock?, date_range to work.
|
90
91
|
self.product_type = variant.product_type
|
91
|
-
self.from_date = opts.delete(:from_date)&.
|
92
|
-
self.to_date = opts.delete(:to_date)&.
|
92
|
+
self.from_date = opts.delete(:from_date)&.to_datetime
|
93
|
+
self.to_date = opts.delete(:to_date)&.to_datetime
|
93
94
|
|
94
95
|
base_price = variant.price_in(currency)
|
95
96
|
|
@@ -43,6 +43,7 @@ module SpreeCmCommissioner
|
|
43
43
|
base.has_many :vendors, through: :products, class_name: 'Spree::Vendor'
|
44
44
|
base.has_many :taxons, through: :products, class_name: 'Spree::Taxon'
|
45
45
|
base.has_many :guests, through: :line_items, class_name: 'SpreeCmCommissioner::Guest'
|
46
|
+
base.has_many :saved_guests, -> { distinct }, through: :guests, class_name: 'SpreeCmCommissioner::SavedGuest'
|
46
47
|
base.has_many :blocks, through: :guests, class_name: 'SpreeCmCommissioner::Block', source: :block
|
47
48
|
base.has_many :reserved_blocks, through: :guests, class_name: 'SpreeCmCommissioner::ReservedBlock'
|
48
49
|
base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variants
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Reusable guest profile, can be linked to a user or be nil for anonymous checkouts.
|
2
|
+
# Orders associate to saved_guest through guests. If an order later associates to a user, the saved_guest is assigned to that user.
|
3
|
+
module SpreeCmCommissioner
|
4
|
+
class SavedGuest < Base
|
5
|
+
enum gender: { :other => 0, :male => 1, :female => 2 }
|
6
|
+
|
7
|
+
belongs_to :user, class_name: 'Spree::User', optional: true
|
8
|
+
belongs_to :occupation, class_name: 'Spree::Taxon', optional: true
|
9
|
+
belongs_to :nationality, class_name: 'Spree::Taxon', optional: true
|
10
|
+
|
11
|
+
has_many :guests, class_name: 'SpreeCmCommissioner::Guest', dependent: :nullify
|
12
|
+
end
|
13
|
+
end
|
@@ -17,8 +17,13 @@ module SpreeCmCommissioner
|
|
17
17
|
has_many :blazer_queries, through: :trip_blazer_queries, source: :blazer_query, class_name: 'Blazer::Query'
|
18
18
|
|
19
19
|
has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy
|
20
|
+
has_many :boarding_trip_stops, -> { boarding }, class_name: 'SpreeCmCommissioner::TripStop'
|
21
|
+
has_many :drop_off_trip_stops, -> { drop_off }, class_name: 'SpreeCmCommissioner::TripStop'
|
22
|
+
|
20
23
|
has_many :variants, through: :product
|
21
24
|
has_many :inventory_items, through: :variants
|
25
|
+
has_many :variant_blocks, through: :variants
|
26
|
+
has_many :blocks, through: :variant_blocks
|
22
27
|
|
23
28
|
validates :departure_time, presence: true
|
24
29
|
validates :duration, numericality: { greater_than: 0 }
|
@@ -5,5 +5,13 @@ module SpreeCmCommissioner
|
|
5
5
|
belongs_to :block, class_name: 'SpreeCmCommissioner::Block', optional: false
|
6
6
|
|
7
7
|
validates :block_id, uniqueness: { scope: %i[variant_id trip_id] }
|
8
|
+
|
9
|
+
before_validation :set_trip, if: -> { trip_id.blank? }
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def set_trip
|
14
|
+
self.trip = variant&.product&.trip
|
15
|
+
end
|
8
16
|
end
|
9
17
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CreateSpreeCmCommissionerSavedGuests < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :cm_saved_guests, if_not_exists: true do |t|
|
4
|
+
t.string :first_name
|
5
|
+
t.string :last_name
|
6
|
+
|
7
|
+
t.date :dob
|
8
|
+
t.integer :age
|
9
|
+
t.integer :gender
|
10
|
+
|
11
|
+
t.string :email
|
12
|
+
t.string :country_code, limit: 5
|
13
|
+
t.string :phone_number, limit: 50
|
14
|
+
t.string :intel_phone_number
|
15
|
+
|
16
|
+
t.references :user, foreign_key: { to_table: :spree_users }, null: true
|
17
|
+
t.references :nationality, foreign_key: { to_table: :spree_taxons }, null: true
|
18
|
+
t.references :occupation, foreign_key: { to_table: :spree_taxons }, null: true
|
19
|
+
|
20
|
+
t.timestamps
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SpreeCmCommissioner::Transit
|
2
|
+
class Leg
|
3
|
+
attr_accessor :direction, :trip_id, :boarding_trip_stop_id, :drop_off_trip_stop_id, :seat_selections
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@direction = options[:direction]
|
7
|
+
@trip_id = options[:trip_id]
|
8
|
+
@boarding_trip_stop_id = options[:boarding_trip_stop_id]
|
9
|
+
@drop_off_trip_stop_id = options[:drop_off_trip_stop_id]
|
10
|
+
@seat_selections = options[:seat_selections] || []
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_hash(hash)
|
14
|
+
new(
|
15
|
+
direction: hash[:direction], # outbound / inbound
|
16
|
+
trip_id: hash[:trip_id],
|
17
|
+
boarding_trip_stop_id: hash[:boarding_trip_stop_id],
|
18
|
+
drop_off_trip_stop_id: hash[:drop_off_trip_stop_id],
|
19
|
+
seat_selections: (hash[:seat_selections] || []).map { |seat_selection| SeatSelection.from_hash(seat_selection) }
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SpreeCmCommissioner::Transit
|
2
|
+
class SeatSelection
|
3
|
+
attr_accessor :variant_id, :quantity, :block_ids
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@variant_id = options[:variant_id]
|
7
|
+
@quantity = options[:quantity]
|
8
|
+
@block_ids = options[:block_ids] || []
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.from_hash(hash)
|
12
|
+
new(
|
13
|
+
variant_id: hash[:variant_id],
|
14
|
+
quantity: hash[:quantity],
|
15
|
+
block_ids: hash[:block_ids] || [] # empty when no seat selection.
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -20,6 +20,8 @@ require 'spree_cm_commissioner/user_session_jwt_token'
|
|
20
20
|
require 'spree_cm_commissioner/trip_result'
|
21
21
|
require 'spree_cm_commissioner/trip_query_result'
|
22
22
|
require 'spree_cm_commissioner/cached_inventory_item'
|
23
|
+
require 'spree_cm_commissioner/transit/leg'
|
24
|
+
require 'spree_cm_commissioner/transit/seat_selection'
|
23
25
|
|
24
26
|
require 'activerecord_multi_tenant'
|
25
27
|
require 'google/cloud/recaptcha_enterprise'
|
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.0.3.pre.
|
4
|
+
version: 2.0.3.pre.pre5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- You
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: spree
|
@@ -1195,6 +1195,7 @@ files:
|
|
1195
1195
|
- app/interactors/spree_cm_commissioner/telegram_web_app_vendor_user_authorizer.rb
|
1196
1196
|
- app/interactors/spree_cm_commissioner/telegram_web_app_vendor_user_checker.rb
|
1197
1197
|
- app/interactors/spree_cm_commissioner/transactional_email_sender.rb
|
1198
|
+
- app/interactors/spree_cm_commissioner/transit/draft_order_creator.rb
|
1198
1199
|
- app/interactors/spree_cm_commissioner/unique_device_token_cron_executor.rb
|
1199
1200
|
- app/interactors/spree_cm_commissioner/update_payment_gateway_status.rb
|
1200
1201
|
- app/interactors/spree_cm_commissioner/user_contact_updater.rb
|
@@ -1279,6 +1280,7 @@ files:
|
|
1279
1280
|
- app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb
|
1280
1281
|
- app/models/concerns/spree_cm_commissioner/line_item_durationable.rb
|
1281
1282
|
- app/models/concerns/spree_cm_commissioner/line_item_guests_concern.rb
|
1283
|
+
- app/models/concerns/spree_cm_commissioner/line_item_transitable.rb
|
1282
1284
|
- app/models/concerns/spree_cm_commissioner/line_items_filter_scope.rb
|
1283
1285
|
- app/models/concerns/spree_cm_commissioner/metafield.rb
|
1284
1286
|
- app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb
|
@@ -1427,6 +1429,7 @@ files:
|
|
1427
1429
|
- app/models/spree_cm_commissioner/role_permission.rb
|
1428
1430
|
- app/models/spree_cm_commissioner/s3_presigned_url.rb
|
1429
1431
|
- app/models/spree_cm_commissioner/s3_presigned_url_builder.rb
|
1432
|
+
- app/models/spree_cm_commissioner/saved_guest.rb
|
1430
1433
|
- app/models/spree_cm_commissioner/seat_layout.rb
|
1431
1434
|
- app/models/spree_cm_commissioner/seat_section.rb
|
1432
1435
|
- app/models/spree_cm_commissioner/seats/blocks_canceler.rb
|
@@ -2681,6 +2684,8 @@ files:
|
|
2681
2684
|
- db/migrate/20250721080738_add_vehicle_type_to_cm_vehicles.rb
|
2682
2685
|
- db/migrate/20250725092713_add_block_type_to_cm_blocks.rb
|
2683
2686
|
- db/migrate/20250731062816_add_departure_time_and_arrival_time_to_trip_stop.rb
|
2687
|
+
- db/migrate/20250807033035_create_spree_cm_commissioner_saved_guests.rb
|
2688
|
+
- db/migrate/20250808054835_add_saved_guest_reference_to_cm_blocks.rb
|
2684
2689
|
- docker-compose.yml
|
2685
2690
|
- docs/option_types/attr_types.md
|
2686
2691
|
- docs/private_key.pem
|
@@ -2790,6 +2795,8 @@ files:
|
|
2790
2795
|
- lib/spree_cm_commissioner/test_helper/factories/video_on_demand_factory.rb
|
2791
2796
|
- lib/spree_cm_commissioner/test_helper/factories/webhook_subscriber_factory.rb
|
2792
2797
|
- lib/spree_cm_commissioner/test_helper/factories/webhook_subscriber_rule_factory.rb
|
2798
|
+
- lib/spree_cm_commissioner/transit/leg.rb
|
2799
|
+
- lib/spree_cm_commissioner/transit/seat_selection.rb
|
2793
2800
|
- lib/spree_cm_commissioner/trip_query_result.rb
|
2794
2801
|
- lib/spree_cm_commissioner/trip_result.rb
|
2795
2802
|
- lib/spree_cm_commissioner/user_session_jwt_token.rb
|