spree_cm_commissioner 2.14.11 → 2.15.1
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 +1 -2
- data/.tool-versions +1 -1
- data/Gemfile.lock +2 -2
- data/app/controllers/concerns/spree_cm_commissioner/operator/event_authorization.rb +23 -0
- data/app/controllers/concerns/spree_cm_commissioner/waiting_room_authorization.rb +12 -1
- data/app/controllers/spree/admin/system/waiting_room_sessions_controller.rb +12 -4
- data/app/controllers/spree/api/v2/operator/check_ins_controller.rb +3 -1
- data/app/controllers/spree/api/v2/operator/event_charts_controller.rb +20 -24
- data/app/controllers/spree/api/v2/operator/pie_chart_event_aggregators_controller.rb +8 -4
- data/app/controllers/spree/api/v2/storefront/draft_orders_controller.rb +3 -1
- data/app/controllers/spree/api/v2/storefront/queue_draft_orders_controller.rb +5 -0
- data/app/controllers/spree/api/v2/storefront/transit/draft_orders_controller.rb +2 -1
- data/app/controllers/spree/api/v2/tenant/draft_orders_controller.rb +3 -1
- data/app/controllers/spree/api/v2/tenant/private_sale_products_controller.rb +33 -0
- data/app/controllers/spree/api/v2/tenant/queue_draft_orders_controller.rb +64 -0
- data/app/controllers/spree/api/v2/tenant/seat_layouts_controller.rb +93 -0
- data/app/controllers/spree/api/v2/tenant/transit/draft_orders_controller.rb +2 -1
- data/app/jobs/spree_cm_commissioner/queue_draft_orders/process_job.rb +6 -3
- data/app/models/concerns/spree_cm_commissioner/event_metadata.rb +10 -0
- data/app/models/concerns/spree_cm_commissioner/homepage_section_relatable_concern.rb +0 -8
- data/app/models/concerns/spree_cm_commissioner/order_attribution.rb +16 -0
- data/app/models/concerns/spree_cm_commissioner/promotion/rules/oauth_application_scoped.rb +44 -0
- data/app/models/spree_cm_commissioner/dynamic_field.rb +0 -11
- data/app/models/spree_cm_commissioner/dynamic_field_option.rb +0 -11
- data/app/models/spree_cm_commissioner/homepage_background.rb +0 -3
- data/app/models/spree_cm_commissioner/homepage_section.rb +0 -3
- data/app/models/spree_cm_commissioner/inventory_item.rb +12 -31
- data/app/models/spree_cm_commissioner/maintenance_tasks/cache_invalidation.rb +2 -8
- data/app/models/spree_cm_commissioner/menu_decorator.rb +0 -3
- data/app/models/spree_cm_commissioner/menu_item_decorator.rb +11 -3
- data/app/models/spree_cm_commissioner/oauth_application_promotion_rule.rb +8 -0
- data/app/models/spree_cm_commissioner/order_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +0 -13
- data/app/models/spree_cm_commissioner/product_dynamic_field.rb +0 -9
- data/app/models/spree_cm_commissioner/product_option_type_decorator.rb +0 -6
- data/app/models/spree_cm_commissioner/promotion/rules/customer_order_count.rb +87 -0
- data/app/models/spree_cm_commissioner/promotion/rules/oauth_applications.rb +34 -0
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +0 -6
- data/app/models/spree_cm_commissioner/variant_decorator.rb +0 -4
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +0 -3
- data/app/models/spree_cm_commissioner/waiting_room_session.rb +4 -0
- data/app/overrides/spree/admin/menu_items/_form/hidden_mini_apps_field.html.erb.deface +16 -0
- data/app/overrides/spree/admin/taxons/_form/check_in_flows.html.erb.deface +18 -0
- data/app/serializers/spree/api/v2/base_serializer_decorator.rb +2 -2
- data/app/serializers/spree/v2/tenant/cart_serializer.rb +2 -1
- data/app/serializers/spree_cm_commissioner/v2/operator/dashboard_crew_event_serializer.rb +32 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/menu_item_serializer_decorator.rb +4 -0
- data/app/services/spree_cm_commissioner/api_caches/fetch_details.rb +2 -3
- data/app/services/spree_cm_commissioner/doorkeeper_refresh_token_request_decorator.rb +23 -0
- data/app/services/spree_cm_commissioner/doorkeeper_server_decorator.rb +28 -0
- data/app/services/spree_cm_commissioner/draft_orders/create.rb +6 -1
- data/app/services/spree_cm_commissioner/events/clone.rb +238 -0
- data/app/services/spree_cm_commissioner/events/publish_stock_statuses_to_firestore.rb +11 -36
- data/app/services/spree_cm_commissioner/imports/orders/create.rb +27 -10
- data/app/services/spree_cm_commissioner/mini_app_login_application.rb +32 -0
- data/app/services/spree_cm_commissioner/transit_order/create.rb +9 -4
- data/app/services/spree_cm_commissioner/waiting_room/admission/call_guests_chunk.rb +9 -5
- data/app/services/spree_cm_commissioner/waiting_room/guests/list.rb +10 -5
- data/app/views/spree/admin/private_sales/_stock.html.erb +11 -10
- data/app/views/spree/admin/promotions/rules/_customer_order_count.html.erb +20 -0
- data/app/views/spree/admin/promotions/rules/_oauth_application_select.html.erb +15 -0
- data/app/views/spree/admin/promotions/rules/_oauth_applications.html.erb +6 -0
- data/app/views/spree/admin/system/waiting_room_sessions/_guests_pagination.html.erb +1 -1
- data/app/views/spree/admin/system/waiting_room_sessions/guests.html.erb +19 -7
- data/config/initializers/spree_permitted_attributes.rb +3 -0
- data/config/locales/en.yml +30 -2
- data/config/locales/km.yml +27 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20261105000001_add_hidden_mini_app_to_spree_menu_items.rb +5 -0
- data/db/migrate/20261105000001_add_waiting_room_session_id_to_spree_orders.rb +13 -0
- data/db/migrate/20261105000002_add_index_to_spree_orders_waiting_room_session_id.rb +13 -0
- data/db/migrate/20261105000003_add_queue_fields_to_cm_waiting_room_sessions.rb +12 -0
- data/db/migrate/20261105000004_add_oauth_application_id_to_spree_orders.rb +13 -0
- data/db/migrate/20261110000001_create_cm_oauth_application_promotion_rules.rb +16 -0
- data/lib/spree_cm_commissioner/engine.rb +3 -1
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +24 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7cd929df06e0243dc4cf2b0b4ffc57d3346ced9f9d0cb5bdd3142d6adc153a88
|
|
4
|
+
data.tar.gz: 29a71dceac61e9e6497137c525ff9e76ace8ca1a179f0dc6eab157fd83a30a4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2bd7890db6844d7057a5e89f8e2cd0285e387772aaccba0ba2806aee4b7f78de3cea566402fb5a97279613bae6be342bbef2b55e2d185c82d8d85fec59401520
|
|
7
|
+
data.tar.gz: 682f9b46dae492ff3f0bdb9f558dccc3185ec4b9a6a0770e4fca4146ecd496e90577b97c049a9a171f7ab6ac76486fc14d4cdefe6bf7098ec96de9b8c09c170b
|
data/.env.example
CHANGED
|
@@ -49,8 +49,6 @@ DISTANCE_SIGNING_KEY=hei********************VY
|
|
|
49
49
|
|
|
50
50
|
# CDN cache durations (s-maxage, in seconds) for listed public APIs; client max-age is always 0, unlisted APIs are never CDN-cached
|
|
51
51
|
# See: app/controllers/concerns/spree_cm_commissioner/content_cachable.rb
|
|
52
|
-
CACHE_AUTO_INVALIDATION_DISABLED="" # Set to yes to stop automatic CDN cache invalidation on save (kill switch); a clear-cache click still sends one
|
|
53
|
-
CACHE_SERIALIZER_DISABLED="" # Set to yes to turn off Spree's serializer record cache everywhere
|
|
54
52
|
CACHE_STATIC_MAX_AGE=86400 # Static content (CMS pages) - 1 day
|
|
55
53
|
CACHE_REALTIME_MAX_AGE=300 # Everything else listed (homepage, menus, products, events, vendors, trips, seat layouts, countries, provinces, guest card classes) - 5 minutes
|
|
56
54
|
|
|
@@ -95,3 +93,4 @@ ORDER_INTEGRITY_TELEGRAM_ALERT_ENABLED="no" # Set to yes to enable the order in
|
|
|
95
93
|
PRE_INVENTORY_DAYS_TRANSIT=180 # How many days ahead inventory items are pre-generated for transit variants
|
|
96
94
|
PRE_INVENTORY_DAYS_ACCOMMODATION=365 # How many days ahead inventory items are pre-generated for accommodation variants
|
|
97
95
|
PRE_INVENTORY_DAYS_SERVICE=180 # How many days ahead inventory items are pre-generated for service variants
|
|
96
|
+
PAYMENT_BASE_URL=http://localhost:4000
|
data/.tool-versions
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby 3.2.
|
|
1
|
+
ruby 3.2.11
|
data/Gemfile.lock
CHANGED
|
@@ -34,7 +34,7 @@ GIT
|
|
|
34
34
|
PATH
|
|
35
35
|
remote: .
|
|
36
36
|
specs:
|
|
37
|
-
spree_cm_commissioner (2.
|
|
37
|
+
spree_cm_commissioner (2.15.1)
|
|
38
38
|
activerecord-multi-tenant
|
|
39
39
|
activerecord_json_validator (~> 2.1, >= 2.1.3)
|
|
40
40
|
aws-sdk-cloudfront
|
|
@@ -886,7 +886,7 @@ GEM
|
|
|
886
886
|
spree_backend (>= 4.3.0.rc1)
|
|
887
887
|
spree_emails (>= 4.3.0.rc1)
|
|
888
888
|
spree_extension
|
|
889
|
-
spree_vpago (2.
|
|
889
|
+
spree_vpago (2.4.6)
|
|
890
890
|
faraday
|
|
891
891
|
google-cloud-firestore
|
|
892
892
|
spree_api (>= 4.5)
|
|
@@ -3,6 +3,8 @@ module SpreeCmCommissioner
|
|
|
3
3
|
module EventAuthorization
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
|
|
6
|
+
OPERATOR_CHART_TYPE = 'entry_type'.freeze
|
|
7
|
+
|
|
6
8
|
private
|
|
7
9
|
|
|
8
10
|
# True only when the current user holds the organizer role AND owns the
|
|
@@ -14,6 +16,27 @@ module SpreeCmCommissioner
|
|
|
14
16
|
|
|
15
17
|
spree_current_user.vendors.exists?(id: taxon.vendor_id)
|
|
16
18
|
end
|
|
19
|
+
|
|
20
|
+
# Whether the current user may load `chart_type` for `taxon`. A user holding the operator role —
|
|
21
|
+
# even if also an organizer — sees only the entry type chart, and only while the event allows
|
|
22
|
+
# operators the total ticket number or the event overview (allow_total_ticket_order_for_operator,
|
|
23
|
+
# allow_event_overview_for_operator); the participation chart exposes the event's overall
|
|
24
|
+
# show / no-show totals, so it is never shown to them. Everyone else: the event's vendor
|
|
25
|
+
# organizer sees every chart, and a user assigned to the event sees only the entry type chart.
|
|
26
|
+
def chart_type_allowed?(taxon, chart_type)
|
|
27
|
+
return false if taxon.nil? || spree_current_user.nil?
|
|
28
|
+
return operator_chart_allowed?(taxon, chart_type) if spree_current_user.operator?
|
|
29
|
+
return true if organizer_for_event?(taxon)
|
|
30
|
+
return false unless spree_current_user.events.exists?(id: taxon.id)
|
|
31
|
+
|
|
32
|
+
chart_type.to_s == OPERATOR_CHART_TYPE
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def operator_chart_allowed?(taxon, chart_type)
|
|
36
|
+
(taxon.allow_total_ticket_order_for_operator || taxon.allow_event_overview_for_operator) &&
|
|
37
|
+
chart_type.to_s == OPERATOR_CHART_TYPE &&
|
|
38
|
+
spree_current_user.events.exists?(id: taxon.id)
|
|
39
|
+
end
|
|
17
40
|
end
|
|
18
41
|
end
|
|
19
42
|
end
|
|
@@ -53,7 +53,18 @@ module SpreeCmCommissioner
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def active_waiting_room_session?
|
|
56
|
-
|
|
56
|
+
current_waiting_room_session.present?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The active session behind this request's token, memoized so the guard and the draft order it
|
|
60
|
+
# admits share one lookup. Draft-order actions store its id on the order for analytics even when
|
|
61
|
+
# the guard didn't run (non-enforced product, private sale, waiting room disabled): the app sends
|
|
62
|
+
# the token on every request while its session is live, and only an active one is ever linked.
|
|
63
|
+
def current_waiting_room_session
|
|
64
|
+
return @current_waiting_room_session if defined?(@current_waiting_room_session)
|
|
65
|
+
|
|
66
|
+
token = params[:waiting_room_session_token]
|
|
67
|
+
@current_waiting_room_session = token.present? ? SpreeCmCommissioner::WaitingRoomSession.active.find_by(token: token) : nil
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
# Verified, not just present: a made-up `st` must not skip the queue, so a token that fails
|
|
@@ -33,6 +33,7 @@ module Spree
|
|
|
33
33
|
@doc_id = params[:doc_id].presence
|
|
34
34
|
@per_page = guests_per_page
|
|
35
35
|
@in_queue_only = guests_in_queue_only?
|
|
36
|
+
@newest_first = guests_newest_first?
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# GET .../waiting_room_sessions/guests_status -- the poll target, mirroring
|
|
@@ -43,6 +44,7 @@ module Spree
|
|
|
43
44
|
date = params[:date].presence || Time.zone.today.strftime('%Y-%m-%d')
|
|
44
45
|
doc_id = params[:doc_id].presence
|
|
45
46
|
in_queue_only = guests_in_queue_only?
|
|
47
|
+
newest_first = guests_newest_first?
|
|
46
48
|
error = nil
|
|
47
49
|
|
|
48
50
|
guests =
|
|
@@ -56,17 +58,17 @@ module Spree
|
|
|
56
58
|
else
|
|
57
59
|
# Cached for the same span as the client's poll interval so N concurrently open admin
|
|
58
60
|
# tabs (each polling every 10s) collapse into one Firestore read per date per 10s,
|
|
59
|
-
# instead of one per open tab. Keyed by filter too -- "in queue"
|
|
60
|
-
# different Firestore queries, not a client-side slice of
|
|
61
|
+
# instead of one per open tab. Keyed by filter and order too -- "in queue"/"all" and
|
|
62
|
+
# oldest/newest are different Firestore queries, not a client-side slice of one result.
|
|
61
63
|
#
|
|
62
64
|
# A failed fetch (e.g. a missing Firestore composite index for in_queue_only's
|
|
63
65
|
# where+order combination) is never cached: caching `[]` would make the next 10s
|
|
64
66
|
# poll -- and the one after that -- believe the queue is empty instead of retrying.
|
|
65
|
-
cache_key = "admin/waiting_room_guests/#{date}/#{in_queue_only}"
|
|
67
|
+
cache_key = "admin/waiting_room_guests/#{date}/#{in_queue_only}/#{newest_first}"
|
|
66
68
|
docs = Rails.cache.read(cache_key)
|
|
67
69
|
|
|
68
70
|
if docs.nil?
|
|
69
|
-
result = SpreeCmCommissioner::WaitingRoom::Guests::List.call(date: date, in_queue_only: in_queue_only)
|
|
71
|
+
result = SpreeCmCommissioner::WaitingRoom::Guests::List.call(date: date, in_queue_only: in_queue_only, newest_first: newest_first)
|
|
70
72
|
if result.success?
|
|
71
73
|
docs = result.value[:guests]
|
|
72
74
|
Rails.cache.write(cache_key, docs, expires_in: 10.seconds)
|
|
@@ -122,6 +124,12 @@ module Spree
|
|
|
122
124
|
params[:status] != 'all'
|
|
123
125
|
end
|
|
124
126
|
|
|
127
|
+
# Defaults to oldest-first (queue order). ?order=newest is for spotting who joined last,
|
|
128
|
+
# which oldest-first buries on the final page -- or past MAX_DOCS, doesn't load at all.
|
|
129
|
+
def guests_newest_first?
|
|
130
|
+
params[:order] == 'newest'
|
|
131
|
+
end
|
|
132
|
+
|
|
125
133
|
# Same 10s window as the guest list itself: this is one extra doc read per render, and the
|
|
126
134
|
# "people ahead" figure it feeds only changes as often as Positions::Stamp ticks (10s),
|
|
127
135
|
# so caching it any shorter would just be re-fetching a value that hasn't moved.
|
|
@@ -50,10 +50,12 @@ module Spree
|
|
|
50
50
|
# The organizer who owns the event's vendor always sees check-in history.
|
|
51
51
|
# Crew/operators assigned via user.events only see it while the event is
|
|
52
52
|
# ongoing — once it ends, it becomes organizer-owned history. A missing/
|
|
53
|
-
# nonexistent taxon_id is restricted the same way.
|
|
53
|
+
# nonexistent taxon_id is restricted the same way. Any user, organizer included,
|
|
54
|
+
# loses it when the event turns off allow_check_in_history_for_operator (mirrors DashboardCrewEventSerializer#permissions).
|
|
54
55
|
def restricted?
|
|
55
56
|
taxon = Spree::Taxon.find_by(id: params[:taxon_id])
|
|
56
57
|
return true if taxon.nil?
|
|
58
|
+
return true unless taxon.allow_check_in_history_for_operator
|
|
57
59
|
return false if organizer_for_event?(taxon)
|
|
58
60
|
return true unless spree_current_user.events.exists?(id: taxon.id)
|
|
59
61
|
|
|
@@ -11,30 +11,26 @@ module Spree
|
|
|
11
11
|
ADDITIONAL_FIELDS = %i[participation entry_type].freeze
|
|
12
12
|
|
|
13
13
|
def index
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
else
|
|
17
|
-
kyc_fields = fetch_filtered_kyc_fields
|
|
18
|
-
types = ADDITIONAL_FIELDS + kyc_fields
|
|
14
|
+
kyc_fields = fetch_filtered_kyc_fields
|
|
15
|
+
types = (ADDITIONAL_FIELDS + kyc_fields).select { |type| chart_type_allowed?(event_taxon, type) }
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
17
|
+
resources = types.map do |type|
|
|
18
|
+
# Create unique ID by using chart_type as the identifier
|
|
19
|
+
SpreeCmCommissioner::EventChart.new(
|
|
20
|
+
id: type.to_s,
|
|
21
|
+
chart_type: type,
|
|
22
|
+
product_charts: nil
|
|
23
|
+
)
|
|
24
|
+
end
|
|
29
25
|
|
|
30
26
|
render json: SpreeCmCommissioner::V2::Operator::PieChartEventAggregatorSerializer.new(resources).serializable_hash
|
|
31
27
|
end
|
|
32
28
|
|
|
33
29
|
def resource
|
|
34
|
-
@resource = if
|
|
35
|
-
SpreeCmCommissioner::EventChart.new(id: params[:id], chart_type: params[:id] || 'participation', product_charts: [])
|
|
36
|
-
else
|
|
30
|
+
@resource = if chart_type_allowed?(event_taxon, chart_type)
|
|
37
31
|
event_chart_aggregator_queries.call
|
|
32
|
+
else
|
|
33
|
+
SpreeCmCommissioner::EventChart.new(id: params[:id], chart_type: chart_type, product_charts: [])
|
|
38
34
|
end
|
|
39
35
|
end
|
|
40
36
|
|
|
@@ -44,18 +40,18 @@ module Spree
|
|
|
44
40
|
|
|
45
41
|
private
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
def chart_type
|
|
44
|
+
params[:id] || 'participation'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def event_taxon
|
|
48
|
+
@event_taxon ||= Spree::Taxon.find_by(id: params[:taxon_id])
|
|
53
49
|
end
|
|
54
50
|
|
|
55
51
|
def event_chart_aggregator_queries
|
|
56
52
|
SpreeCmCommissioner::EventChartQueries.new(
|
|
57
53
|
taxon_id: params[:taxon_id],
|
|
58
|
-
chart_type:
|
|
54
|
+
chart_type: chart_type,
|
|
59
55
|
check_in_session_id: params[:check_in_session_id],
|
|
60
56
|
refreshed: params[:refreshed] || false
|
|
61
57
|
)
|
|
@@ -10,12 +10,12 @@ module Spree
|
|
|
10
10
|
def resource
|
|
11
11
|
taxon = Spree::Taxon.find_by(id: params[:taxon_id])
|
|
12
12
|
|
|
13
|
-
@resource = if
|
|
13
|
+
@resource = if chart_type_allowed?(taxon, chart_type)
|
|
14
14
|
pie_chart_event_aggregator_queries.call
|
|
15
15
|
else
|
|
16
16
|
SpreeCmCommissioner::EventChart.new(
|
|
17
|
-
id:
|
|
18
|
-
chart_type:
|
|
17
|
+
id: chart_type,
|
|
18
|
+
chart_type: chart_type,
|
|
19
19
|
product_charts: []
|
|
20
20
|
)
|
|
21
21
|
end
|
|
@@ -27,10 +27,14 @@ module Spree
|
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
+
def chart_type
|
|
31
|
+
params[:chart_type] || 'participation'
|
|
32
|
+
end
|
|
33
|
+
|
|
30
34
|
def pie_chart_event_aggregator_queries
|
|
31
35
|
SpreeCmCommissioner::PieChartEventAggregatorQueries.new(
|
|
32
36
|
taxon_id: params[:taxon_id],
|
|
33
|
-
chart_type:
|
|
37
|
+
chart_type: chart_type,
|
|
34
38
|
refreshed: params[:refreshed] || false
|
|
35
39
|
)
|
|
36
40
|
end
|
|
@@ -30,7 +30,9 @@ module Spree
|
|
|
30
30
|
options: {
|
|
31
31
|
device_id: device_id,
|
|
32
32
|
last_ip_address: request.remote_ip,
|
|
33
|
-
private_sale_token: params[:st]
|
|
33
|
+
private_sale_token: params[:st],
|
|
34
|
+
waiting_room_session_id: current_waiting_room_session&.id,
|
|
35
|
+
oauth_application_id: doorkeeper_token&.application_id
|
|
34
36
|
}
|
|
35
37
|
)
|
|
36
38
|
|
|
@@ -32,6 +32,11 @@ module Spree
|
|
|
32
32
|
device_id: device_id,
|
|
33
33
|
last_ip_address: request.remote_ip,
|
|
34
34
|
private_sale_token: params[:st],
|
|
35
|
+
|
|
36
|
+
# Resolved now, not in ProcessJob: the session may have expired by the time the job runs.
|
|
37
|
+
waiting_room_session_id: current_waiting_room_session&.id,
|
|
38
|
+
oauth_application_id: doorkeeper_token&.application_id,
|
|
39
|
+
|
|
35
40
|
# Carried into the job so it can bypass hold limits for a load test (see ProcessJob).
|
|
36
41
|
load_test_bypass: SpreeCmCommissioner::Current.load_test_bypass
|
|
37
42
|
}
|
|
@@ -25,7 +25,8 @@ module Spree
|
|
|
25
25
|
outbound_legs: @outbound_legs,
|
|
26
26
|
inbound_legs: @inbound_legs,
|
|
27
27
|
user: spree_current_user,
|
|
28
|
-
include_return: ActiveModel::Type::Boolean.new.cast(params[:include_return])
|
|
28
|
+
include_return: ActiveModel::Type::Boolean.new.cast(params[:include_return]),
|
|
29
|
+
oauth_application_id: doorkeeper_token&.application_id
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
if result.success?
|
|
@@ -27,7 +27,9 @@ module Spree
|
|
|
27
27
|
options: {
|
|
28
28
|
device_id: params[:device_id],
|
|
29
29
|
last_ip_address: request.remote_ip,
|
|
30
|
-
private_sale_token: params[:st]
|
|
30
|
+
private_sale_token: params[:st],
|
|
31
|
+
waiting_room_session_id: current_waiting_room_session&.id,
|
|
32
|
+
oauth_application_id: doorkeeper_token&.application_id
|
|
31
33
|
}
|
|
32
34
|
)
|
|
33
35
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
# GET /api/v2/tenant/private_sale_products?st=<private sale token>
|
|
6
|
+
class PrivateSaleProductsController < ::Spree::Api::V2::Tenant::ProductsController
|
|
7
|
+
before_action :require_spree_current_user
|
|
8
|
+
before_action :authorize_private_sale_token!
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
attr_reader :private_sale
|
|
13
|
+
|
|
14
|
+
def authorize_private_sale_token!
|
|
15
|
+
result = SpreeCmCommissioner::PrivateSales::Authorize.call(
|
|
16
|
+
token: params[:st],
|
|
17
|
+
user: spree_current_user
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
return @private_sale = result.value if result.success?
|
|
21
|
+
|
|
22
|
+
render_error_payload(SpreeCmCommissioner::PrivateSales::Authorize.error_message(result.error.value), 403)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# override — super keeps it to this tenant's products, so a token for another tenant's sale returns nothing.
|
|
26
|
+
def scope
|
|
27
|
+
super.where(id: private_sale.product_ids)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
class QueueDraftOrdersController < BaseController
|
|
6
|
+
include SpreeCmCommissioner::LoadTestBypassable
|
|
7
|
+
include SpreeCmCommissioner::AnonymousBookingGuard
|
|
8
|
+
|
|
9
|
+
before_action -> { ensure_anonymous_booking_allowed!(draft_order_products) }, only: :create
|
|
10
|
+
before_action -> { ensure_waiting_room_session!(draft_order_products) }, only: :create
|
|
11
|
+
|
|
12
|
+
# Tenant counterpart of Storefront::QueueDraftOrdersController: the order is built later by
|
|
13
|
+
# QueueDraftOrders::ProcessJob, under this tenant.
|
|
14
|
+
#
|
|
15
|
+
# Endpoint: POST /api/v2/tenant/queue_draft_orders
|
|
16
|
+
# Params: same as POST /api/v2/tenant/draft_orders
|
|
17
|
+
def create
|
|
18
|
+
result = SpreeCmCommissioner::QueueDraftOrders::Enqueue.call(
|
|
19
|
+
create_type: 'generic',
|
|
20
|
+
creation_params: {
|
|
21
|
+
line_items: draft_order_line_items,
|
|
22
|
+
user: spree_current_user,
|
|
23
|
+
store: current_store,
|
|
24
|
+
currency: current_currency,
|
|
25
|
+
tenant: @tenant,
|
|
26
|
+
options: {
|
|
27
|
+
device_id: params[:device_id],
|
|
28
|
+
last_ip_address: request.remote_ip,
|
|
29
|
+
private_sale_token: params[:st],
|
|
30
|
+
load_test_bypass: SpreeCmCommissioner::Current.load_test_bypass
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
locale: I18n.locale.to_s
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if result.success?
|
|
37
|
+
render_serialized_payload { result.value }
|
|
38
|
+
else
|
|
39
|
+
render_error_payload(result.error)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Plain symbol-keyed Hashes: this crosses the ActiveJob serialization boundary.
|
|
46
|
+
def draft_order_line_items
|
|
47
|
+
params.permit(line_items: [:variant_id, :quantity, { options: {}, public_metadata: {}, private_metadata: {} }])
|
|
48
|
+
.to_h
|
|
49
|
+
.deep_symbolize_keys
|
|
50
|
+
.fetch(:line_items, [])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Memoized: both the anonymous-booking and waiting-room guards read it on the same request.
|
|
54
|
+
def draft_order_products
|
|
55
|
+
@draft_order_products ||= begin
|
|
56
|
+
variant_ids = draft_order_line_items.pluck(:variant_id).compact
|
|
57
|
+
Spree::Variant.where(id: variant_ids).includes(product: :event).map(&:product)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
module Tenant
|
|
5
|
+
class SeatLayoutsController < BaseController
|
|
6
|
+
# GET /api/v2/tenant/seat_layouts/:id
|
|
7
|
+
# - include_details=true|false (optional, default: true)
|
|
8
|
+
#
|
|
9
|
+
# Always returns `product_ids` (see SeatLayout#product_ids).
|
|
10
|
+
def show
|
|
11
|
+
render_serialized_payload { serialize_resource(resource) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# GET /api/v2/tenant/seat_layouts?
|
|
17
|
+
# - variant_ids[]=[1,2]
|
|
18
|
+
# - include_details=true|false (optional, default: true)
|
|
19
|
+
#
|
|
20
|
+
# override
|
|
21
|
+
def collection
|
|
22
|
+
return @collection if defined?(@collection)
|
|
23
|
+
|
|
24
|
+
collection = scope.active.joins(:blocks).where(blocks: { variant_id: params[:variant_ids] }).distinct
|
|
25
|
+
collection = collection.includes(:top_level_blocks, seat_sections: :blocks) if include_details?
|
|
26
|
+
|
|
27
|
+
@collection = collection
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# override
|
|
31
|
+
def resource
|
|
32
|
+
SpreeCmCommissioner::SeatLayout.includes(
|
|
33
|
+
:top_level_blocks,
|
|
34
|
+
{ seat_sections: :blocks }
|
|
35
|
+
).where(id: scope.select(:id)).find(params[:id])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Seat layouts have no tenant_id. Event layouts reach the tenant through their blocks' variants;
|
|
39
|
+
# trip layouts are copied without variant_id on their blocks, so they go through the trip's vendor.
|
|
40
|
+
# override
|
|
41
|
+
def scope
|
|
42
|
+
event_layouts = SpreeCmCommissioner::SeatLayout.joins(blocks: { variant: :vendor })
|
|
43
|
+
.where(spree_vendors: { tenant_id: @tenant.id })
|
|
44
|
+
tenant_trips = SpreeCmCommissioner::Trip.joins(:vendor).where(spree_vendors: { tenant_id: @tenant.id })
|
|
45
|
+
trip_layouts = SpreeCmCommissioner::SeatLayout.where(
|
|
46
|
+
layoutable_type: 'SpreeCmCommissioner::Trip',
|
|
47
|
+
layoutable_id: tenant_trips.select(:id)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
SpreeCmCommissioner::SeatLayout.where(id: event_layouts.select(:id)).or(
|
|
51
|
+
SpreeCmCommissioner::SeatLayout.where(id: trip_layouts.select(:id))
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# override
|
|
56
|
+
def default_resource_includes
|
|
57
|
+
[
|
|
58
|
+
'top_level_blocks',
|
|
59
|
+
'seat_sections',
|
|
60
|
+
'seat_sections.blocks'
|
|
61
|
+
]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# override
|
|
65
|
+
def required_schema
|
|
66
|
+
return nil unless action_name == 'index'
|
|
67
|
+
|
|
68
|
+
SpreeCmCommissioner::SeatLayoutSchema
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# override
|
|
72
|
+
def resource_serializer
|
|
73
|
+
SpreeCmCommissioner::V2::Storefront::SeatLayoutSerializer
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# override
|
|
77
|
+
def collection_serializer
|
|
78
|
+
SpreeCmCommissioner::V2::Storefront::SeatLayoutSerializer
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# override
|
|
82
|
+
def serializer_params
|
|
83
|
+
super.merge(include_details: include_details?, include_product_ids: action_name == 'show')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def include_details?
|
|
87
|
+
params.fetch(:include_details, 'true') == 'true'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -25,7 +25,8 @@ module Spree
|
|
|
25
25
|
outbound_legs: @outbound_legs,
|
|
26
26
|
inbound_legs: @inbound_legs,
|
|
27
27
|
user: spree_current_user,
|
|
28
|
-
include_return: ActiveModel::Type::Boolean.new.cast(params[:include_return])
|
|
28
|
+
include_return: ActiveModel::Type::Boolean.new.cast(params[:include_return]),
|
|
29
|
+
oauth_application_id: doorkeeper_token&.application_id
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
if result.success?
|
|
@@ -16,9 +16,12 @@ module SpreeCmCommissioner
|
|
|
16
16
|
# Current doesn't cross into the job, so restore the load-test-bypass flag here — otherwise
|
|
17
17
|
# ValidateLimits would reject a trusted load test.
|
|
18
18
|
SpreeCmCommissioner::Current.set(load_test_bypass: creation_params.dig(:options, :load_test_bypass) || false) do
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
# Order#set_tenant_id reads MultiTenant.current_tenant_id, which the request set but the job doesn't.
|
|
20
|
+
MultiTenant.with(creation_params[:tenant]) do
|
|
21
|
+
SpreeCmCommissioner::QueueDraftOrders::Process.call(
|
|
22
|
+
document_path: document_path, create_type: create_type, creation_params: creation_params
|
|
23
|
+
)
|
|
24
|
+
end
|
|
22
25
|
end
|
|
23
26
|
end
|
|
24
27
|
end
|
|
@@ -18,6 +18,16 @@ module SpreeCmCommissioner
|
|
|
18
18
|
# For certain large events, admin can disable this manually.
|
|
19
19
|
store_public_metadata :allow_manual_search_for_operator, :boolean, default: true
|
|
20
20
|
|
|
21
|
+
# Whether operators may see this event's total ticket number (and the entry type chart) in the
|
|
22
|
+
# operator app. Organizers of the event's vendor always see it. Admin can disable per event.
|
|
23
|
+
store_public_metadata :allow_total_ticket_order_for_operator, :boolean, default: true
|
|
24
|
+
|
|
25
|
+
# Operator app switches for this event. Only users holding the operator role are restricted
|
|
26
|
+
# (see DashboardCrewEventSerializer): check-in history screens, and the full Home screen with
|
|
27
|
+
# the dashboard charts ("event overview").
|
|
28
|
+
store_public_metadata :allow_check_in_history_for_operator, :boolean, default: true
|
|
29
|
+
store_public_metadata :allow_event_overview_for_operator, :boolean, default: true
|
|
30
|
+
|
|
21
31
|
# Requires an active waiting-room session to create a draft order for this event's products
|
|
22
32
|
# (Product#enforce_waiting_room_session?). Server-only switch, so private.
|
|
23
33
|
store_private_metadata :enforce_waiting_room_session, :boolean, default: false
|
|
@@ -12,14 +12,6 @@ module SpreeCmCommissioner
|
|
|
12
12
|
has_many :homepage_sections,
|
|
13
13
|
through: :homepage_section_relatables,
|
|
14
14
|
class_name: 'SpreeCmCommissioner::HomepageSection'
|
|
15
|
-
|
|
16
|
-
after_save :schedule_homepage_section_caches_invalidation
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def schedule_homepage_section_caches_invalidation
|
|
20
|
-
homepage_sections.each do |homepage_section|
|
|
21
|
-
SpreeCmCommissioner::MaintenanceTasks::CacheInvalidation.pending.create_or_find_by(maintainable: homepage_section)
|
|
22
|
-
end
|
|
23
15
|
end
|
|
24
16
|
end
|
|
25
17
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
# Where an order came from, recorded once by DraftOrders::Create for analytics.
|
|
3
|
+
module OrderAttribution
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
# The active session the order was created through. Continuing checkout on another device or
|
|
8
|
+
# from booking history never updates it, so a later admission by the same buyer shows up as a
|
|
9
|
+
# session with no order of its own.
|
|
10
|
+
belongs_to :waiting_room_session, class_name: 'SpreeCmCommissioner::WaitingRoomSession', optional: true
|
|
11
|
+
|
|
12
|
+
# The OAuth client app whose access token created the order. nil for a request without a token.
|
|
13
|
+
belongs_to :oauth_application, class_name: 'Spree::OauthApplication', optional: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Promotion
|
|
3
|
+
module Rules
|
|
4
|
+
# The OAuth applications a promotion rule is scoped to, shared by every rule that narrows by
|
|
5
|
+
# app. Kept in `cm_oauth_application_promotion_rules` rather than a preference so "which
|
|
6
|
+
# promotions use this app" is a join, and deleting an app cannot leave a dead id in a rule.
|
|
7
|
+
# How an empty list behaves is up to the rule.
|
|
8
|
+
module OauthApplicationScoped
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
has_many :oauth_application_promotion_rules,
|
|
13
|
+
class_name: 'SpreeCmCommissioner::OauthApplicationPromotionRule',
|
|
14
|
+
foreign_key: :promotion_rule_id,
|
|
15
|
+
inverse_of: :promotion_rule,
|
|
16
|
+
autosave: true,
|
|
17
|
+
dependent: :destroy
|
|
18
|
+
|
|
19
|
+
# For reading and reporting. Not written through: that would load and validate every
|
|
20
|
+
# Spree::OauthApplication (whose `api_key` is encrypted) just to link it, and the admin form
|
|
21
|
+
# posts ids anyway.
|
|
22
|
+
has_many :oauth_applications, through: :oauth_application_promotion_rules, class_name: 'Spree::OauthApplication'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Reads the join rows, loaded once per rule instance: eligibility is re-checked several times in
|
|
26
|
+
# one cart update, and a plain `pluck` would query every time. Join rows rather than
|
|
27
|
+
# `oauth_applications`, which would load every app just to get its id.
|
|
28
|
+
def oauth_application_ids
|
|
29
|
+
oauth_application_promotion_rules.load.reject(&:marked_for_destruction?).map(&:oauth_application_id)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# What the admin form posts. Only the join rows change, saved with the rule. The blank value a
|
|
33
|
+
# multi-select always submits is dropped.
|
|
34
|
+
def oauth_application_ids=(ids)
|
|
35
|
+
wanted = Array(ids).compact_blank.map(&:to_i).uniq
|
|
36
|
+
existing = oauth_application_promotion_rules.load.to_a
|
|
37
|
+
|
|
38
|
+
existing.each { |row| row.mark_for_destruction unless wanted.include?(row.oauth_application_id) }
|
|
39
|
+
(wanted - existing.map(&:oauth_application_id)).each { |id| oauth_application_promotion_rules.build(oauth_application_id: id) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|