spree_cm_commissioner 2.1.7 → 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 +8 -1
- data/app/controllers/spree/admin/user_security_controller.rb +25 -0
- 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/user_decorator.rb +16 -0
- 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/overrides/spree/admin/users/_tabs/tabs.html.erb.deface +6 -0
- 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/app/views/spree/admin/user_security/show.html.erb +25 -0
- data/config/initializers/spree_permitted_attributes.rb +5 -4
- data/config/locales/en.yml +1 -0
- data/config/routes.rb +11 -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 +35 -12
- data/spree_cm_commissioner.gemspec +1 -0
- metadata +28 -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
|
@@ -34,7 +34,7 @@ GIT
|
|
|
34
34
|
PATH
|
|
35
35
|
remote: .
|
|
36
36
|
specs:
|
|
37
|
-
spree_cm_commissioner (2.1.
|
|
37
|
+
spree_cm_commissioner (2.1.9.pre.pre1)
|
|
38
38
|
activerecord-multi-tenant
|
|
39
39
|
activerecord_json_validator (~> 2.1, >= 2.1.3)
|
|
40
40
|
aws-sdk-cloudfront
|
|
@@ -44,6 +44,7 @@ PATH
|
|
|
44
44
|
byebug
|
|
45
45
|
connection_pool
|
|
46
46
|
counter_culture (~> 3.2)
|
|
47
|
+
devise-two-factor
|
|
47
48
|
dry-validation (~> 1.10)
|
|
48
49
|
elasticsearch (~> 8.5)
|
|
49
50
|
exception_notification
|
|
@@ -291,6 +292,11 @@ GEM
|
|
|
291
292
|
warden (~> 1.2.3)
|
|
292
293
|
devise-encryptable (0.2.0)
|
|
293
294
|
devise (>= 2.1.0)
|
|
295
|
+
devise-two-factor (6.1.0)
|
|
296
|
+
activesupport (>= 7.0, < 8.1)
|
|
297
|
+
devise (~> 4.0)
|
|
298
|
+
railties (>= 7.0, < 8.1)
|
|
299
|
+
rotp (~> 6.0)
|
|
294
300
|
diff-lcs (1.5.0)
|
|
295
301
|
docile (1.4.0)
|
|
296
302
|
domain_name (0.5.20190701)
|
|
@@ -701,6 +707,7 @@ GEM
|
|
|
701
707
|
railties (>= 5.2)
|
|
702
708
|
retriable (3.1.2)
|
|
703
709
|
rexml (3.2.6)
|
|
710
|
+
rotp (6.3.0)
|
|
704
711
|
rqrcode (2.2.0)
|
|
705
712
|
chunky_png (~> 1.0)
|
|
706
713
|
rqrcode_core (~> 1.0)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class UserSecurityController < Spree::Admin::BaseController
|
|
4
|
+
before_action :load_user
|
|
5
|
+
|
|
6
|
+
def disable_authenticator
|
|
7
|
+
@user.otp_secret = nil
|
|
8
|
+
@user.otp_required_for_login = false
|
|
9
|
+
begin
|
|
10
|
+
@user.save!
|
|
11
|
+
flash[:success] = I18n.t('authenticator.success_disabled_2fa')
|
|
12
|
+
rescue StandardError => e
|
|
13
|
+
flash[:error] = "Failed to disable 2FA: #{e.message}"
|
|
14
|
+
end
|
|
15
|
+
redirect_to admin_user_security_path(@user)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def load_user
|
|
21
|
+
@user = Spree::User.find(params[:user_id])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -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
|