spree_cm_commissioner 2.1.3 → 2.1.4.pre.pre1
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/api/v2/storefront/seat_layouts_controller.rb +36 -0
- data/app/{models/concerns → interactors}/spree_cm_commissioner/guest_seat_updater.rb +1 -1
- data/app/interactors/spree_cm_commissioner/trip_clone_creator.rb +1 -0
- data/app/interactors/spree_cm_commissioner/variant_block_updater.rb +1 -0
- data/app/mailers/spree_cm_commissioner/guest_mailer.rb +18 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +14 -0
- data/app/models/spree_cm_commissioner/block.rb +3 -5
- data/app/models/spree_cm_commissioner/check_in.rb +7 -1
- data/app/models/spree_cm_commissioner/check_in_rule.rb +6 -0
- data/app/models/spree_cm_commissioner/check_in_session.rb +16 -0
- data/app/models/spree_cm_commissioner/guest.rb +26 -1
- data/app/models/spree_cm_commissioner/import.rb +3 -1
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +2 -1
- data/app/models/spree_cm_commissioner/seat_layout.rb +17 -2
- data/app/models/spree_cm_commissioner/seat_section.rb +1 -1
- data/app/models/spree_cm_commissioner/stock/line_item_availability_checker.rb +2 -2
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +13 -0
- data/app/models/spree_cm_commissioner/trip.rb +37 -5
- data/app/models/spree_cm_commissioner/user_decorator.rb +3 -0
- data/app/models/spree_cm_commissioner/variant_decorator.rb +4 -3
- data/app/models/spree_cm_commissioner/vehicle.rb +11 -0
- data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +5 -0
- data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +3 -0
- data/app/serializers/spree/v2/storefront/user_serializer_decorator.rb +1 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/block_serializer.rb +1 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/trip_serializer.rb +1 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/trip_variant_serializer.rb +0 -2
- data/app/serializers/spree_cm_commissioner/v2/storefront/trip_vehicle_serializer.rb +0 -2
- data/app/services/spree_cm_commissioner/imports/create_order_service.rb +27 -12
- data/app/views/spree/admin/shared/_vectors_option_type_tabs.html.erb +8 -2
- data/app/views/spree/admin/vectors/icons/index.html.erb +11 -7
- data/app/views/spree_cm_commissioner/guest_mailer/_greeting.html.erb +14 -0
- data/app/views/spree_cm_commissioner/guest_mailer/_mailer_stylesheets.html.erb +213 -0
- data/app/views/spree_cm_commissioner/guest_mailer/send_ticket_to_guest.html.erb +15 -0
- data/app/views/spree_cm_commissioner/layouts/guest_mailer.html.erb +35 -0
- data/config/locales/en.yml +2 -0
- data/config/locales/km.yml +2 -0
- data/config/routes.rb +1 -2
- data/db/migrate/20250827022503_create_spree_cm_commissioner_sessions.rb +14 -0
- data/db/migrate/20250827022757_create_spree_cm_commissioner_check_in_rules.rb +13 -0
- data/db/migrate/20250827032901_add_check_in_session_to_check_ins.rb +5 -0
- data/db/migrate/20250827083828_add_unique_index_on_guest_id_check_in_session_id_to_cm_check_ins.rb +11 -0
- data/db/migrate/20250903085931_add_importable_to_cm_imports.rb +5 -0
- data/db/migrate/20250911100738_drop_cm_variant_blocks.rb +5 -0
- data/db/migrate/20250911100835_add_variant_id_to_cm_blocks.rb +5 -0
- data/db/migrate/20250911174607_add_metadata_columns_to_cm_vehicles.rb +11 -0
- data/db/migrate/20250911174653_add_metadata_columns_to_cm_trips.rb +11 -0
- data/lib/spree_cm_commissioner/test_helper/factories/check_in_session_factory.rb +11 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +23 -8
- data/app/models/spree_cm_commissioner/variant_block.rb +0 -17
- data/app/serializers/spree_cm_commissioner/v2/storefront/variant_block_serializer.rb +0 -9
- data/lib/spree_cm_commissioner/test_helper/factories/variant_block_factory.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53fa91ddf6ab2cbf7d0fecc25664116e5e8689f212e0db8a1319d6f37ba58f69
|
4
|
+
data.tar.gz: 3b64416fc5ecb2b21578f1a736cbffe887f264e51433d42fb28c009b72b2259c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '029fc4d782f0b7e61af6675999f36905e345d4406f55098728959c05cebd26b32562c29d63af8b472dc2d98f50657b627e6ba1d8880ef81968d1611f115d8c2b'
|
7
|
+
data.tar.gz: ca652af048f6c0a6d2efd16c9a1b59d14eccbb2d7861a8403ffa5822bb32cdc8501c92277bcd894ca58e568ea04df4fd6367cd36992816e580a28e0459b4dcd0
|
data/Gemfile.lock
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
# This endpoint is only used for events (via `event.seat_layout_id` to call this API).
|
2
|
+
# For transit, seat layouts are included directly in the Trip API (no separate call needed).
|
3
|
+
#
|
4
|
+
# In both cases, seat availability must always be checked using:
|
5
|
+
# (on another API) /api/v2/storefront/inventory_items?includes[]=reserved_blocks
|
6
|
+
module Spree
|
7
|
+
module Api
|
8
|
+
module V2
|
9
|
+
module Storefront
|
10
|
+
class SeatLayoutsController < Spree::Api::V2::ResourceController
|
11
|
+
# override
|
12
|
+
def resource
|
13
|
+
SpreeCmCommissioner::SeatLayout.includes(
|
14
|
+
:top_level_blocks,
|
15
|
+
{ seat_sections: :blocks }
|
16
|
+
).find(params[:id])
|
17
|
+
end
|
18
|
+
|
19
|
+
# override
|
20
|
+
def default_resource_includes
|
21
|
+
[
|
22
|
+
'top_level_blocks',
|
23
|
+
'seat_sections',
|
24
|
+
'seat_sections.blocks'
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
# override
|
29
|
+
def resource_serializer
|
30
|
+
SpreeCmCommissioner::V2::Storefront::SeatLayoutSerializer
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -39,7 +39,7 @@ module SpreeCmCommissioner
|
|
39
39
|
def block_belongs_to_variant?(block_id, variant_id)
|
40
40
|
return false unless block_id.present? && variant_id.present?
|
41
41
|
|
42
|
-
SpreeCmCommissioner::
|
42
|
+
SpreeCmCommissioner::Block.exists?(variant_id: variant_id, id: block_id)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
class GuestMailer < Spree::BaseMailer
|
3
|
+
layout 'spree_cm_commissioner/layouts/guest_mailer'
|
4
|
+
|
5
|
+
def send_ticket_to_guest(guest_id, email)
|
6
|
+
@guest = SpreeCmCommissioner::Guest.find_by(id: guest_id)
|
7
|
+
@line_item = @guest.line_item
|
8
|
+
@order = @line_item.order
|
9
|
+
@product_type = @line_item.product_type || 'transit'
|
10
|
+
@trip = SpreeCmCommissioner::Trip.find_by(id: @line_item.trip_id) if @line_item.trip_id.present?
|
11
|
+
@email = email
|
12
|
+
@current_store = @order.store
|
13
|
+
subject = "#{@current_store&.name} Booking Confirmation ##{@order.number}"
|
14
|
+
|
15
|
+
mail(from: from_address, to: @email, subject: subject)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -18,6 +18,7 @@ module SpreeCmCommissioner
|
|
18
18
|
|
19
19
|
state_machine.around_transition to: :complete, do: :unstock_inventory!
|
20
20
|
|
21
|
+
state_machine.after_transition to: :complete, do: :mark_user_as_has_incomplete_guest_info, if: :has_incomplete_guest_info?
|
21
22
|
state_machine.after_transition to: :complete, do: :send_transaction_email_to_user, if: :user_has_email?
|
22
23
|
state_machine.after_transition to: :resumed, do: :precalculate_conversion
|
23
24
|
state_machine.around_transition to: :resumed, do: :unstock_inventory!
|
@@ -73,6 +74,19 @@ module SpreeCmCommissioner
|
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
77
|
+
def has_incomplete_guest_info? # rubocop:disable Naming/PredicateName
|
78
|
+
line_items.any? do |line_item|
|
79
|
+
line_item.guests.any? do |guest|
|
80
|
+
!guest.post_registration_completed?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def mark_user_as_has_incomplete_guest_info
|
86
|
+
user&.public_metadata&.[]=('has_incomplete_guest_info', true)
|
87
|
+
user&.save!
|
88
|
+
end
|
89
|
+
|
76
90
|
def unstock_inventory!
|
77
91
|
ActiveRecord::Base.transaction do
|
78
92
|
yield # Equal to block.call
|
@@ -5,17 +5,15 @@ module SpreeCmCommissioner
|
|
5
5
|
sleeping_seat: 2,
|
6
6
|
driver: 3,
|
7
7
|
bathroom: 4,
|
8
|
+
text: 5,
|
8
9
|
other: 0
|
9
10
|
}
|
10
11
|
|
11
12
|
belongs_to :seat_section, class_name: 'SpreeCmCommissioner::SeatSection', optional: true
|
12
13
|
belongs_to :seat_layout, class_name: 'SpreeCmCommissioner::SeatLayout', optional: false
|
14
|
+
belongs_to :variant, class_name: 'Spree::Variant', optional: true
|
13
15
|
|
14
16
|
validates :label, presence: true, if: :label_required?
|
15
|
-
|
16
|
-
has_many :variant_blocks, class_name: 'SpreeCmCommissioner::VariantBlock', dependent: :destroy
|
17
|
-
has_many :variants, class_name: 'Spree::Variant', through: :variant_blocks
|
18
|
-
|
19
17
|
validates :width, presence: true, numericality: { greater_than: 0 }
|
20
18
|
validates :height, presence: true, numericality: { greater_than: 0 }
|
21
19
|
validates :x, presence: true, numericality: true
|
@@ -25,7 +23,7 @@ module SpreeCmCommissioner
|
|
25
23
|
before_validation :assign_layout_from_section, if: -> { seat_layout.nil? && seat_section.present? }
|
26
24
|
|
27
25
|
def label_required?
|
28
|
-
seatable? || block_type.to_s
|
26
|
+
seatable? || %w[other text].include?(block_type.to_s)
|
29
27
|
end
|
30
28
|
|
31
29
|
def self.seatable?(block_type)
|
@@ -12,6 +12,7 @@ module SpreeCmCommissioner
|
|
12
12
|
has_one :line_item, class_name: 'Spree::LineItem', through: :guest
|
13
13
|
|
14
14
|
belongs_to :checkinable, polymorphic: true
|
15
|
+
belongs_to :check_in_session, class_name: 'SpreeCmCommissioner::CheckInSession', optional: true
|
15
16
|
before_validation :set_entry_type, on: :create
|
16
17
|
|
17
18
|
accepts_nested_attributes_for :guest, allow_destroy: true
|
@@ -20,6 +21,11 @@ module SpreeCmCommissioner
|
|
20
21
|
self.entry_type = guest.entry_type
|
21
22
|
end
|
22
23
|
|
23
|
-
validates :guest_id,
|
24
|
+
validates :guest_id,
|
25
|
+
uniqueness: {
|
26
|
+
scope: :check_in_session_id,
|
27
|
+
message: I18n.t('spree.check_in.already_checked_in')
|
28
|
+
},
|
29
|
+
if: -> { check_in_session_id.present? }
|
24
30
|
end
|
25
31
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
class CheckInSession < SpreeCmCommissioner::Base
|
3
|
+
belongs_to :event, class_name: 'Spree::Taxon', optional: false
|
4
|
+
has_many :check_in_rules, class_name: 'SpreeCmCommissioner::CheckInRule', dependent: :destroy
|
5
|
+
|
6
|
+
accepts_nested_attributes_for :check_in_rules, allow_destroy: true
|
7
|
+
|
8
|
+
before_create :set_position
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def set_position
|
13
|
+
self.position ||= event.check_in_sessions.count + 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -255,7 +255,7 @@ module SpreeCmCommissioner
|
|
255
255
|
def validate_block
|
256
256
|
return if block_id.blank?
|
257
257
|
return if line_item.blank? || line_item.variant.blank?
|
258
|
-
return if line_item.variant.
|
258
|
+
return if line_item.variant.blocks.exists?(id: block_id)
|
259
259
|
|
260
260
|
errors.add(:block, "does not exist in variant #{line_item.variant_id}")
|
261
261
|
end
|
@@ -356,6 +356,31 @@ module SpreeCmCommissioner
|
|
356
356
|
line_item&.product&.dynamic_fields&.during_check_in&.any?
|
357
357
|
end
|
358
358
|
|
359
|
+
def cm_check_in_rules
|
360
|
+
SpreeCmCommissioner::CheckInRule.where(ruleable_id: line_item.product_id)
|
361
|
+
end
|
362
|
+
|
363
|
+
def eligible_check_in_sessions
|
364
|
+
check_in_session_ids = []
|
365
|
+
|
366
|
+
cm_check_in_rules.includes(:check_in_session).find_each do |rule|
|
367
|
+
case rule.ruleable_type
|
368
|
+
when 'Spree::Product'
|
369
|
+
check_in_session_ids << rule.check_in_session_id if line_item.product_id == rule.ruleable_id
|
370
|
+
when 'Spree::Variant'
|
371
|
+
check_in_session_ids << rule.check_in_session_id if line_item.variant_id == rule.ruleable_id
|
372
|
+
when 'Spree::OptionValue'
|
373
|
+
check_in_session_ids << rule.check_in_session_id if line_item.variant.option_value_ids.include?(rule.ruleable_id)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
SpreeCmCommissioner::CheckInSession.where(id: check_in_session_ids.uniq)
|
378
|
+
end
|
379
|
+
|
380
|
+
def can_check_in?(session)
|
381
|
+
eligible_check_in_sessions.include?(session)
|
382
|
+
end
|
383
|
+
|
359
384
|
private
|
360
385
|
|
361
386
|
def product_dynamic_fields
|
@@ -2,8 +2,10 @@ module SpreeCmCommissioner
|
|
2
2
|
class Import < Base
|
3
3
|
extend FriendlyId
|
4
4
|
|
5
|
-
enum status
|
5
|
+
enum :status, { :queue => 0, :progress => 1, :done => 2, :failed => 3 }
|
6
6
|
has_one_attached :imported_file
|
7
7
|
friendly_id :name, use: :slugged
|
8
|
+
|
9
|
+
belongs_to :importable, polymorphic: true
|
8
10
|
end
|
9
11
|
end
|
@@ -22,10 +22,11 @@ module SpreeCmCommissioner
|
|
22
22
|
base.has_many :pending_guests, pending_guests_query, class_name: 'SpreeCmCommissioner::Guest', dependent: :destroy
|
23
23
|
base.has_many :product_completion_steps, class_name: 'SpreeCmCommissioner::ProductCompletionStep', through: :product
|
24
24
|
|
25
|
+
base.before_validation -> { self.product_type = variant.product_type || product.product_type }, if: -> { product_type.nil? }
|
26
|
+
|
25
27
|
base.before_save :update_vendor_id
|
26
28
|
|
27
29
|
base.before_create :add_due_date, if: :subscription?
|
28
|
-
base.before_save -> { self.product_type = variant.product_type }, if: -> { product_type.nil? }
|
29
30
|
|
30
31
|
base.validate :ensure_not_exceed_max_quantity_per_order, if: -> { variant&.max_quantity_per_order.present? }
|
31
32
|
|
@@ -2,8 +2,9 @@ module SpreeCmCommissioner
|
|
2
2
|
class SeatLayout < Base
|
3
3
|
has_many :seat_sections, class_name: 'SpreeCmCommissioner::SeatSection', dependent: :destroy
|
4
4
|
|
5
|
+
has_many :blocks, class_name: 'SpreeCmCommissioner::Block'
|
5
6
|
has_many :top_level_blocks, -> { where(seat_section_id: nil) }, class_name: 'SpreeCmCommissioner::Block', dependent: :destroy
|
6
|
-
has_many :section_blocks, -> { where.not(seat_section_id: nil) }, class_name: 'SpreeCmCommissioner::Block'
|
7
|
+
has_many :section_blocks, -> { where.not(seat_section_id: nil) }, class_name: 'SpreeCmCommissioner::Block' # destroy is handled by seat_section
|
7
8
|
|
8
9
|
belongs_to :layoutable, polymorphic: true
|
9
10
|
|
@@ -15,6 +16,20 @@ module SpreeCmCommissioner
|
|
15
16
|
|
16
17
|
validates :name, presence: true
|
17
18
|
|
18
|
-
accepts_nested_attributes_for :seat_sections, :top_level_blocks
|
19
|
+
accepts_nested_attributes_for :seat_sections, :top_level_blocks, allow_destroy: true
|
20
|
+
|
21
|
+
after_commit :sync_seat_layout_id_to_layoutable!, if: -> { layoutable.present? }
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# layoutable (taxon, vehicle) must have seat_layout_id attribute,
|
26
|
+
# either as a column or as a key in a JSONB column (e.g., metadata)
|
27
|
+
def sync_seat_layout_id_to_layoutable!
|
28
|
+
if destroyed?
|
29
|
+
layoutable.update!(seat_layout_id: nil)
|
30
|
+
else
|
31
|
+
layoutable.update!(seat_layout_id: id)
|
32
|
+
end
|
33
|
+
end
|
19
34
|
end
|
20
35
|
end
|
@@ -11,6 +11,6 @@ module SpreeCmCommissioner
|
|
11
11
|
validates :y, presence: true, numericality: true
|
12
12
|
validates :rotation, presence: true, numericality: { greater_than_or_equal_to: -360, less_than_or_equal_to: 360 }
|
13
13
|
|
14
|
-
accepts_nested_attributes_for :blocks
|
14
|
+
accepts_nested_attributes_for :blocks, allow_destroy: true
|
15
15
|
end
|
16
16
|
end
|
@@ -8,6 +8,14 @@ module SpreeCmCommissioner
|
|
8
8
|
base.preference :background_color, :string
|
9
9
|
base.preference :foreground_color, :string
|
10
10
|
|
11
|
+
# seat_layout_id:
|
12
|
+
# This model has no seat_layout column (polymorphic association), so we store the seat_layout ID in public_metadata.
|
13
|
+
# This lets us check if a seat layout exists without triggering a database query.
|
14
|
+
# The ID is automatically updated whenever the seat_layout is saved.
|
15
|
+
base.store :public_metadata, accessors: %i[
|
16
|
+
seat_layout_id
|
17
|
+
], coder: JSON
|
18
|
+
|
11
19
|
base.has_many :taxon_vendors, class_name: 'SpreeCmCommissioner::TaxonVendor'
|
12
20
|
base.has_many :vendors, through: :taxon_vendors
|
13
21
|
|
@@ -23,6 +31,7 @@ module SpreeCmCommissioner
|
|
23
31
|
base.has_many :check_ins, as: :checkinable, class_name: 'SpreeCmCommissioner::CheckIn', dependent: :nullify
|
24
32
|
base.has_many :customer_taxons, class_name: 'SpreeCmCommissioner::CustomerTaxon'
|
25
33
|
|
34
|
+
base.has_one :seat_layout, as: :layoutable, class_name: 'SpreeCmCommissioner::SeatLayout', dependent: :destroy
|
26
35
|
base.has_one :category_icon, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::TaxonCategoryIcon'
|
27
36
|
|
28
37
|
base.has_one :web_banner, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::TaxonWebBanner'
|
@@ -64,6 +73,7 @@ module SpreeCmCommissioner
|
|
64
73
|
base.has_many :line_items, through: :products
|
65
74
|
base.has_many :event_line_items, class_name: 'Spree::LineItem', foreign_key: :event_id
|
66
75
|
base.has_many :event_products, class_name: 'Spree::Product', foreign_key: :event_id
|
76
|
+
base.has_many :event_variants, through: :event_products, class_name: 'Spree::Variant', source: :variants
|
67
77
|
|
68
78
|
base.has_many :event_blazer_queries, as: :queryable, class_name: 'SpreeCmCommissioner::BlazerQueryable'
|
69
79
|
base.has_many :blazer_queries, through: :event_blazer_queries, source: :blazer_query, class_name: 'Blazer::Query'
|
@@ -75,6 +85,9 @@ module SpreeCmCommissioner
|
|
75
85
|
|
76
86
|
base.has_many :taxon_places, class_name: 'SpreeCmCommissioner::TaxonPlace', dependent: :destroy
|
77
87
|
base.has_many :places, through: :taxon_places
|
88
|
+
base.has_many :check_in_sessions, class_name: 'SpreeCmCommissioner::CheckInSession', foreign_key: :event_id, dependent: :destroy
|
89
|
+
|
90
|
+
base.has_many :import_orders, as: :importable, class_name: 'SpreeCmCommissioner::Imports::ImportOrder', dependent: :destroy
|
78
91
|
|
79
92
|
def base.active_homepage_events
|
80
93
|
joins(:homepage_section_relatables)
|
@@ -1,15 +1,25 @@
|
|
1
1
|
module SpreeCmCommissioner
|
2
2
|
class Trip < Base
|
3
|
+
include ::Spree::Metadata
|
3
4
|
include SpreeCmCommissioner::RouteType
|
4
5
|
|
5
6
|
attr_accessor :hours, :minutes, :seconds
|
6
7
|
|
8
|
+
# seat_layout_id:
|
9
|
+
# This model has no seat_layout column (polymorphic association), so we store the seat_layout ID in public_metadata.
|
10
|
+
# This lets us check if a seat layout exists without triggering a database query.
|
11
|
+
# The ID is automatically updated whenever the seat_layout is saved.
|
12
|
+
store :public_metadata, accessors: %i[
|
13
|
+
seat_layout_id
|
14
|
+
], coder: JSON
|
15
|
+
|
7
16
|
before_validation :convert_duration_to_seconds
|
8
17
|
|
9
18
|
belongs_to :vendor, class_name: 'Spree::Vendor', inverse_of: :trips, optional: false
|
10
19
|
belongs_to :product, class_name: 'Spree::Product', inverse_of: :trip, optional: false
|
11
20
|
|
12
21
|
belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle', optional: false
|
22
|
+
has_one :seat_layout, as: :layoutable, class_name: 'SpreeCmCommissioner::SeatLayout', dependent: :destroy
|
13
23
|
|
14
24
|
belongs_to :origin_place, class_name: 'SpreeCmCommissioner::Place', optional: false
|
15
25
|
belongs_to :destination_place, class_name: 'SpreeCmCommissioner::Place', optional: false
|
@@ -23,14 +33,16 @@ module SpreeCmCommissioner
|
|
23
33
|
|
24
34
|
has_many :variants, through: :product
|
25
35
|
has_many :inventory_items, through: :variants
|
26
|
-
has_many :
|
27
|
-
has_many :blocks, through: :variant_blocks
|
36
|
+
has_many :blocks, through: :variants
|
28
37
|
|
29
38
|
validates :departure_time, presence: true
|
30
39
|
validates :duration, numericality: { greater_than: 0 }
|
31
40
|
validate :origin_and_destination_cannot_be_the_same
|
32
41
|
|
33
|
-
|
42
|
+
# before create, duplicate seat layout from vehicle if present
|
43
|
+
before_validation :duplicate_seat_layout_from_vehicle, if: -> { seat_layout.nil? && vehicle.present? && vehicle.seat_layout.present? }
|
44
|
+
|
45
|
+
accepts_nested_attributes_for :trip_stops, :product, :seat_layout, allow_destroy: true
|
34
46
|
|
35
47
|
self.whitelisted_ransackable_associations = %w[product vehicle]
|
36
48
|
self.whitelisted_ransackable_attributes = %w[origin_place_id destination_place_id]
|
@@ -56,12 +68,32 @@ module SpreeCmCommissioner
|
|
56
68
|
departure_time + duration.seconds
|
57
69
|
end
|
58
70
|
|
59
|
-
private
|
60
|
-
|
61
71
|
def origin_and_destination_cannot_be_the_same
|
62
72
|
return unless origin_place_id == destination_place_id
|
63
73
|
|
64
74
|
errors.add(:base, 'Origin and destination cannot be the same')
|
65
75
|
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def duplicate_seat_layout_from_vehicle
|
80
|
+
original = vehicle.seat_layout
|
81
|
+
|
82
|
+
self.seat_layout_attributes =
|
83
|
+
original.attributes.except('id', 'created_at', 'updated_at', 'layoutable_id', 'layoutable_type')
|
84
|
+
.merge(
|
85
|
+
seat_sections_attributes: original.seat_sections.map do |section|
|
86
|
+
section.attributes.except('id', 'created_at', 'updated_at', 'seat_layout_id')
|
87
|
+
.merge(
|
88
|
+
blocks_attributes: section.blocks.map do |block|
|
89
|
+
block.attributes.except('id', 'created_at', 'updated_at', 'seat_layout_id', 'seat_section_id')
|
90
|
+
end
|
91
|
+
)
|
92
|
+
end,
|
93
|
+
top_level_blocks_attributes: original.top_level_blocks.map do |block|
|
94
|
+
block.attributes.except('id', 'created_at', 'updated_at', 'seat_layout_id', 'seat_section_id')
|
95
|
+
end
|
96
|
+
)
|
97
|
+
end
|
66
98
|
end
|
67
99
|
end
|
@@ -42,6 +42,9 @@ module SpreeCmCommissioner
|
|
42
42
|
base.attr_accessor :assigned_roles
|
43
43
|
base.validates_password_strength :password, if: -> { requires_strong_password? && password.present? }
|
44
44
|
|
45
|
+
# Store has_incomplete_guest_info in public_metadata for easy frontend access
|
46
|
+
base.store :public_metadata, accessors: [:has_incomplete_guest_info], coder: JSON
|
47
|
+
|
45
48
|
define_user_places(base)
|
46
49
|
|
47
50
|
def base.end_users
|
@@ -12,6 +12,8 @@ module SpreeCmCommissioner
|
|
12
12
|
|
13
13
|
base.belongs_to :vendor, class_name: 'Spree::Vendor'
|
14
14
|
base.has_one :event, class_name: 'Spree::Taxon', through: :product
|
15
|
+
base.has_one :trip, class_name: 'SpreeCmCommissioner::Trip', through: :product
|
16
|
+
base.has_one :vehicle, class_name: 'SpreeCmCommissioner::Vehicle', through: :trip
|
15
17
|
|
16
18
|
base.has_many :taxons, class_name: 'Spree::Taxon', through: :product
|
17
19
|
base.has_many :visible_option_values, lambda {
|
@@ -21,8 +23,7 @@ module SpreeCmCommissioner
|
|
21
23
|
base.has_many :video_on_demands, class_name: 'SpreeCmCommissioner::VideoOnDemand', dependent: :destroy
|
22
24
|
base.has_many :complete_line_items, -> { complete }, class_name: 'Spree::LineItem'
|
23
25
|
|
24
|
-
base.has_many :
|
25
|
-
base.has_many :blocks, class_name: 'SpreeCmCommissioner::Block', through: :variant_blocks
|
26
|
+
base.has_many :blocks, class_name: 'SpreeCmCommissioner::Block', dependent: :nullify
|
26
27
|
|
27
28
|
base.has_many :variant_guest_card_class, class_name: 'SpreeCmCommissioner::VariantGuestCardClass'
|
28
29
|
base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variant_guest_card_class
|
@@ -35,7 +36,7 @@ module SpreeCmCommissioner
|
|
35
36
|
|
36
37
|
base.accepts_nested_attributes_for :option_values
|
37
38
|
|
38
|
-
base.
|
39
|
+
base.before_validation -> { self.product_type = product.product_type }, if: -> { product_type.nil? }
|
39
40
|
end
|
40
41
|
|
41
42
|
def delivery_required?
|
@@ -1,7 +1,16 @@
|
|
1
1
|
module SpreeCmCommissioner
|
2
2
|
class Vehicle < Base
|
3
|
+
include ::Spree::Metadata
|
3
4
|
include SpreeCmCommissioner::VehicleType
|
4
5
|
|
6
|
+
# seat_layout_id:
|
7
|
+
# This model has no seat_layout column (polymorphic association), so we store the seat_layout ID in public_metadata.
|
8
|
+
# This lets us check if a seat layout exists without triggering a database query.
|
9
|
+
# The ID is automatically updated whenever the seat_layout is saved.
|
10
|
+
store :public_metadata, accessors: %i[
|
11
|
+
seat_layout_id
|
12
|
+
], coder: JSON
|
13
|
+
|
5
14
|
belongs_to :vendor, class_name: 'Spree::Vendor'
|
6
15
|
|
7
16
|
has_one :primary_photo, -> { order(position: :asc) }, class_name: 'SpreeCmCommissioner::VehiclePhoto', as: :viewable, dependent: :destroy
|
@@ -14,6 +23,8 @@ module SpreeCmCommissioner
|
|
14
23
|
has_many :option_values, through: :option_value_vehicles, class_name: 'Spree::OptionValue'
|
15
24
|
has_many :amenities, through: :option_value_vehicles, source: :option_value
|
16
25
|
|
26
|
+
has_many :trips, class_name: 'SpreeCmCommissioner::Trip', dependent: :nullify
|
27
|
+
|
17
28
|
validates :code, uniqueness: { scope: :vendor_id }, presence: true
|
18
29
|
validates :license_plate, uniqueness: true, allow_blank: true
|
19
30
|
|
@@ -15,6 +15,11 @@ module Spree
|
|
15
15
|
base.attributes :need_confirmation, :product_type, :kyc, :kyc_fields, :allowed_upload_later, :allow_anonymous_booking, :use_video_as_default
|
16
16
|
base.attributes :reveal_description, :discontinue_on, :public_metadata, :purchasable_on
|
17
17
|
|
18
|
+
# Expose only the `event_id` here instead of the full event object.
|
19
|
+
# This lets the client fetch event details separately (usually already cached),
|
20
|
+
# avoids extra DB queries from a `has_one :event` association, and keeps this response lightweight.
|
21
|
+
base.attributes :event_id
|
22
|
+
|
18
23
|
base.attribute :purchasable_on_app do |product|
|
19
24
|
product.purchasable_on == 'app' || product.purchasable_on == 'both'
|
20
25
|
end
|
@@ -17,6 +17,9 @@ module Spree
|
|
17
17
|
:background_color, :foreground_color, :show_badge_status,
|
18
18
|
:purchasable_on, :vendor_id, :available_on, :hide_video_banner
|
19
19
|
|
20
|
+
# To get full seat layout details, call the seat_layouts API with this ID.
|
21
|
+
base.attributes :seat_layout_id
|
22
|
+
|
20
23
|
base.attribute :purchasable_on_app do |taxon|
|
21
24
|
taxon.purchasable_on == 'app' || taxon.purchasable_on == 'both'
|
22
25
|
end
|
@@ -5,7 +5,7 @@ module Spree
|
|
5
5
|
def self.prepended(base)
|
6
6
|
base.attributes :first_name, :last_name, :gender, :phone_number, :intel_phone_number,
|
7
7
|
:country_code, :otp_enabled, :otp_email, :otp_phone_number,
|
8
|
-
:confirm_pin_code_enabled, :tenant_id
|
8
|
+
:confirm_pin_code_enabled, :tenant_id, :has_incomplete_guest_info
|
9
9
|
|
10
10
|
base.has_one :profile, serializer: ::Spree::V2::Storefront::UserProfileSerializer
|
11
11
|
base.has_many :device_tokens, serializer: Spree::V2::Storefront::UserDeviceTokenSerializer
|
@@ -2,7 +2,7 @@ module SpreeCmCommissioner
|
|
2
2
|
module V2
|
3
3
|
module Storefront
|
4
4
|
class BlockSerializer < BaseSerializer
|
5
|
-
attributes :label, :width, :height, :x, :y, :rotation, :block_type
|
5
|
+
attributes :label, :width, :height, :x, :y, :rotation, :block_type, :variant_id
|
6
6
|
|
7
7
|
attribute :seatable, &:seatable?
|
8
8
|
end
|
@@ -5,6 +5,7 @@ module SpreeCmCommissioner
|
|
5
5
|
attributes :departure_time, :arrival_time, :duration_in_hms, :route_type
|
6
6
|
|
7
7
|
has_one :vehicle, serializer: ::SpreeCmCommissioner::V2::Storefront::TripVehicleSerializer
|
8
|
+
has_one :seat_layout, serializer: SpreeCmCommissioner::V2::Storefront::SeatLayoutSerializer
|
8
9
|
|
9
10
|
has_one :origin_place, serializer: ::SpreeCmCommissioner::V2::Storefront::TripPlaceSerializer
|
10
11
|
has_one :destination_place, serializer: ::SpreeCmCommissioner::V2::Storefront::TripPlaceSerializer
|
@@ -4,8 +4,6 @@ module SpreeCmCommissioner
|
|
4
4
|
class TripVariantSerializer < BaseSerializer
|
5
5
|
attributes :price, :display_price, :compare_at_price, :display_compare_at_price, :currency,
|
6
6
|
:number_of_guests, :seat_type
|
7
|
-
|
8
|
-
has_many :variant_blocks, serializer: ::SpreeCmCommissioner::V2::Storefront::VariantBlockSerializer
|
9
7
|
end
|
10
8
|
end
|
11
9
|
end
|
@@ -8,8 +8,6 @@ module SpreeCmCommissioner
|
|
8
8
|
|
9
9
|
has_many :vehicle_photos, serializer: ::SpreeCmCommissioner::V2::Storefront::AssetSerializer
|
10
10
|
has_many :amenities, serializer: ::SpreeCmCommissioner::V2::Storefront::AmenitySerializer
|
11
|
-
|
12
|
-
has_one :seat_layout, serializer: SpreeCmCommissioner::V2::Storefront::SeatLayoutSerializer
|
13
11
|
end
|
14
12
|
end
|
15
13
|
end
|
@@ -31,18 +31,33 @@ module SpreeCmCommissioner
|
|
31
31
|
|
32
32
|
def build_order_params(order_data)
|
33
33
|
guest_attributes = order_data.slice(*SpreeCmCommissioner::Guest.csv_importable_columns)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
34
|
+
if order_data[:variant_id].present?
|
35
|
+
{
|
36
|
+
channel: construct_channel('google_form'),
|
37
|
+
email: order_data[:email],
|
38
|
+
phone_number: order_data[:phone_number],
|
39
|
+
line_items_attributes: [
|
40
|
+
{
|
41
|
+
quantity: 1,
|
42
|
+
variant_id: order_data[:variant_id].to_i,
|
43
|
+
guests_attributes: Array.new(1, guest_attributes)
|
44
|
+
}
|
45
|
+
]
|
46
|
+
}
|
47
|
+
else
|
48
|
+
{
|
49
|
+
channel: construct_channel(order_data[:order_channel]),
|
50
|
+
email: order_data[:email],
|
51
|
+
phone_number: order_data[:phone_number],
|
52
|
+
line_items_attributes: [
|
53
|
+
{
|
54
|
+
quantity: order_data[:quantity].to_i,
|
55
|
+
sku: order_data[:variant_sku],
|
56
|
+
guests_attributes: Array.new(order_data[:quantity].to_i, guest_attributes)
|
57
|
+
}
|
58
|
+
]
|
59
|
+
}
|
60
|
+
end
|
46
61
|
end
|
47
62
|
|
48
63
|
def create_order(params, index)
|