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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5002e62ca5423aa0f8f048cc8c1f5aa1d5e6ad8cfa432a0db2c0f7d4ef5aabde
4
- data.tar.gz: d640c0e1e081b49f5317491c4bfb1a0a66903db15c22d97354f274d3fa4200b8
3
+ metadata.gz: de104e725d2f24613de706e3246bdc7d316520fac6eb9b21789a6b2f2c45ab06
4
+ data.tar.gz: df640f714603d5d38bb33ad1b7ded9f66ca5126610bf2d8bdeba79890e7ac090
5
5
  SHA512:
6
- metadata.gz: 71efbcfc0e5960c3a8a80eb18791c0095d52f476c5a69a068df695524e5352372070dca03279df3961536ba0de88ffca48bff6c5b53597b29be2b513cf9d9ce3
7
- data.tar.gz: 5c5632425f155a19970e0ea0d61d9e3d492fc56986bef12802158fc808099d4f8e71f2749116712bcbce87f6dc79ef500922fceff5c68d99177cf83a393d2c2f
6
+ metadata.gz: 782d5091a568ece086d739de4c0e8b9e1a00a97302b68d1cc982b8f9a5718fb5c4cfa928f9310427e69e11fd34211c54893104fbacbc8293d8033bf4d2213ad0
7
+ data.tar.gz: 8aeca4178f0bf6ce687b9cd2dffd5d648db14f1c2a5537964e6e728a73801362495d370d32509029408ec7d9d5d8fe88094fbd3da392b61e68446f4ad58b5e68
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.0.3.pre.pre3)
37
+ spree_cm_commissioner (2.0.3.pre.pre5)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -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
- (from_date.to_date..to_date.to_date).to_a
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&.to_date
40
+ from_date&.to_datetime
32
41
  end
33
42
 
34
43
  def checkout_date
35
- return to_date ? to_date.to_date - 1.day : nil if accommodation?
44
+ return to_date ? to_date.to_datetime + 1.day : nil if accommodation?
36
45
 
37
- to_date&.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: -> { public_metadata[:number_of_adults].present? }
7
- validate :validate_total_kids, if: -> { public_metadata[:number_of_kids].present? }
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 remaining_total_guests = [number_of_guests - guests.size, 0].max
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 number_of_adults = public_metadata[:number_of_adults] || (variant.number_of_adults * quantity)
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[:number_of_adults] - (variant.number_of_adults * quantity)
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[:number_of_kids] - (variant.number_of_kids * quantity)
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
- public_metadata[:number_of_adults].present? && public_metadata[:number_of_adults] > variant.number_of_adults * quantity
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
- public_metadata[:number_of_kids].present? && public_metadata[:number_of_kids] > variant.number_of_kids * quantity
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 validate_total_adults
68
- return if public_metadata[:number_of_adults] <= allowed_total_adults
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
- errors.add(:quantity, 'exceed_total_adults')
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
- return if public_metadata[:number_of_kids] <= allowed_total_kids
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
- Spree::Address.prepend(SpreeCmCommissioner::AddressDecorator) unless Spree::Address.included_modules.include?(SpreeCmCommissioner::AddressDecorator)
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)&.to_date
92
- self.to_date = opts.delete(:to_date)&.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,5 @@
1
+ class AddSavedGuestReferenceToCmBlocks < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_reference :cm_guests, :saved_guest, foreign_key: { to_table: :cm_saved_guests }, null: true, index: true, if_not_exists: true
4
+ end
5
+ end
@@ -6,5 +6,8 @@ FactoryBot.define do
6
6
 
7
7
  stop_type { :boarding }
8
8
  stop_name { stop_place.name }
9
+
10
+ arrival_time { Time.current }
11
+ departure_time { Time.current + 10.minutes }
9
12
  end
10
13
  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
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.0.3-pre3'.freeze
2
+ VERSION = '2.0.3-pre5'.freeze
3
3
 
4
4
  module_function
5
5
 
@@ -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.pre3
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-06 00:00:00.000000000 Z
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