spree_cm_commissioner 2.1.8 → 2.1.9.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/.env.example +75 -29
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/api/v2/storefront/access_tokens_controller.rb +10 -0
- data/app/controllers/spree/api/v2/storefront/account/guest_dynamic_fields_controller.rb +82 -0
- data/app/controllers/spree/api/v2/storefront/account/mark_guest_info_complete_controller.rb +39 -0
- data/app/controllers/spree/api/v2/storefront/account/orders_controller_decorator.rb +34 -5
- data/app/controllers/spree/api/v2/storefront/seat_layouts_controller.rb +1 -1
- data/app/controllers/spree/api/v2/tenant/access_tokens_controller.rb +10 -0
- data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +1 -1
- data/app/models/concerns/spree_cm_commissioner/event_metadata.rb +39 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +27 -21
- data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +97 -0
- data/app/models/spree_cm_commissioner/guest.rb +38 -4
- data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +1 -0
- data/app/models/spree_cm_commissioner/inventory_item.rb +1 -1
- data/app/models/spree_cm_commissioner/invite_team.rb +8 -0
- data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +2 -2
- data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +2 -2
- data/app/models/spree_cm_commissioner/seat_layout.rb +3 -3
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +2 -20
- data/app/models/spree_cm_commissioner/trip.rb +5 -7
- data/app/models/spree_cm_commissioner/vehicle.rb +5 -7
- data/app/overrides/spree/admin/taxons/_form/background_color_and_foreground_color.html.erb.deface +6 -6
- data/app/overrides/spree/admin/taxons/_form/check_in_flows.html.erb.deface +14 -8
- data/app/serializers/spree/v2/storefront/order_serializer_decorator.rb +63 -0
- data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +1 -1
- data/app/serializers/spree_cm_commissioner/v2/operator/dashboard_crew_event_serializer.rb +1 -0
- data/config/initializers/spree_permitted_attributes.rb +5 -4
- data/config/routes.rb +6 -0
- data/docs/api/scoped-access-token-endpoints.md +84 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +34 -12
- metadata +12 -5
- data/app/models/concerns/spree_cm_commissioner/event_check_in_flowable.rb +0 -30
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a278c011c4129a11c968c695ac8ae1a22996991ab69f8700bdafa6d9002c470e
|
|
4
|
+
data.tar.gz: 5354f593b70e02b4956f27006a82cc06341a2630ee45306b9e8f33a4c246eb9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 140d7641c8e2542f82e64f8d8bf5758b31b5c3a6daf9232bd57bc9835c27685aba70182cf173df43fb805edc08b0968c80612d726469fd1780ad05a05d38d596
|
|
7
|
+
data.tar.gz: fe162b2e261ace9661ea9b2e05a699617ff5da1a707dab0cad139b72c6fb0242966a9728c37e2a653213665c34767177ab8a4ee1d11c8c02ecf82088631386bb
|
data/.env.example
CHANGED
|
@@ -1,29 +1,75 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
# Rails Configuration
|
|
2
|
+
RAILS_ENV=development # Rails environment (development, test, production)
|
|
3
|
+
RAILS_MASTER_KEY= # Rails master key for encrypted credentials
|
|
4
|
+
RAILS_MIN_THREADS=5 # Minimum number of threads for Puma server
|
|
5
|
+
RAILS_MAX_THREADS=5 # Maximum number of threads for Puma server
|
|
6
|
+
RAILS_SERVE_STATIC_FILES= # Enable static file serving (useful for production)
|
|
7
|
+
RAILS_LOG_TO_STDOUT=enabled # Enable Rails logging to STDOUT
|
|
8
|
+
|
|
9
|
+
# Server Configuration
|
|
10
|
+
PORT=3000 # Port for the server to listen on
|
|
11
|
+
PIDFILE=tmp/pids/server.pid # Process ID file location
|
|
12
|
+
WEB_CONCURRENCY=2 # Number of web workers for Puma
|
|
13
|
+
DEFAULT_URL_HOST=http://localhost:4000 # Default host URL for the application
|
|
14
|
+
ORGANIZER_URL= # Organizer platform URL (e.g., https://organizer.bookme.plus)
|
|
15
|
+
|
|
16
|
+
# Redis Configuration
|
|
17
|
+
REDIS_URL=redis://localhost:6379/12 # Redis connection URL
|
|
18
|
+
REDIS_POOL_SIZE=5 # Redis connection pool size
|
|
19
|
+
REDIS_TIMEOUT=5 # Redis connection timeout in seconds
|
|
20
|
+
|
|
21
|
+
# AWS Configuration
|
|
22
|
+
AWS_REGION=ap-southeast-1 # AWS region for services
|
|
23
|
+
AWS_ACCESS_KEY_ID= # AWS access key ID for authentication
|
|
24
|
+
AWS_SECRET_ACCESS_KEY= # AWS secret access key for authentication
|
|
25
|
+
AWS_BUCKET_NAME=contigoasia-production # AWS S3 bucket name for storage
|
|
26
|
+
AWS_OUTPUT_BUCKET_NAME=output-production-cm # AWS S3 output bucket name for media processing
|
|
27
|
+
AWS_CLUSTER_NAME=production-cm-web # AWS ECS cluster name for waiting room system
|
|
28
|
+
|
|
29
|
+
# AWS CloudFront Configuration
|
|
30
|
+
ASSETS_SYNC_CF_DIST_ID=E3DV5NICAX5NCH # CloudFront distribution ID for asset synchronization
|
|
31
|
+
AWS_CF_MEDIA_DOMAIN=medias.bookme.plus # CloudFront media domain (e.g., medias.bookme.plus)
|
|
32
|
+
AWS_CF_PUBLIC_KEY_ID= # CloudFront public key ID for signed URLs
|
|
33
|
+
AWS_CF_PRIVATE_KEY= # CloudFront private key for signed URLs (PEM format)
|
|
34
|
+
|
|
35
|
+
# Cache Configuration
|
|
36
|
+
CONTENT_CACHE_MAX_AGE=180 # Maximum age for CDN content cache in seconds (default: 86400)
|
|
37
|
+
|
|
38
|
+
# Email Configuration
|
|
39
|
+
NO_REPLY_EMAIL=noreply@example.com # No-reply email address for outgoing emails
|
|
40
|
+
|
|
41
|
+
# SMS Configuration
|
|
42
|
+
SMS_SENDER_ID=SMS Info # Sender ID for SMS messages (default: 'SMS Info')
|
|
43
|
+
|
|
44
|
+
# Payment Gateway Configuration
|
|
45
|
+
VATTANAC_AES_SECRET_KEY= # Vattanac Bank AES secret key for encryption
|
|
46
|
+
BOOKMEPLUS_PRIVATE_KEY= # BookMe+ private key for Vattanac Bank integration
|
|
47
|
+
VATTANAC_PUBLIC_KEY= # Vattanac Bank public key for signature verification
|
|
48
|
+
|
|
49
|
+
# Google Services Configuration
|
|
50
|
+
GOOGLE_MAP_KEY=AIzaSyBt-OSnDZze6hxGjfiapSxpcyGzBo_COv8 # Google Maps API key for nearby places and location services
|
|
51
|
+
ISSUER_ID=3388000000022336356 # Google Wallet issuer ID for digital passes
|
|
52
|
+
|
|
53
|
+
# Waiting Room System Configuration
|
|
54
|
+
WAITING_ROOM_DISABLED=no # Disable waiting room functionality (set to 'yes' to disable)
|
|
55
|
+
WAITING_ROOM_SESSION_SIGNATURE=4234f0a3-c42f-40b5-9057-7c006793cea4 # JWT signature key for waiting room session tokens
|
|
56
|
+
WAITING_ROOM_SESSION_EXPIRE_DURATION_IN_SECOND=180 # Session expiration duration in seconds (default: 180 / 3 minutes)
|
|
57
|
+
WAITING_ROOM_MIN_SESSIONS_COUNT=5 # Minimum sessions count for waiting room calculation (default: 5)
|
|
58
|
+
WAITING_ROOM_SERVERS_COUNT=2 # Number of running servers for capacity calculation (default: 2)
|
|
59
|
+
WAITING_ROOM_MAX_THREAD_COUNT=10 # Maximum thread count per server (default: 10)
|
|
60
|
+
WAITING_ROOM_MULTIPLIER=150 # Multiplier for waiting room capacity calculation (default: 150)
|
|
61
|
+
|
|
62
|
+
# Exception Notification Configuration
|
|
63
|
+
EXCEPTION_NOTIFY_ENABLE=yes # Enable exception notifications (set to 'yes' to enable)
|
|
64
|
+
EXCEPTION_TELEGRAM_BOT_TOKEN=6441690414:AAFBpevRdBaRTXmalJR2vcSdPNzYoDnMEFk # Telegram bot token for exception notifications
|
|
65
|
+
EXCEPTION_NOTIFIER_TELEGRAM_CHANNEL_ID=-1001807686211 # Telegram channel ID for exception notifications
|
|
66
|
+
|
|
67
|
+
# Telegram Bot Configuration
|
|
68
|
+
DEFAULT_TELEGRAM_BOT_API_TOKEN=6441690414:AAFBpevRdBaRTXmalJR2vcSdPNzYoDnMEFk # Default Telegram bot API token
|
|
69
|
+
PIN_CODE_DEBUG_NOTIFIY_TELEGRAM_ENABLE=yes # Enable PIN code debug notifications via Telegram (set to 'yes' to enable)
|
|
70
|
+
|
|
71
|
+
# Business Rules Configuration
|
|
72
|
+
ACCOMMODATION_MAX_STAY_DAYS=10 # Maximum number of days allowed for accommodation stays (default: 10)
|
|
73
|
+
|
|
74
|
+
# Development/Testing Configuration
|
|
75
|
+
BUNDLE_GEMFILE= # Gemfile location for bundler
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Storefront
|
|
5
|
+
module Account
|
|
6
|
+
class GuestDynamicFieldsController < ::Spree::Api::V2::ResourceController
|
|
7
|
+
before_action :require_spree_current_user
|
|
8
|
+
before_action :load_guest
|
|
9
|
+
|
|
10
|
+
def update_dynamic_field
|
|
11
|
+
phase = params[:phase]
|
|
12
|
+
|
|
13
|
+
return render_error_payload('Invalid phase', 422) unless %w[pre_registration post_registration].include?(phase)
|
|
14
|
+
|
|
15
|
+
begin
|
|
16
|
+
fields = dynamic_fields_params
|
|
17
|
+
rescue ActionController::ParameterMissing
|
|
18
|
+
return render_error_payload('No dynamic fields provided', 422)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
return render_error_payload('No dynamic fields provided', 422) if fields.blank?
|
|
22
|
+
|
|
23
|
+
ActiveRecord::Base.transaction do
|
|
24
|
+
fields.each do |field|
|
|
25
|
+
process_dynamic_field(field, phase)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@guest.save_and_move_to_next_stage
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
render_serialized_payload { serialize_guest(@guest) }
|
|
32
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
33
|
+
render_error_payload(e.record.errors, 422)
|
|
34
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
35
|
+
render_error_payload(e.message, 404)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def dynamic_fields_params
|
|
41
|
+
params.require(:dynamic_fields).map do |field|
|
|
42
|
+
field.permit(:id, :value)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def load_guest
|
|
47
|
+
@guest = SpreeCmCommissioner::Guest.find(params[:guest_id])
|
|
48
|
+
|
|
49
|
+
unless spree_current_user.guests.exists?(id: @guest.id) ||
|
|
50
|
+
@guest.line_item.order.user == spree_current_user
|
|
51
|
+
render_error_payload('Unauthorized', 401)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def process_dynamic_field(field, phase)
|
|
56
|
+
dynamic_field = SpreeCmCommissioner::DynamicField.find(field[:id])
|
|
57
|
+
guest_field = @guest.guest_dynamic_fields.find_or_initialize_by(dynamic_field_id: dynamic_field.id)
|
|
58
|
+
|
|
59
|
+
return if guest_field.new_record? && dynamic_field.data_fill_stage != phase
|
|
60
|
+
|
|
61
|
+
if dynamic_field.requires_dynamic_field_options?
|
|
62
|
+
option = dynamic_field.dynamic_field_options.find_by(value: field[:value])
|
|
63
|
+
raise ActiveRecord::RecordNotFound, "Invalid value for dynamic field #{dynamic_field.label}" unless option
|
|
64
|
+
|
|
65
|
+
guest_field.dynamic_field_option = option
|
|
66
|
+
guest_field.value = option.value
|
|
67
|
+
else
|
|
68
|
+
guest_field.value = field[:value]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
guest_field.save!
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def serialize_guest(guest)
|
|
75
|
+
SpreeCmCommissioner::V2::Storefront::GuestSerializer.new(guest).serializable_hash
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Storefront
|
|
5
|
+
module Account
|
|
6
|
+
class MarkGuestInfoCompleteController < ::Spree::Api::V2::ResourceController
|
|
7
|
+
before_action :require_spree_current_user
|
|
8
|
+
|
|
9
|
+
def update
|
|
10
|
+
update_user_metadata
|
|
11
|
+
render_success_response
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def update_user_metadata
|
|
17
|
+
spree_current_user.public_metadata['has_incomplete_guest_info'] = false
|
|
18
|
+
spree_current_user.save!
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def render_success_response
|
|
22
|
+
render_serialized_payload(200) do
|
|
23
|
+
{
|
|
24
|
+
success: true,
|
|
25
|
+
message: 'Guest information marked as complete',
|
|
26
|
+
has_incomplete_guest_info: false
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def model_class
|
|
32
|
+
Spree::User
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -4,12 +4,41 @@ module Spree
|
|
|
4
4
|
module Storefront
|
|
5
5
|
module Account
|
|
6
6
|
module OrdersControllerDecorator
|
|
7
|
+
def self.prepended(base)
|
|
8
|
+
base.include Spree::Api::V2::Storefront::OrderConcern
|
|
9
|
+
end
|
|
10
|
+
|
|
7
11
|
def collection
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
@collection =
|
|
13
|
+
if params[:incomplete_guest_info] == 'true'
|
|
14
|
+
find_orders_with_incomplete_guest_info
|
|
15
|
+
elsif params[:request_state].present?
|
|
16
|
+
spree_current_user.orders.filter_by_request_state
|
|
17
|
+
else
|
|
18
|
+
collection_finder.new(user: spree_current_user, store: current_store, state: params.delete(:state)).execute
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
@collection.page(params[:page]).per(params[:per_page])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def find_orders_with_incomplete_guest_info
|
|
27
|
+
orders = spree_current_user.orders
|
|
28
|
+
.complete
|
|
29
|
+
.includes(line_items: { guests: [:guest_dynamic_fields] })
|
|
30
|
+
|
|
31
|
+
incomplete_order_ids = orders.select do |order|
|
|
32
|
+
order.line_items.any? do |line_item|
|
|
33
|
+
line_item.guests.any? do |guest|
|
|
34
|
+
guest.pre_registration_completed? &&
|
|
35
|
+
guest.post_registration_fields? &&
|
|
36
|
+
!guest.post_registration_completed?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end.map(&:id)
|
|
40
|
+
|
|
41
|
+
spree_current_user.orders.where(id: incomplete_order_ids)
|
|
13
42
|
end
|
|
14
43
|
|
|
15
44
|
def collection_finder
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# This endpoint is only used for events (via `event.
|
|
1
|
+
# This endpoint is only used for events (via `event.preload_seat_layout_id` to call this API).
|
|
2
2
|
# For transit, seat layouts are included directly in the Trip API (no separate call needed).
|
|
3
3
|
#
|
|
4
4
|
# In both cases, seat availability must always be checked using:
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module EventMetadata
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
store_public_metadata :background_color, :string
|
|
7
|
+
store_public_metadata :foreground_color, :string
|
|
8
|
+
|
|
9
|
+
store_public_metadata :group_check_in_enabled, :boolean, default: true
|
|
10
|
+
store_public_metadata :individual_check_in_enabled, :boolean, default: true
|
|
11
|
+
|
|
12
|
+
# By default, for most events, manual search is allowed for operators.
|
|
13
|
+
# For certain large events, admin can disable this manually.
|
|
14
|
+
store_public_metadata :allow_manual_search_for_operator, :boolean, default: true
|
|
15
|
+
|
|
16
|
+
# This model has no seat_layout column (polymorphic association), so we store the preload_seat_layout_id ID in public_metadata.
|
|
17
|
+
# This lets us check if a seat layout exists without triggering a database query.
|
|
18
|
+
# The ID is automatically updated whenever the seat_layout is saved.
|
|
19
|
+
store_public_metadata :preload_seat_layout_id, :integer
|
|
20
|
+
|
|
21
|
+
before_validation :validate_at_least_one_check_in_flow_presence
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_in_flows
|
|
25
|
+
flows = []
|
|
26
|
+
flows << 'group' if group_check_in_enabled?
|
|
27
|
+
flows << 'individual' if individual_check_in_enabled?
|
|
28
|
+
flows
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def validate_at_least_one_check_in_flow_presence
|
|
34
|
+
return if check_in_flows.any?
|
|
35
|
+
|
|
36
|
+
errors.add(:check_in_flows, 'must have at least one flow selected')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -74,17 +74,40 @@ module SpreeCmCommissioner
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
def mark_user_as_has_incomplete_guest_info
|
|
78
|
+
user&.public_metadata&.[]=('has_incomplete_guest_info', true)
|
|
79
|
+
user&.save!
|
|
80
|
+
end
|
|
81
|
+
|
|
77
82
|
def has_incomplete_guest_info? # rubocop:disable Naming/PredicateName
|
|
78
83
|
line_items.any? do |line_item|
|
|
79
84
|
line_item.guests.any? do |guest|
|
|
80
|
-
|
|
85
|
+
guest_incomplete?(guest)
|
|
81
86
|
end
|
|
82
87
|
end
|
|
83
88
|
end
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
# overrided not to send email yet to user if order needs confirmation
|
|
91
|
+
# it will send after vendors accepted.
|
|
92
|
+
def confirmation_delivered?
|
|
93
|
+
confirmation_delivered || need_confirmation?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# allow authorized user to accept all requested line items
|
|
97
|
+
def accepted_by(user)
|
|
98
|
+
transaction do
|
|
99
|
+
accept!
|
|
100
|
+
|
|
101
|
+
line_items.each do |line_item|
|
|
102
|
+
line_item.accepted_by(user)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def guest_incomplete?(guest)
|
|
108
|
+
(guest.pre_registration_fields? && !guest.pre_registration_completed?) ||
|
|
109
|
+
(guest.post_registration_fields? && !guest.post_registration_completed?) ||
|
|
110
|
+
(guest.during_check_in_fields? && !guest.check_in_completed?)
|
|
88
111
|
end
|
|
89
112
|
|
|
90
113
|
def unstock_inventory!
|
|
@@ -159,23 +182,6 @@ module SpreeCmCommissioner
|
|
|
159
182
|
user.present? && user.email.present?
|
|
160
183
|
end
|
|
161
184
|
|
|
162
|
-
# allow authorized user to accept all requested line items
|
|
163
|
-
def accepted_by(user)
|
|
164
|
-
transaction do
|
|
165
|
-
accept!
|
|
166
|
-
|
|
167
|
-
line_items.each do |line_item|
|
|
168
|
-
line_item.accepted_by(user)
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# overrided not to send email yet to user if order needs confirmation
|
|
174
|
-
# it will send after vendors accepted.
|
|
175
|
-
def confirmation_delivered?
|
|
176
|
-
confirmation_delivered || need_confirmation?
|
|
177
|
-
end
|
|
178
|
-
|
|
179
185
|
# can_accepted? already use by ransack/visitor.rb
|
|
180
186
|
def can_accept_all?
|
|
181
187
|
approved? && requested?
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Provides a DSL for defining typed key/value attributes stored in JSON columns
|
|
2
|
+
# using ActiveRecord's `store`.
|
|
3
|
+
#
|
|
4
|
+
# Enhancements over ActiveRecord::Store:
|
|
5
|
+
# - Supports default values when a key is missing or nil
|
|
6
|
+
# - Applies type casting when reading from the database
|
|
7
|
+
# - Adds predicate helpers (`foo?`) for boolean attributes
|
|
8
|
+
# - Validates new data types only (does not validate existing DB values)
|
|
9
|
+
#
|
|
10
|
+
# Supported types:
|
|
11
|
+
# - boolean
|
|
12
|
+
# - string
|
|
13
|
+
# - integer
|
|
14
|
+
#
|
|
15
|
+
# Example usage:
|
|
16
|
+
# ```
|
|
17
|
+
# class Spree::Taxon < ApplicationRecord
|
|
18
|
+
# include SpreeCmCommissioner::StoreMetadata
|
|
19
|
+
|
|
20
|
+
# store_public_metadata :completed, :boolean, default: true
|
|
21
|
+
# store_public_metadata :count, :integer, default: 5
|
|
22
|
+
# store_private_metadata :app_token, :string, default: "XYZ"
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# taxon = Spree::Taxon.new(completed: true, app_token: "ABC", count: 10)
|
|
26
|
+
# taxon.completed # => true, store in public_metadata
|
|
27
|
+
# taxon.completed? # => true, store in public_metadata
|
|
28
|
+
# taxon.count # => 10, store in public_metadata
|
|
29
|
+
# taxon.app_token # => "ABC", store in private_metadata
|
|
30
|
+
# ```
|
|
31
|
+
|
|
32
|
+
module SpreeCmCommissioner
|
|
33
|
+
module StoreMetadata
|
|
34
|
+
extend ActiveSupport::Concern
|
|
35
|
+
|
|
36
|
+
class_methods do
|
|
37
|
+
def store_private_metadata(key, type, default: nil)
|
|
38
|
+
store :private_metadata, accessors: [key], coder: JSON
|
|
39
|
+
|
|
40
|
+
define_metadata_reader(:private_metadata, key, type, default)
|
|
41
|
+
define_metadata_validation(:private_metadata, key, type)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def store_public_metadata(key, type, default: nil)
|
|
45
|
+
store :public_metadata, accessors: [key], coder: JSON
|
|
46
|
+
|
|
47
|
+
define_metadata_reader(:public_metadata, key, type, default)
|
|
48
|
+
define_metadata_validation(:public_metadata, key, type)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Override the store reader to:
|
|
54
|
+
# - Return default if nil
|
|
55
|
+
# - Cast to correct type
|
|
56
|
+
# - Add `?` predicate for booleans
|
|
57
|
+
def define_metadata_reader(column_name, key, type, default)
|
|
58
|
+
define_method(key) do
|
|
59
|
+
metadata = send(column_name) || {}
|
|
60
|
+
raw_value = metadata[key.to_s]
|
|
61
|
+
return default if raw_value.nil?
|
|
62
|
+
|
|
63
|
+
case type
|
|
64
|
+
when :boolean
|
|
65
|
+
ActiveModel::Type::Boolean.new.cast(raw_value)
|
|
66
|
+
when :integer
|
|
67
|
+
raw_value.to_i
|
|
68
|
+
when :string
|
|
69
|
+
raw_value.to_s
|
|
70
|
+
else
|
|
71
|
+
raw_value
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
define_method("#{key}?") { send(key) } if type == :boolean
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Validates only new assignments (raw JSON values) for type safety
|
|
79
|
+
def define_metadata_validation(column_name, key, type)
|
|
80
|
+
case type
|
|
81
|
+
when :boolean
|
|
82
|
+
validates key, inclusion: { in: [true, false] }, allow_nil: true
|
|
83
|
+
when :integer
|
|
84
|
+
validate do
|
|
85
|
+
metadata = send(column_name) || {}
|
|
86
|
+
raw_value = metadata[key.to_s]
|
|
87
|
+
next if raw_value.nil?
|
|
88
|
+
|
|
89
|
+
errors.add(key, 'is not a number') unless raw_value.to_s =~ /\A-?\d+\z/
|
|
90
|
+
end
|
|
91
|
+
when :string
|
|
92
|
+
validates key, length: { maximum: 255 }, allow_nil: true
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -57,10 +57,9 @@ module SpreeCmCommissioner
|
|
|
57
57
|
before_create :generate_bib, if: -> { line_item.reload && variant.bib_pre_generation_on_create? }
|
|
58
58
|
after_create :preload_order_block_ids, if: -> { block_id.present? }
|
|
59
59
|
after_update :preload_order_block_ids, if: :saved_change_to_block_id?
|
|
60
|
-
|
|
61
60
|
before_destroy :cancel_reserved_block!, if: -> { reserved_block.present? && reserved_block.active_on_hold? }
|
|
62
|
-
|
|
63
61
|
after_destroy :preload_order_block_ids
|
|
62
|
+
after_commit :update_user_incomplete_guest_info_status, if: :should_update_incomplete_guest_info?
|
|
64
63
|
|
|
65
64
|
validates :bib_index, uniqueness: true, allow_nil: true
|
|
66
65
|
|
|
@@ -396,8 +395,6 @@ module SpreeCmCommissioner
|
|
|
396
395
|
eligible_check_in_sessions.include?(session)
|
|
397
396
|
end
|
|
398
397
|
|
|
399
|
-
private
|
|
400
|
-
|
|
401
398
|
def product_dynamic_fields
|
|
402
399
|
@product_dynamic_fields ||= line_item.product.dynamic_fields
|
|
403
400
|
end
|
|
@@ -417,6 +414,43 @@ module SpreeCmCommissioner
|
|
|
417
414
|
# Reject if dynamic_field_id is blank or if _destroy is true
|
|
418
415
|
attributes[:dynamic_field_id].blank? || ActiveModel::Type::Boolean.new.cast(attributes[:_destroy])
|
|
419
416
|
end
|
|
417
|
+
|
|
418
|
+
def update_user_incomplete_guest_info_status
|
|
419
|
+
return unless user
|
|
420
|
+
return unless line_item&.order&.completed?
|
|
421
|
+
|
|
422
|
+
user.reload
|
|
423
|
+
|
|
424
|
+
has_incomplete = user.orders.complete.any? do |order|
|
|
425
|
+
order_has_incomplete_guests?(order)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
current_status = user.public_metadata['has_incomplete_guest_info'] || false
|
|
429
|
+
|
|
430
|
+
return unless current_status != has_incomplete
|
|
431
|
+
|
|
432
|
+
user.public_metadata['has_incomplete_guest_info'] = has_incomplete
|
|
433
|
+
user.save!
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def order_has_incomplete_guests?(order)
|
|
437
|
+
order.line_items.any? do |line_item|
|
|
438
|
+
line_item.guests.any? do |guest|
|
|
439
|
+
guest_incomplete?(guest)
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def guest_incomplete?(guest)
|
|
445
|
+
(guest.pre_registration_fields? && !guest.pre_registration_completed?) ||
|
|
446
|
+
(guest.post_registration_fields? && !guest.post_registration_completed?) ||
|
|
447
|
+
(guest.during_check_in_fields? && !guest.check_in_completed?)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def should_update_incomplete_guest_info?
|
|
451
|
+
saved_change_to_data_fill_stage_phase? ||
|
|
452
|
+
(guest_dynamic_fields.any? && (guest_dynamic_fields.any?(&:saved_changes?) || guest_dynamic_fields.any?(&:destroyed?)))
|
|
453
|
+
end
|
|
420
454
|
end
|
|
421
455
|
end
|
|
422
456
|
# rubocop:enable Metrics/ClassLength
|
|
@@ -48,7 +48,7 @@ module SpreeCmCommissioner
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def adjust_quantity_in_redis(quantity)
|
|
51
|
-
SpreeCmCommissioner.
|
|
51
|
+
SpreeCmCommissioner.inventory_redis_pool.with do |redis|
|
|
52
52
|
cached_quantity_available = redis.get(redis_key)
|
|
53
53
|
# ignore if redis doesn't exist
|
|
54
54
|
return if cached_quantity_available.nil? # rubocop:disable Lint/NonLocalExitFromIterator
|
|
@@ -14,6 +14,8 @@ module SpreeCmCommissioner
|
|
|
14
14
|
|
|
15
15
|
validate :validate_roles
|
|
16
16
|
|
|
17
|
+
before_validation :normalize_email
|
|
18
|
+
|
|
17
19
|
after_create :set_expiration
|
|
18
20
|
after_create :send_team_invite_email
|
|
19
21
|
|
|
@@ -42,5 +44,11 @@ module SpreeCmCommissioner
|
|
|
42
44
|
def url_valid?
|
|
43
45
|
expires_at.present? && expires_at > Time.current
|
|
44
46
|
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def normalize_email
|
|
51
|
+
self.email = email.strip.downcase if email.present?
|
|
52
|
+
end
|
|
45
53
|
end
|
|
46
54
|
end
|
|
@@ -12,7 +12,7 @@ module SpreeCmCommissioner
|
|
|
12
12
|
keys = inventory_items.map { |item| "inventory:#{item.id}" }
|
|
13
13
|
return [] unless keys.any?
|
|
14
14
|
|
|
15
|
-
counts = SpreeCmCommissioner.
|
|
15
|
+
counts = SpreeCmCommissioner.inventory_redis_pool.with { |redis| redis.mget(*keys) }
|
|
16
16
|
inventory_items.map.with_index do |inventory_item, i|
|
|
17
17
|
::SpreeCmCommissioner::CachedInventoryItem.new(
|
|
18
18
|
inventory_key: keys[i],
|
|
@@ -30,7 +30,7 @@ module SpreeCmCommissioner
|
|
|
30
30
|
return count_in_redis.to_i if count_in_redis.present?
|
|
31
31
|
return inventory_item.quantity_available unless inventory_item.active?
|
|
32
32
|
|
|
33
|
-
SpreeCmCommissioner.
|
|
33
|
+
SpreeCmCommissioner.inventory_redis_pool.with do |redis|
|
|
34
34
|
redis.set(key, inventory_item.quantity_available, ex: inventory_item.redis_expired_in)
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -51,13 +51,13 @@ module SpreeCmCommissioner
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def unstock(keys, quantities)
|
|
54
|
-
SpreeCmCommissioner.
|
|
54
|
+
SpreeCmCommissioner.inventory_redis_pool.with do |redis|
|
|
55
55
|
redis.eval(unstock_redis_script, keys: keys, argv: quantities)
|
|
56
56
|
end.positive?
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def restock(keys, quantities)
|
|
60
|
-
SpreeCmCommissioner.
|
|
60
|
+
SpreeCmCommissioner.inventory_redis_pool.with do |redis|
|
|
61
61
|
redis.eval(restock_redis_script, keys: keys, argv: quantities)
|
|
62
62
|
end.positive?
|
|
63
63
|
end
|
|
@@ -27,13 +27,13 @@ module SpreeCmCommissioner
|
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
-
# layoutable (taxon, vehicle) must have
|
|
30
|
+
# layoutable (taxon, vehicle) must have preload_seat_layout_id attribute,
|
|
31
31
|
# either as a column or as a key in a JSONB column (e.g., metadata)
|
|
32
32
|
def sync_seat_layout_id_to_layoutable!
|
|
33
33
|
if destroyed?
|
|
34
|
-
layoutable.update!(
|
|
34
|
+
layoutable.update!(preload_seat_layout_id: nil)
|
|
35
35
|
else
|
|
36
|
-
layoutable.update!(
|
|
36
|
+
layoutable.update!(preload_seat_layout_id: id)
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
end
|
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
module TaxonDecorator
|
|
3
3
|
def self.prepended(base) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
4
|
+
base.include SpreeCmCommissioner::StoreMetadata
|
|
4
5
|
base.include SpreeCmCommissioner::TaxonKind
|
|
5
6
|
base.include SpreeCmCommissioner::ParticipationTypeBitwise
|
|
6
|
-
base.include SpreeCmCommissioner::
|
|
7
|
-
|
|
8
|
-
base.preference :background_color, :string
|
|
9
|
-
base.preference :foreground_color, :string
|
|
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
|
|
7
|
+
base.include SpreeCmCommissioner::EventMetadata
|
|
18
8
|
|
|
19
9
|
base.has_many :taxon_vendors, class_name: 'SpreeCmCommissioner::TaxonVendor'
|
|
20
10
|
base.has_many :vendors, through: :taxon_vendors
|
|
@@ -104,14 +94,6 @@ module SpreeCmCommissioner
|
|
|
104
94
|
end
|
|
105
95
|
end
|
|
106
96
|
|
|
107
|
-
def background_color
|
|
108
|
-
preferred_background_color
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def foreground_color
|
|
112
|
-
preferred_foreground_color
|
|
113
|
-
end
|
|
114
|
-
|
|
115
97
|
def set_kind
|
|
116
98
|
self.kind = taxonomy.kind
|
|
117
99
|
end
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
class Trip < Base
|
|
3
|
+
include SpreeCmCommissioner::StoreMetadata
|
|
3
4
|
include SpreeCmCommissioner::RouteType
|
|
4
5
|
|
|
5
6
|
attr_accessor :hours, :minutes, :seconds
|
|
6
7
|
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
store :public_metadata, accessors: %i[
|
|
12
|
-
seat_layout_id
|
|
13
|
-
], coder: JSON
|
|
8
|
+
# This model has no seat_layout column (polymorphic association), so we store the preload_seat_layout_id ID in public_metadata.
|
|
9
|
+
# This lets us check if a seat layout exists without triggering a database query.
|
|
10
|
+
# The ID is automatically updated whenever the seat_layout is saved.
|
|
11
|
+
store_public_metadata :preload_seat_layout_id, :integer
|
|
14
12
|
|
|
15
13
|
before_validation :convert_duration_to_seconds
|
|
16
14
|
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
class Vehicle < Base
|
|
3
|
+
include SpreeCmCommissioner::StoreMetadata
|
|
3
4
|
include SpreeCmCommissioner::VehicleType
|
|
4
5
|
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
store :public_metadata, accessors: %i[
|
|
10
|
-
seat_layout_id
|
|
11
|
-
], coder: JSON
|
|
6
|
+
# This model has no seat_layout column (polymorphic association), so we store the preload_seat_layout_id ID in public_metadata.
|
|
7
|
+
# This lets us check if a seat layout exists without triggering a database query.
|
|
8
|
+
# The ID is automatically updated whenever the seat_layout is saved.
|
|
9
|
+
store_public_metadata :preload_seat_layout_id, :integer
|
|
12
10
|
|
|
13
11
|
belongs_to :vendor, class_name: 'Spree::Vendor'
|
|
14
12
|
|
data/app/overrides/spree/admin/taxons/_form/background_color_and_foreground_color.html.erb.deface
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
<div class="row <%= "d-none" unless @taxon.depth == 1 %>">
|
|
6
6
|
<div class="col-md-6">
|
|
7
|
-
<%= f.label :
|
|
8
|
-
<%= f.text_field :
|
|
7
|
+
<%= f.label :background_color, Spree.t('background_color'), class: 'form-label' %>
|
|
8
|
+
<%= f.text_field :background_color, class: 'form-control color-picker', placeholder: '#FFFFFF', value: @object.background_color %>
|
|
9
9
|
<div class="color-preview" style="margin-top: 10px;">
|
|
10
|
-
<%= content_tag :div, nil, style: "background-color: #{@object.
|
|
10
|
+
<%= content_tag :div, nil, style: "background-color: #{@object.background_color}; width: 60px; height: 60px; border: 1px solid #ccc; box-sizing: border-box; display: block;" %>
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
13
13
|
<div class="col-md-6">
|
|
14
|
-
<%= f.label :
|
|
15
|
-
<%= f.text_field :
|
|
14
|
+
<%= f.label :foreground_color, Spree.t('foreground_color'), class: 'form-label' %>
|
|
15
|
+
<%= f.text_field :foreground_color, class: 'form-control color-picker', placeholder: '#FFFFFF', value: @object.foreground_color %>
|
|
16
16
|
<div class="color-preview" style="margin-top: 10px;">
|
|
17
|
-
<%= content_tag :div, nil, style: "background-color: #{@object.
|
|
17
|
+
<%= content_tag :div, nil, style: "background-color: #{@object.foreground_color}; width: 60px; height: 60px; border: 1px solid #ccc; box-sizing: border-box; display: block;" %>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
@@ -2,17 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
<%# Sections don't use video banner, so we hide the form here to avoid confusing admin. %>
|
|
4
4
|
|
|
5
|
-
<%= f.field_container :
|
|
6
|
-
<%= f.check_box :
|
|
7
|
-
<%= f.label :
|
|
8
|
-
<%= f.error_message_on :
|
|
5
|
+
<%= f.field_container :group_check_in_enabled, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
|
|
6
|
+
<%= f.check_box :group_check_in_enabled, class: 'custom-control-input' %>
|
|
7
|
+
<%= f.label :group_check_in_enabled, Spree.t(:enable_group_check_in), class: 'custom-control-label' %>
|
|
8
|
+
<%= f.error_message_on :group_check_in_enabled %>
|
|
9
9
|
<small class="form-text text-muted">
|
|
10
10
|
Enabled by default for most events where users can purchase multiple tickets or use group scanning. For large events (like PSK, ASK, etc.) or invitation-based events, you should disable this to ensure each ticket or invitation is scanned individually.
|
|
11
11
|
</small>
|
|
12
12
|
<% end if @taxon.event? && @taxon.depth == 1 %> %>
|
|
13
13
|
|
|
14
|
-
<%= f.field_container :
|
|
15
|
-
<%= f.check_box :
|
|
16
|
-
<%= f.label :
|
|
17
|
-
<%= f.error_message_on :
|
|
14
|
+
<%= f.field_container :individual_check_in_enabled, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
|
|
15
|
+
<%= f.check_box :individual_check_in_enabled, class: 'custom-control-input' %>
|
|
16
|
+
<%= f.label :individual_check_in_enabled, Spree.t(:enable_individual_check_in), class: 'custom-control-label' %>
|
|
17
|
+
<%= f.error_message_on :individual_check_in_enabled %>
|
|
18
|
+
<% end if @taxon.event? && @taxon.depth == 1 %> %>
|
|
19
|
+
|
|
20
|
+
<%= f.field_container :allow_manual_search_for_operator, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
|
|
21
|
+
<%= f.check_box :allow_manual_search_for_operator, class: 'custom-control-input' %>
|
|
22
|
+
<%= f.label :allow_manual_search_for_operator, Spree.t(:allow_manual_search_for_operator), class: 'custom-control-label' %>
|
|
23
|
+
<%= f.error_message_on :allow_manual_search_for_operator %>
|
|
18
24
|
<% end if @taxon.event? && @taxon.depth == 1 %> %>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module V2
|
|
3
|
+
module Storefront
|
|
4
|
+
module OrderSerializerDecorator
|
|
5
|
+
def self.prepended(base)
|
|
6
|
+
base.attribute :has_incomplete_guest_info do |order|
|
|
7
|
+
order.user.public_metadata['has_incomplete_guest_info'] || false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
base.attribute :guest_info_status do |order|
|
|
11
|
+
{
|
|
12
|
+
incomplete: order_has_incomplete_guests?(order),
|
|
13
|
+
can_mark_complete: can_mark_complete?(order)
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.order_has_incomplete_guests?(order)
|
|
19
|
+
order.line_items.any? do |line_item|
|
|
20
|
+
line_item.guests.any? do |guest|
|
|
21
|
+
guest_incomplete?(guest)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.guest_incomplete?(guest)
|
|
27
|
+
(guest.pre_registration_fields? && !guest.pre_registration_completed?) ||
|
|
28
|
+
(guest.post_registration_fields? && !guest.post_registration_completed?) ||
|
|
29
|
+
(guest.during_check_in_fields? && !guest.check_in_completed?)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.can_mark_complete?(order)
|
|
33
|
+
event_ended?(order) || all_guest_info_complete?(order)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.event_ended?(order)
|
|
37
|
+
order.line_items.any? { |line_item| event_ended_for_line_item?(line_item) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.event_ended_for_line_item?(line_item)
|
|
41
|
+
event = line_item.event
|
|
42
|
+
event&.to_date.present? && event.to_date < Time.zone.today
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.all_guest_info_complete?(order)
|
|
46
|
+
order.line_items.all? { |line_item| line_item_guests_complete?(line_item) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.line_item_guests_complete?(line_item)
|
|
50
|
+
line_item.guests.all? { |guest| guest_complete?(guest) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.guest_complete?(guest)
|
|
54
|
+
guest.pre_registration_completed? &&
|
|
55
|
+
(!guest.post_registration_fields? || guest.post_registration_completed?) &&
|
|
56
|
+
(!guest.during_check_in_fields? || guest.check_in_completed?)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Spree::V2::Storefront::OrderSerializer.prepend(Spree::V2::Storefront::OrderSerializerDecorator)
|
|
@@ -18,7 +18,7 @@ module Spree
|
|
|
18
18
|
:purchasable_on, :vendor_id, :available_on, :hide_video_banner
|
|
19
19
|
|
|
20
20
|
# To get full seat layout details, call the seat_layouts API with this ID.
|
|
21
|
-
base.
|
|
21
|
+
base.attribute :seat_layout_id, &:preload_seat_layout_id
|
|
22
22
|
|
|
23
23
|
base.attribute :purchasable_on_app do |taxon|
|
|
24
24
|
taxon.purchasable_on == 'app' || taxon.purchasable_on == 'both'
|
|
@@ -31,10 +31,11 @@ module Spree
|
|
|
31
31
|
from_date
|
|
32
32
|
kind
|
|
33
33
|
subtitle
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
background_color
|
|
35
|
+
foreground_color
|
|
36
|
+
group_check_in_enabled
|
|
37
|
+
individual_check_in_enabled
|
|
38
|
+
allow_manual_search_for_operator
|
|
38
39
|
show_badge_status
|
|
39
40
|
purchasable_on
|
|
40
41
|
available_on
|
data/config/routes.rb
CHANGED
|
@@ -477,6 +477,7 @@ Spree::Core::Engine.add_routes do
|
|
|
477
477
|
end
|
|
478
478
|
|
|
479
479
|
namespace :tenant do
|
|
480
|
+
resource :token, controller: :access_tokens, only: [:create]
|
|
480
481
|
resources :vendors
|
|
481
482
|
resources :products
|
|
482
483
|
resources :taxons, only: %i[index show], id: /.+/
|
|
@@ -560,6 +561,7 @@ Spree::Core::Engine.add_routes do
|
|
|
560
561
|
end
|
|
561
562
|
|
|
562
563
|
namespace :storefront do
|
|
564
|
+
resource :token, controller: :access_tokens, only: [:create]
|
|
563
565
|
resources :waiting_room_sessions, only: :create
|
|
564
566
|
resources :vattanac_banks, only: %i[create]
|
|
565
567
|
resource :cart, controller: :cart, only: %i[show create destroy] do
|
|
@@ -599,6 +601,10 @@ Spree::Core::Engine.add_routes do
|
|
|
599
601
|
|
|
600
602
|
namespace :account do
|
|
601
603
|
resource :preferred_payment_method, controller: :preferred_payment_method, only: %i[show update]
|
|
604
|
+
patch :mark_guest_info_complete, to: 'mark_guest_info_complete#update'
|
|
605
|
+
resources :guests, only: [] do
|
|
606
|
+
patch 'dynamic_fields/:phase', to: 'guest_dynamic_fields#update_dynamic_field', as: :update_dynamic_fields
|
|
607
|
+
end
|
|
602
608
|
end
|
|
603
609
|
|
|
604
610
|
resource :s3_signed_urls
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Scoped Access Token Endpoints
|
|
2
|
+
|
|
3
|
+
This document describes the new scoped access token endpoints that provide better security and clarity for different API usage patterns.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Previously, all access tokens were retrieved via the generic `/oauth/token` endpoint, which made it difficult to apply security rules and prevent abuse. The new scoped endpoints provide:
|
|
8
|
+
|
|
9
|
+
- **Clear separation** between storefront and tenant API usage
|
|
10
|
+
- **Enhanced security** with application-specific validations
|
|
11
|
+
- **Better monitoring** and logging capabilities
|
|
12
|
+
- **Easier maintenance** and debugging
|
|
13
|
+
|
|
14
|
+
## Endpoints
|
|
15
|
+
|
|
16
|
+
### Storefront Access Token Endpoint
|
|
17
|
+
|
|
18
|
+
**POST** `/api/v2/storefront/token`
|
|
19
|
+
|
|
20
|
+
Issues access tokens for storefront applications.
|
|
21
|
+
|
|
22
|
+
#### Example Response
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"access_token": "abc123...",
|
|
27
|
+
"token_type": "Bearer",
|
|
28
|
+
"expires_in": 86400,
|
|
29
|
+
"refresh_token": "def456...",
|
|
30
|
+
"created_at": 1640995200
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Tenant Access Token Endpoint
|
|
35
|
+
|
|
36
|
+
**POST** `/api/v2/tenant/token`
|
|
37
|
+
|
|
38
|
+
Issues access tokens for tenant applications (third-party integrations, partner apps, etc.).
|
|
39
|
+
|
|
40
|
+
#### Example Response
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"access_token": "xyz789...",
|
|
45
|
+
"token_type": "Bearer",
|
|
46
|
+
"expires_in": 86400,
|
|
47
|
+
"refresh_token": "uvw012...",
|
|
48
|
+
"created_at": 1640995200
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Migration Guide
|
|
53
|
+
|
|
54
|
+
### For Storefront Applications
|
|
55
|
+
|
|
56
|
+
1. **Update endpoint URL**: Change from `/oauth/token` to `/api/v2/storefront/token`
|
|
57
|
+
2. **No other changes required**: All existing parameters and flows work identically
|
|
58
|
+
|
|
59
|
+
### For Tenant Applications
|
|
60
|
+
|
|
61
|
+
1. **Update endpoint URL**: Change from `/oauth/token` to `/api/v2/tenant/token`
|
|
62
|
+
2. **No other changes required**: All existing parameters and flows work identically
|
|
63
|
+
|
|
64
|
+
### Legacy Support
|
|
65
|
+
|
|
66
|
+
The original `/oauth/token` endpoint remains available.
|
|
67
|
+
|
|
68
|
+
## Implementation
|
|
69
|
+
|
|
70
|
+
### Controller Inheritance
|
|
71
|
+
|
|
72
|
+
Both controllers inherit from `Doorkeeper::TokensController`.
|
|
73
|
+
|
|
74
|
+
### Route Configuration
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
namespace :storefront do
|
|
78
|
+
resource :token, controller: :access_tokens, only: [:create]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
namespace :tenant do
|
|
82
|
+
resource :token, controller: :access_tokens, only: [:create]
|
|
83
|
+
end
|
|
84
|
+
```
|
|
@@ -52,27 +52,49 @@ require 'byebug' if Rails.env.development? || Rails.env.test?
|
|
|
52
52
|
|
|
53
53
|
module SpreeCmCommissioner
|
|
54
54
|
class << self
|
|
55
|
-
# Allows overriding the default Redis connection
|
|
56
|
-
attr_writer :
|
|
55
|
+
# Allows overriding the default Redis connection pools with custom ones
|
|
56
|
+
attr_writer :inventory_redis_pool, :external_integrations_redis_pool
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
# Inventory Redis pool for inventory management
|
|
59
|
+
def inventory_redis_pool
|
|
60
|
+
@inventory_redis_pool ||= default_inventory_redis_pool
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
#
|
|
63
|
-
def
|
|
64
|
-
@
|
|
63
|
+
# External integrations Redis pool for external API integrations
|
|
64
|
+
def external_integrations_redis_pool
|
|
65
|
+
@external_integrations_redis_pool ||= default_external_integrations_redis_pool
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Resets all Redis pools, useful for testing or reinitialization
|
|
69
|
+
def reset_redis_pools
|
|
70
|
+
@inventory_redis_pool = nil
|
|
71
|
+
@external_integrations_redis_pool = nil
|
|
65
72
|
end
|
|
66
73
|
|
|
67
74
|
private
|
|
68
75
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
def default_inventory_redis_pool
|
|
77
|
+
create_redis_pool(
|
|
78
|
+
url: ENV.fetch('REDIS_INVENTORY_URL', ENV.fetch('REDIS_URL', 'redis://localhost:6379/12')),
|
|
79
|
+
pool_size: ENV.fetch('REDIS_INVENTORY_POOL_SIZE', ENV.fetch('REDIS_POOL_SIZE', '5')).to_i,
|
|
80
|
+
timeout: ENV.fetch('REDIS_TIMEOUT', '5').to_i
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def default_external_integrations_redis_pool
|
|
85
|
+
create_redis_pool(
|
|
86
|
+
url: ENV.fetch('REDIS_EXTERNAL_INTEGRATIONS_URL', ENV.fetch('REDIS_URL', 'redis://localhost:6379/13')),
|
|
87
|
+
pool_size: ENV.fetch('REDIS_EXTERNAL_INTEGRATIONS_POOL_SIZE', ENV.fetch('REDIS_POOL_SIZE', '5')).to_i,
|
|
88
|
+
timeout: ENV.fetch('REDIS_TIMEOUT', '5').to_i
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def create_redis_pool(url:, pool_size:, timeout:)
|
|
93
|
+
ssl_params = {}
|
|
94
|
+
ssl_params.merge!({ verify_mode: OpenSSL::SSL::VERIFY_NONE }) if ENV['HEROKU_APP_ID'].present?
|
|
73
95
|
|
|
74
96
|
ConnectionPool.new(size: pool_size, timeout: timeout) do
|
|
75
|
-
Redis.new(url:
|
|
97
|
+
Redis.new(url: url, timeout: timeout, ssl_params: ssl_params)
|
|
76
98
|
end
|
|
77
99
|
end
|
|
78
100
|
end
|
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.1.
|
|
4
|
+
version: 2.1.9.pre.pre1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- You
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: spree
|
|
@@ -909,8 +909,11 @@ files:
|
|
|
909
909
|
- app/controllers/spree/api/v2/platform/seat_number_layouts_controller.rb
|
|
910
910
|
- app/controllers/spree/api/v2/platform/taxons_controller_decorator.rb
|
|
911
911
|
- app/controllers/spree/api/v2/resource_controller_decorator.rb
|
|
912
|
+
- app/controllers/spree/api/v2/storefront/access_tokens_controller.rb
|
|
912
913
|
- app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb
|
|
913
914
|
- app/controllers/spree/api/v2/storefront/accommodations_controller.rb
|
|
915
|
+
- app/controllers/spree/api/v2/storefront/account/guest_dynamic_fields_controller.rb
|
|
916
|
+
- app/controllers/spree/api/v2/storefront/account/mark_guest_info_complete_controller.rb
|
|
914
917
|
- app/controllers/spree/api/v2/storefront/account/orders_controller_decorator.rb
|
|
915
918
|
- app/controllers/spree/api/v2/storefront/account/preferred_payment_method_controller.rb
|
|
916
919
|
- app/controllers/spree/api/v2/storefront/account_checker_controller.rb
|
|
@@ -978,6 +981,7 @@ files:
|
|
|
978
981
|
- app/controllers/spree/api/v2/storefront/waiting_room_sessions_controller.rb
|
|
979
982
|
- app/controllers/spree/api/v2/storefront/wished_items_controller.rb
|
|
980
983
|
- app/controllers/spree/api/v2/storefront/wishlists_controller_decorator.rb
|
|
984
|
+
- app/controllers/spree/api/v2/tenant/access_tokens_controller.rb
|
|
981
985
|
- app/controllers/spree/api/v2/tenant/account/orders_controller.rb
|
|
982
986
|
- app/controllers/spree/api/v2/tenant/account_checker_controller.rb
|
|
983
987
|
- app/controllers/spree/api/v2/tenant/account_controller.rb
|
|
@@ -1309,7 +1313,7 @@ files:
|
|
|
1309
1313
|
- app/mailers/spree_cm_commissioner/pin_code_mailer.rb
|
|
1310
1314
|
- app/mailers/spree_cm_commissioner/team_invite_mailer.rb
|
|
1311
1315
|
- app/models/.gitkeep
|
|
1312
|
-
- app/models/concerns/spree_cm_commissioner/
|
|
1316
|
+
- app/models/concerns/spree_cm_commissioner/event_metadata.rb
|
|
1313
1317
|
- app/models/concerns/spree_cm_commissioner/homepage_section_bitwise.rb
|
|
1314
1318
|
- app/models/concerns/spree_cm_commissioner/json_preference_validator.rb
|
|
1315
1319
|
- app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb
|
|
@@ -1329,6 +1333,7 @@ files:
|
|
|
1329
1333
|
- app/models/concerns/spree_cm_commissioner/product_type.rb
|
|
1330
1334
|
- app/models/concerns/spree_cm_commissioner/route_type.rb
|
|
1331
1335
|
- app/models/concerns/spree_cm_commissioner/service_calendar_type.rb
|
|
1336
|
+
- app/models/concerns/spree_cm_commissioner/store_metadata.rb
|
|
1332
1337
|
- app/models/concerns/spree_cm_commissioner/store_preference.rb
|
|
1333
1338
|
- app/models/concerns/spree_cm_commissioner/taxon_kind.rb
|
|
1334
1339
|
- app/models/concerns/spree_cm_commissioner/tenant_preference.rb
|
|
@@ -1738,6 +1743,7 @@ files:
|
|
|
1738
1743
|
- app/serializers/spree/v2/storefront/notification_serializer.rb
|
|
1739
1744
|
- app/serializers/spree/v2/storefront/option_type_serializer_decorator.rb
|
|
1740
1745
|
- app/serializers/spree/v2/storefront/option_value_serializer_decorator.rb
|
|
1746
|
+
- app/serializers/spree/v2/storefront/order_serializer_decorator.rb
|
|
1741
1747
|
- app/serializers/spree/v2/storefront/place_serializer.rb
|
|
1742
1748
|
- app/serializers/spree/v2/storefront/product_serializer_decorator.rb
|
|
1743
1749
|
- app/serializers/spree/v2/storefront/role_serializer.rb
|
|
@@ -2773,6 +2779,7 @@ files:
|
|
|
2773
2779
|
- db/migrate/20250930094228_add_application_id_to_cm_pin_codes.rb
|
|
2774
2780
|
- db/migrate/20250930100657_add_settings_to_spree_oauth_applications.rb
|
|
2775
2781
|
- docker-compose.yml
|
|
2782
|
+
- docs/api/scoped-access-token-endpoints.md
|
|
2776
2783
|
- docs/option_types/attr_types.md
|
|
2777
2784
|
- docs/private_key.pem
|
|
2778
2785
|
- docs/public_key.pem
|
|
@@ -2931,9 +2938,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
2931
2938
|
version: '2.7'
|
|
2932
2939
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
2933
2940
|
requirements:
|
|
2934
|
-
- - "
|
|
2941
|
+
- - ">"
|
|
2935
2942
|
- !ruby/object:Gem::Version
|
|
2936
|
-
version:
|
|
2943
|
+
version: 1.3.1
|
|
2937
2944
|
requirements:
|
|
2938
2945
|
- none
|
|
2939
2946
|
rubygems_version: 3.4.1
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module SpreeCmCommissioner
|
|
2
|
-
module EventCheckInFlowable
|
|
3
|
-
extend ActiveSupport::Concern
|
|
4
|
-
|
|
5
|
-
included do
|
|
6
|
-
preference :group_check_in_enabled, :boolean, default: true
|
|
7
|
-
preference :individual_check_in_enabled, :boolean, default: true
|
|
8
|
-
|
|
9
|
-
before_validation :validate_at_least_one_check_in_flow_presence
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def check_in_flows
|
|
13
|
-
flows = []
|
|
14
|
-
flows << 'group' if group_check_in_enabled?
|
|
15
|
-
flows << 'individual' if individual_check_in_enabled?
|
|
16
|
-
flows
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def group_check_in_enabled? = preferred_group_check_in_enabled
|
|
20
|
-
def individual_check_in_enabled? = preferred_individual_check_in_enabled
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def validate_at_least_one_check_in_flow_presence
|
|
25
|
-
return if check_in_flows.any?
|
|
26
|
-
|
|
27
|
-
errors.add(:check_in_flows, 'must have at least one flow selected')
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|