spree_cm_commissioner 2.14.2 → 2.14.4
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 +4 -6
- data/Gemfile.lock +1 -1
- data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +76 -70
- data/app/controllers/spree/admin/api_caches_controller.rb +26 -0
- data/app/controllers/spree/admin/base_controller_decorator.rb +0 -14
- data/app/controllers/spree/admin/guests_controller.rb +43 -4
- data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +26 -5
- data/app/jobs/spree_cm_commissioner/audit_events/cleanup_expired_job.rb +18 -0
- data/app/models/concerns/spree_cm_commissioner/vendor_preference.rb +1 -1
- data/app/models/spree_cm_commissioner/audit_event.rb +5 -0
- data/app/overrides/spree/admin/menus/index/clear_cache_button.html.erb.deface +4 -0
- data/app/overrides/spree/admin/payment_methods/index/clear_cache_button.html.erb.deface +6 -0
- data/app/overrides/spree/admin/products/edit/clear_cache_button.html.erb.deface +1 -1
- data/app/overrides/spree/admin/taxons/edit/clear_cache_button.html.erb.deface +1 -4
- data/app/overrides/spree/admin/vendors/edit/clear_cache_button.html.erb.deface +5 -0
- data/app/serializers/spree/api/v2/base_serializer_decorator.rb +24 -0
- data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +39 -87
- data/app/services/spree_cm_commissioner/api_caches/invalidate_all.rb +61 -0
- data/app/services/spree_cm_commissioner/audit_events/cleanup_expired.rb +58 -0
- data/app/services/spree_cm_commissioner/audit_logger.rb +1 -1
- data/app/services/spree_cm_commissioner/guests/build_dynamic_field_attributes.rb +34 -0
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/polling/sync_seat_layout.rb +45 -128
- data/app/services/spree_cm_commissioner/integrations/book_me_bus_v1/resources/seat_layout.rb +36 -148
- data/app/services/spree_cm_commissioner/inventory_items/reset.rb +103 -35
- data/app/services/spree_cm_commissioner/order/guests/update.rb +2 -24
- data/app/services/spree_cm_commissioner/rates_availabilities/bulk_update.rb +14 -8
- data/app/services/spree_cm_commissioner/seat_layouts/grid.rb +56 -0
- data/app/views/spree/admin/guests/_dynamic_fields.html.erb +67 -0
- data/app/views/spree/admin/guests/_form.html.erb +2 -0
- data/app/views/spree/admin/homepage_background/index.html.erb +1 -1
- data/app/views/spree/admin/homepage_section/edit.html.erb +0 -1
- data/app/views/spree/admin/homepage_section/index.html.erb +1 -1
- data/app/views/spree/admin/inventory_items/_details.html.erb +16 -0
- data/app/views/spree/admin/rates_availabilities/_script.html.erb +49 -7
- data/app/views/spree/admin/rates_availabilities/index.html.erb +4 -0
- data/app/views/spree/admin/shared/_api_cache_button.html.erb +5 -0
- data/config/locales/en.yml +25 -1
- data/config/locales/km.yml +25 -1
- data/config/routes.rb +5 -1
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +13 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5317bd75c6057165a07eae82d0e30f986371767925346783242d8d12a0a8d24
|
|
4
|
+
data.tar.gz: b015f2bebfc1105dee65f7b4b22f63ee909b113541741f9dbc5ed48482ab4b04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c59a388a335fcffdbb53ef8ebd470bf9b310a169ac8057ba6b464c6cef8e4ee6425cccfa5bfda366ea9c7fa89aaca854465c914521df040d460ba39994bd0b3
|
|
7
|
+
data.tar.gz: 0d8572c942886d56c1689ceb532e2d643f208d538dfb8fa40e21209198562dbe52fb7bb8a28f7573d0900407b8651421210eb4b99c6ea80aefa14b73677880c4
|
data/.env.example
CHANGED
|
@@ -47,14 +47,12 @@ ORGANIZER_URL=http://127.0.0.1:4000/organizer
|
|
|
47
47
|
# spree_cm_commissioner/distance.rb
|
|
48
48
|
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
52
|
CACHE_AUTO_INVALIDATION_DISABLED="" # Set to yes to disable CDN cache invalidation entirely (kill switch)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
CACHE_REALTIME_MAX_AGE=300 # High freshness (trips, trip search) - 5 minutes
|
|
57
|
-
CACHE_DEFAULT_MAX_AGE=300 # Default for unlisted controllers - 5 minutes
|
|
53
|
+
CACHE_SERIALIZER_DISABLED="" # Set to yes to turn off Spree's serializer record cache everywhere
|
|
54
|
+
CACHE_STATIC_MAX_AGE=86400 # Static content (CMS pages) - 1 day
|
|
55
|
+
CACHE_REALTIME_MAX_AGE=300 # Everything else listed (homepage, menus, products, events, vendors, trips, seat layouts, countries, provinces, guest card classes) - 5 minutes
|
|
58
56
|
|
|
59
57
|
# Batch size for processing maintenance tasks in app/jobs/spree_cm_commissioner/maintenance_tasks/orchestrate_job.rb
|
|
60
58
|
MAINTENANCE_TASKS_BATCH_SIZE=100
|
data/Gemfile.lock
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Check following file for spec:
|
|
2
|
-
# spec/controllers/
|
|
2
|
+
# spec/controllers/concerns/spree_cm_commissioner/content_cachable_spec.rb
|
|
3
3
|
module SpreeCmCommissioner
|
|
4
4
|
module ContentCachable
|
|
5
5
|
extend ActiveSupport::Concern
|
|
@@ -8,88 +8,94 @@ module SpreeCmCommissioner
|
|
|
8
8
|
after_action :set_cache_control_for_cdn
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
# Every CDN-cached API, by full class name, with its GET actions and TTL tier. Anything not
|
|
12
|
+
# listed keeps Rails' default `max-age=0, private, must-revalidate` and never reaches the CDN.
|
|
13
|
+
#
|
|
14
|
+
# :static (CACHE_STATIC_MAX_AGE, 1 day) is for rarely changed content. :fresh
|
|
15
|
+
# (CACHE_REALTIME_MAX_AGE, 5 minutes) is for anything editors change, short enough that an edit
|
|
16
|
+
# shows up without a CloudFront invalidation.
|
|
17
|
+
#
|
|
18
|
+
# Full names, not demodulized ones: this concern runs on every ActionController::API, and short
|
|
19
|
+
# names collide with admin-only APIs (Platform, Operator, V1, webhooks) that must never be marked
|
|
20
|
+
# public. Never list an action whose response depends on the signed-in user, e.g.
|
|
21
|
+
# Tenant::VotePackagesController#claim_status: the CDN would serve one user's answer to everyone
|
|
22
|
+
# sharing the cache key.
|
|
23
|
+
CDN_CACHEABLE_ACTIONS = {
|
|
24
|
+
# JSON-LD
|
|
25
|
+
'Spree::Api::JsonLd::VendorsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
# Storefront
|
|
28
|
+
'Spree::Api::V2::Storefront::AccommodationsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
29
|
+
'Spree::Api::V2::Storefront::CmsPagesController' => { actions: %w[index show].freeze, max_age: :static },
|
|
30
|
+
'Spree::Api::V2::Storefront::CountriesController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
31
|
+
'Spree::Api::V2::Storefront::DynamicFieldsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
32
|
+
'Spree::Api::V2::Storefront::EventMatchesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
33
|
+
'Spree::Api::V2::Storefront::EventsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
34
|
+
'Spree::Api::V2::Storefront::GuestCardClassesController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
35
|
+
'Spree::Api::V2::Storefront::HomepageBackgroundController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
36
|
+
'Spree::Api::V2::Storefront::HomepageDataController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
37
|
+
'Spree::Api::V2::Storefront::HomepageSectionsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
38
|
+
'Spree::Api::V2::Storefront::MenusController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
39
|
+
'Spree::Api::V2::Storefront::PaymentMethodsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
40
|
+
'Spree::Api::V2::Storefront::PopularRoutePlacesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
41
|
+
'Spree::Api::V2::Storefront::ProductsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
42
|
+
'Spree::Api::V2::Storefront::ProvincesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
43
|
+
'Spree::Api::V2::Storefront::RoutePlacesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
44
|
+
'Spree::Api::V2::Storefront::SeatLayoutsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
45
|
+
'Spree::Api::V2::Storefront::TaxonsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
46
|
+
'Spree::Api::V2::Storefront::TripSearchController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
47
|
+
'Spree::Api::V2::Storefront::TripsController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
48
|
+
'Spree::Api::V2::Storefront::VendorPhotosController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
49
|
+
'Spree::Api::V2::Storefront::VendorsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
# Tenant
|
|
52
|
+
'Spree::Api::V2::Tenant::CmsPagesController' => { actions: %w[index show].freeze, max_age: :static },
|
|
53
|
+
'Spree::Api::V2::Tenant::EpisodesController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
54
|
+
'Spree::Api::V2::Tenant::HomepageSectionsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
55
|
+
'Spree::Api::V2::Tenant::PaymentMethodsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
56
|
+
'Spree::Api::V2::Tenant::PopularRoutePlacesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
57
|
+
'Spree::Api::V2::Tenant::ProductsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
58
|
+
'Spree::Api::V2::Tenant::ShowContestantsController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
59
|
+
'Spree::Api::V2::Tenant::ShowEliminationSessionsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
60
|
+
'Spree::Api::V2::Tenant::ShowPeopleController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
61
|
+
'Spree::Api::V2::Tenant::ShowPersonAssignmentsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
62
|
+
'Spree::Api::V2::Tenant::ShowsController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
63
|
+
'Spree::Api::V2::Tenant::TaxonsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
64
|
+
'Spree::Api::V2::Tenant::TripSearchController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
65
|
+
'Spree::Api::V2::Tenant::TripsController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
66
|
+
'Spree::Api::V2::Tenant::VendorPhotosController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
67
|
+
'Spree::Api::V2::Tenant::VendorsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
68
|
+
'Spree::Api::V2::Tenant::VotePackagesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
69
|
+
'Spree::Api::V2::Tenant::VotingContestantsController' => { actions: %w[index].freeze, max_age: :fresh }
|
|
70
|
+
}.freeze
|
|
50
71
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
DynamicFieldsController
|
|
57
|
-
].freeze
|
|
58
|
-
|
|
59
|
-
# Override This Method based on your UseCase
|
|
60
|
-
# Or let it auto-detect based on controller name
|
|
61
|
-
def client_max_age
|
|
62
|
-
controller_name = self.class.name.demodulize
|
|
63
|
-
|
|
64
|
-
if STATIC_CONTENT_CONTROLLERS.include?(controller_name)
|
|
65
|
-
ENV.fetch('CACHE_STATIC_MAX_AGE', 1.day.to_i).to_i
|
|
66
|
-
elsif SEMI_STATIC_CONTROLLERS.include?(controller_name)
|
|
67
|
-
ENV.fetch('CACHE_SEMI_STATIC_MAX_AGE', 1.hour.to_i).to_i
|
|
68
|
-
elsif MODERATE_FRESHNESS_CONTROLLERS.include?(controller_name)
|
|
69
|
-
ENV.fetch('CACHE_MODERATE_MAX_AGE', 30.minutes.to_i).to_i
|
|
70
|
-
elsif HIGH_FRESHNESS_CONTROLLERS.include?(controller_name)
|
|
71
|
-
ENV.fetch('CACHE_REALTIME_MAX_AGE', 5.minutes.to_i).to_i
|
|
72
|
-
else
|
|
73
|
-
ENV.fetch('CACHE_DEFAULT_MAX_AGE', 0.minutes.to_i).to_i
|
|
72
|
+
# CDN (s-maxage) TTL, or nil for an unlisted controller. Override in a controller for a different one.
|
|
73
|
+
def shared_max_age
|
|
74
|
+
case cdn_cache_config&.dig(:max_age)
|
|
75
|
+
when :static then ENV.fetch('CACHE_STATIC_MAX_AGE', 1.day.to_i).to_i
|
|
76
|
+
when :fresh then ENV.fetch('CACHE_REALTIME_MAX_AGE', 5.minutes.to_i).to_i
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# (max-age 0) must not be CDN-cached at all.
|
|
80
|
-
def shared_max_age
|
|
81
|
-
client_max_age
|
|
80
|
+
def cdn_cacheable?
|
|
81
|
+
request.get? && cdn_cache_config.present? && cdn_cache_config[:actions].include?(action_name)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
def set_cache_control_for_cdn
|
|
85
85
|
return if response.committed?
|
|
86
86
|
return if response.status != 200
|
|
87
|
+
return unless cdn_cacheable?
|
|
88
|
+
|
|
89
|
+
# max-age=0 makes the app revalidate with CloudFront on every request, since a CloudFront
|
|
90
|
+
# invalidation can never reach the copy already on a device. Rack::ETag tags each response,
|
|
91
|
+
# so a revalidation is a small 304. s-maxage alone decides how stale a response can get.
|
|
92
|
+
response.headers['Cache-Control'] = "public, max-age=0, s-maxage=#{shared_max_age}"
|
|
93
|
+
end
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
response.headers['Cache-Control'] = "public, max-age=#{client_max_age}, s-maxage=#{shared_max_age}"
|
|
95
|
+
private
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
def cdn_cache_config
|
|
98
|
+
CDN_CACHEABLE_ACTIONS[self.class.name.to_s]
|
|
93
99
|
end
|
|
94
100
|
end
|
|
95
101
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
# Behind every admin clear-cache button (spree/admin/shared/_api_cache_button). Admin-only through
|
|
4
|
+
# BaseController#authorize_admin, which checks :api_caches since there is no model class.
|
|
5
|
+
class ApiCachesController < Spree::Admin::BaseController
|
|
6
|
+
# POST /admin/api_caches/clear?model=Spree::Product&id=1, or without id to clear every record of
|
|
7
|
+
# model on one storefront: tenant_id for that tenant's API, none for the BookMe+ storefront.
|
|
8
|
+
def clear
|
|
9
|
+
result = if params[:id].present?
|
|
10
|
+
SpreeCmCommissioner::ApiCaches::Invalidate.call(model: params[:model], id: params[:id])
|
|
11
|
+
else
|
|
12
|
+
tenant = SpreeCmCommissioner::Tenant.find(params[:tenant_id]) if params[:tenant_id].present?
|
|
13
|
+
SpreeCmCommissioner::ApiCaches::InvalidateAll.call(model: params[:model], tenant: tenant)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if result.success?
|
|
17
|
+
flash[:success] = Spree.t(:clear_cache_requested)
|
|
18
|
+
else
|
|
19
|
+
flash[:error] = result.error.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
redirect_back fallback_location: admin_root_path
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -31,20 +31,6 @@ module Spree
|
|
|
31
31
|
nil
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
# POST
|
|
35
|
-
def invalidate_api_caches
|
|
36
|
-
if params[:model].present?
|
|
37
|
-
result = SpreeCmCommissioner::ApiCaches::Invalidate.call(
|
|
38
|
-
model: params[:model],
|
|
39
|
-
id: params[:id]
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
flash[:error] = result.error if result.failure?
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
redirect_back fallback_location: admin_root_path
|
|
46
|
-
end
|
|
47
|
-
|
|
48
34
|
private
|
|
49
35
|
|
|
50
36
|
# See SpreeCmCommissioner::Current#actor -- lets StoreCreditEventDecorator's after_create
|
|
@@ -6,6 +6,7 @@ module Spree
|
|
|
6
6
|
|
|
7
7
|
before_action :load_order
|
|
8
8
|
before_action :load_guest, only: %i[check_in uncheck_in]
|
|
9
|
+
before_action :load_edit_data, only: :edit
|
|
9
10
|
|
|
10
11
|
def model_class
|
|
11
12
|
SpreeCmCommissioner::Guest
|
|
@@ -41,10 +42,17 @@ module Spree
|
|
|
41
42
|
@kyc_fields = @line_item.kyc_fields
|
|
42
43
|
end
|
|
43
44
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
def update
|
|
46
|
+
result = SpreeCmCommissioner::Guests::Update.call(guest: @object, guest_params: update_params)
|
|
47
|
+
|
|
48
|
+
if result.success?
|
|
49
|
+
flash[:success] = flash_message_for(@object, :successfully_updated)
|
|
50
|
+
redirect_to location_after_save
|
|
51
|
+
else
|
|
52
|
+
flash.now[:error] = result.error.to_s
|
|
53
|
+
load_edit_data
|
|
54
|
+
render :edit, status: :unprocessable_entity
|
|
55
|
+
end
|
|
48
56
|
end
|
|
49
57
|
|
|
50
58
|
def check_in
|
|
@@ -88,6 +96,37 @@ module Spree
|
|
|
88
96
|
)
|
|
89
97
|
end
|
|
90
98
|
|
|
99
|
+
# Saved through Guests::Update's single transaction so a failing custom field does not leave
|
|
100
|
+
# KYC or seat_number half-saved.
|
|
101
|
+
def update_params
|
|
102
|
+
attrs = permitted_resource_params.to_h.symbolize_keys
|
|
103
|
+
dynamic_fields = submitted_dynamic_fields
|
|
104
|
+
return attrs if dynamic_fields.blank?
|
|
105
|
+
|
|
106
|
+
attrs.merge(
|
|
107
|
+
guest_dynamic_fields_attributes: SpreeCmCommissioner::Guests::BuildDynamicFieldAttributes.call(
|
|
108
|
+
guest: @object, dynamic_fields: dynamic_fields
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The form lists every field of every stage, so blank scalar answers mean "not filled in" and
|
|
114
|
+
# are skipped rather than failing the presence validation (clearing an answer is not supported).
|
|
115
|
+
def submitted_dynamic_fields
|
|
116
|
+
raw = params.require(object_name).permit(dynamic_fields: {})[:dynamic_fields]
|
|
117
|
+
|
|
118
|
+
raw.to_h.reject { |_id, value| value.is_a?(String) && value.strip.empty? }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def load_edit_data
|
|
122
|
+
@kyc_fields = @object.line_item.kyc_fields
|
|
123
|
+
@check_ins = @object.check_ins.includes(:check_in_session, :check_in_by)
|
|
124
|
+
@eligible_check_in_sessions = @object.eligible_check_in_sessions.order(:position)
|
|
125
|
+
@dynamic_fields = @object.line_item.product.dynamic_fields.includes(:dynamic_field_options)
|
|
126
|
+
.reorder(:data_fill_stage, :position)
|
|
127
|
+
@guest_dynamic_fields = @object.guest_dynamic_fields.to_a
|
|
128
|
+
end
|
|
129
|
+
|
|
91
130
|
def load_guest
|
|
92
131
|
@guest = @order.guests.find(params[:id])
|
|
93
132
|
end
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
# Order Number:
|
|
4
4
|
# <code>R621016753</code>
|
|
5
5
|
#
|
|
6
|
-
#
|
|
6
|
+
# 🎪 Event: Temple Run 2023
|
|
7
|
+
# 🏪 Vendor: Temple Run with Sai
|
|
8
|
+
#
|
|
7
9
|
# <b>5km Running Ticket</b>
|
|
10
|
+
# Quantity: 1
|
|
8
11
|
# <i>👉 Distance: 5km, T-shirt: M</i>
|
|
9
12
|
# <i>🗓️ Nov 15, 2023</i>
|
|
10
|
-
# <i>🏪 Temple Run with Sai</i>
|
|
11
13
|
#
|
|
12
|
-
# [ x2 ]
|
|
13
14
|
# <b>5km Running Ticket</b>
|
|
15
|
+
# Quantity: 2
|
|
14
16
|
# <i>👉 Distance: 5km, T-shirt: S</i>
|
|
15
17
|
# <i>🗓️ Nov 15, 2023 -> Nov 17, 2023</i>
|
|
16
|
-
# <i>🏪 Temple Run with Sai</i>
|
|
17
18
|
#
|
|
18
19
|
# <b>🙍 Customer Info</b>
|
|
19
20
|
# Name: Vaneath Awesome
|
|
@@ -43,10 +44,31 @@ module SpreeCmCommissioner
|
|
|
43
44
|
def body
|
|
44
45
|
text = []
|
|
45
46
|
text << "Order Number:\n#{inline_code(order.number)}\n"
|
|
47
|
+
summary = summary_content
|
|
48
|
+
text << "#{summary}\n" if summary.present?
|
|
46
49
|
text << selected_line_items.map { |item| line_item_content(item) }.compact.join("\n\n")
|
|
47
50
|
text.compact.join("\n")
|
|
48
51
|
end
|
|
49
52
|
|
|
53
|
+
# Stated once for the whole order rather than per ticket: an order is almost always one
|
|
54
|
+
# event and one vendor, so a per-ticket line would just repeat the same name.
|
|
55
|
+
def summary_content
|
|
56
|
+
line_items = selected_line_items.to_a
|
|
57
|
+
ActiveRecord::Associations::Preloader.new(records: line_items, associations: %i[event vendor]).call
|
|
58
|
+
|
|
59
|
+
[
|
|
60
|
+
names_line('🎪 Event', line_items.map(&:event)),
|
|
61
|
+
names_line('🏪 Vendor', line_items.map(&:vendor))
|
|
62
|
+
].compact.join("\n").presence
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def names_line(label, records)
|
|
66
|
+
names = records.compact.map(&:name).compact_blank.uniq
|
|
67
|
+
return nil if names.empty?
|
|
68
|
+
|
|
69
|
+
"#{label}: #{names.map { |name| ERB::Util.html_escape(name) }.join(', ')}"
|
|
70
|
+
end
|
|
71
|
+
|
|
50
72
|
def line_item_content(line_item)
|
|
51
73
|
return transit_line_item_content(line_item) if line_item.transit?
|
|
52
74
|
|
|
@@ -56,7 +78,6 @@ module SpreeCmCommissioner
|
|
|
56
78
|
text << "Quantity: #{line_item.quantity}"
|
|
57
79
|
text << italic("👉 #{line_item.options_text}") if line_item.options_text.present?
|
|
58
80
|
text << italic(pretty_date_for(line_item)) if pretty_date_for(line_item).present?
|
|
59
|
-
text << italic("🏪 #{line_item.vendor.name}") if line_item.vendor&.name.present? && vendor.blank?
|
|
60
81
|
text << seat_labels_text(line_item) if seat_labels_text(line_item).present?
|
|
61
82
|
|
|
62
83
|
text.compact.join("\n")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module AuditEvents
|
|
3
|
+
class CleanupExpiredJob < SpreeCmCommissioner::ApplicationJob
|
|
4
|
+
# Gap before continuing a backlog, so the Sidekiq thread is free for other jobs in between.
|
|
5
|
+
CONTINUE_DELAY = 10.seconds
|
|
6
|
+
|
|
7
|
+
queue_as :maintenance
|
|
8
|
+
|
|
9
|
+
# Not an ApplicationUniqueJob: `unique :until_executed` would drop the continuation below as a duplicate of the
|
|
10
|
+
# running job. Overlapping with the daily cron is harmless because deleting is idempotent.
|
|
11
|
+
def perform
|
|
12
|
+
result = SpreeCmCommissioner::AuditEvents::CleanupExpired.new.call
|
|
13
|
+
|
|
14
|
+
self.class.set(wait: CONTINUE_DELAY).perform_later if result.value[:more_remaining]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
class AuditEvent < Base
|
|
3
|
+
# How long AuditLogger keeps an event when the caller gives no retention_days.
|
|
4
|
+
DEFAULT_RETENTION_DAYS = 90
|
|
5
|
+
|
|
3
6
|
belongs_to :auditable, polymorphic: true, optional: true
|
|
4
7
|
belongs_to :user, class_name: 'Spree::User', optional: true
|
|
5
8
|
|
|
@@ -7,5 +10,7 @@ module SpreeCmCommissioner
|
|
|
7
10
|
|
|
8
11
|
scope :success, -> { where(success: true) }
|
|
9
12
|
scope :failed, -> { where(success: false) }
|
|
13
|
+
|
|
14
|
+
scope :expired, -> (now = Time.current) { where(retention_until: ...now.to_date) }
|
|
10
15
|
end
|
|
11
16
|
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!-- insert_after "erb[silent]:contains('content_for :page_actions')" -->
|
|
2
|
+
|
|
3
|
+
<%# A payment method's paths don't depend on the record (vendor_id is a query param), so one clear
|
|
4
|
+
on the list covers them all. This admin lists BookMe+'s storefront; a tenant clears its own
|
|
5
|
+
from the dashboard. %>
|
|
6
|
+
<%= render 'spree/admin/shared/api_cache_button', model: Spree::PaymentMethod.name %>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<!-- insert_after "erb[silent]:contains('content_for :page_actions')" -->
|
|
2
2
|
|
|
3
|
-
<%=
|
|
3
|
+
<%= render 'spree/admin/shared/api_cache_button', model: Spree::Product.name, id: @product.id %>
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<!-- insert_after "erb[silent]:contains('content_for :page_actions')" -->
|
|
2
2
|
|
|
3
3
|
<% if @taxon.depth == 1 %>
|
|
4
|
-
|
|
5
|
-
admin_invalidate_api_caches_path(model: Spree::Taxon.name, id: @taxon.id),
|
|
6
|
-
method: :post,
|
|
7
|
-
class: "btn btn-outline-primary" %>
|
|
4
|
+
<%= render 'spree/admin/shared/api_cache_button', model: Spree::Taxon.name, id: @taxon.id %>
|
|
8
5
|
<% end %>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
# Spree caches every serializer's records keyed by each record's updated_at, so anything
|
|
5
|
+
# computed from other tables stays stale, and behind CloudFront an invalidation can't purge
|
|
6
|
+
# it. CACHE_SERIALIZER_DISABLED=yes turns it off everywhere.
|
|
7
|
+
module BaseSerializerDecorator
|
|
8
|
+
def self.prepended(base)
|
|
9
|
+
base.singleton_class.prepend(ClassMethods)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
# jsonapi-serializer copies Spree's store into each subclass as it is defined, so this
|
|
14
|
+
# reader, which every subclass resolves through, is the one place to turn it off.
|
|
15
|
+
def cache_store_instance
|
|
16
|
+
ENV['CACHE_SERIALIZER_DISABLED'] == 'yes' ? nil : super
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Spree::Api::V2::BaseSerializer.prepend(Spree::Api::V2::BaseSerializerDecorator)
|