spree_cm_commissioner 2.8.7 → 2.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/admin/inventory_holds_controller.rb +20 -1
- data/app/controllers/spree/api/v2/storefront/order_histories_controller.rb +7 -2
- data/app/controllers/spree/api/v2/tenant/order_histories_controller.rb +7 -2
- data/app/interactors/spree_cm_commissioner/waiting_guests_caller.rb +39 -10
- data/app/mailers/spree/order_mailer_decorator.rb +2 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +22 -0
- data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +2 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +1 -0
- data/app/overrides/spree/admin/orders/_search/payment_number_search_field.html.erb.deface +8 -0
- data/app/overrides/spree/admin/products/_form/enable_telegram_alert.html.erb.deface +14 -0
- data/app/services/spree_cm_commissioner/cart/add_guest.rb +16 -2
- data/app/services/spree_cm_commissioner/waiting_room/publish_lobby_path.rb +11 -2
- data/app/services/spree_cm_commissioner/waiting_room/stamp_queue_positions.rb +37 -8
- data/app/views/spree_cm_commissioner/order_mailer/purchased_items/_items.html.erb +12 -9
- data/config/initializers/spree_permitted_attributes.rb +3 -0
- data/config/locales/en.yml +5 -1
- data/config/locales/km.yml +2 -1
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca304c9bd308649d477548d5a5930737a651dc8fa9f1dc55eaff6d60c5b91ba0
|
|
4
|
+
data.tar.gz: e3fc4442166c3a178f759ec32ddc1feb39d7240505623be3d8437a55bffc963d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f469ec325a78e226309906f0ccf6a1b9d66d3354505919cca451c16dd70c8dec2229c9c44fbe6cf17a8d0109d1a4805c1cd38fb1787ccf0f8d3d0ed174164f64
|
|
7
|
+
data.tar.gz: 78d7c13c8c6107ff36203f79e290f44b3543a2db9fa994772ed08a049da97dac9368102855b0726e1b3417f17a22ec7feb4be0778627dd7dd7057d81f2f54d98
|
data/Gemfile.lock
CHANGED
|
@@ -9,6 +9,7 @@ module Spree
|
|
|
9
9
|
q['s'] ||= 'created_at desc'
|
|
10
10
|
q['created_at_gt'] = parse_time_or_nil(q['created_at_gt'], :beginning_of_day) if q['created_at_gt'].present?
|
|
11
11
|
q['created_at_lt'] = parse_time_or_nil(q['created_at_lt'], :end_of_day) if q['created_at_lt'].present?
|
|
12
|
+
normalize_enum_filters!(q)
|
|
12
13
|
|
|
13
14
|
@search = scope.ransack(q)
|
|
14
15
|
result_scope = @search.result(distinct: true)
|
|
@@ -18,7 +19,11 @@ module Spree
|
|
|
18
19
|
.page(params[:page])
|
|
19
20
|
.per(params[:per_page] || Spree::Backend::Config[:admin_orders_per_page])
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
# Status tab counts must reflect every other filter but NOT the status filter, so
|
|
23
|
+
# each tab shows how many holds it would return (otherwise selecting one status
|
|
24
|
+
# zeroes out all the other tabs).
|
|
25
|
+
@status_counts = scope.ransack(q.except('status_eq')).result(distinct: true)
|
|
26
|
+
.reorder(nil).group(:status).count
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
def release
|
|
@@ -44,6 +49,20 @@ module Spree
|
|
|
44
49
|
|
|
45
50
|
private
|
|
46
51
|
|
|
52
|
+
# `status` and `release_reason` are integer-backed enums. Ransack casts a string
|
|
53
|
+
# value for an integer column with String#to_i, so "payment_locked" becomes 0
|
|
54
|
+
# (== :pending) and every label silently filters to the wrong status. Translate
|
|
55
|
+
# enum labels to their stored integer before searching.
|
|
56
|
+
def normalize_enum_filters!(query)
|
|
57
|
+
{
|
|
58
|
+
'status_eq' => SpreeCmCommissioner::InventoryHold.statuses,
|
|
59
|
+
'release_reason_eq' => SpreeCmCommissioner::InventoryHold.release_reasons
|
|
60
|
+
}.each do |key, mapping|
|
|
61
|
+
value = query[key]
|
|
62
|
+
query[key] = mapping[value.to_s] if value.present? && mapping.key?(value.to_s)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
47
66
|
def back_params
|
|
48
67
|
raw_params = params[:back_params].presence || params
|
|
49
68
|
ActionController::Parameters.new(raw_params).permit(:page, :per_page, q: {})
|
|
@@ -30,13 +30,18 @@ module Spree
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
32
|
def collection
|
|
33
|
+
# WARNING: Do NOT remove .order(id: :desc).
|
|
34
|
+
# Mobile relies on this ordering for paginated order_histories.
|
|
35
|
+
# Removing it will cause mobile to paginate by oldest records first (last 10 oldest),
|
|
36
|
+
# breaking the "most recent orders" display on the order history screen.
|
|
37
|
+
# It related to user cancel or complete order flow, which relies on the most recent orders being returned first.
|
|
33
38
|
if spree_current_user.present?
|
|
34
|
-
spree_current_user.orders.not_archived
|
|
39
|
+
spree_current_user.orders.not_archived.order(id: :desc)
|
|
35
40
|
else
|
|
36
41
|
order_tokens = Array(params[:order_tokens])
|
|
37
42
|
return Spree::Order.none if order_tokens.empty?
|
|
38
43
|
|
|
39
|
-
Spree::Order.not_archived.without_user.where(token: order_tokens)
|
|
44
|
+
Spree::Order.not_archived.without_user.where(token: order_tokens).order(id: :desc)
|
|
40
45
|
end
|
|
41
46
|
end
|
|
42
47
|
|
|
@@ -37,13 +37,18 @@ module Spree
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def collection
|
|
40
|
+
# WARNING: Do NOT remove .order(id: :desc).
|
|
41
|
+
# Mobile relies on this ordering for paginated order_histories.
|
|
42
|
+
# Removing it will cause mobile to paginate by oldest records first (last 10 oldest),
|
|
43
|
+
# breaking the "most recent orders" display on the order history screen.
|
|
44
|
+
# It related to user cancel or complete order flow, which relies on the most recent orders being returned first.
|
|
40
45
|
if spree_current_user.present?
|
|
41
|
-
spree_current_user.orders.not_archived
|
|
46
|
+
spree_current_user.orders.not_archived.order(id: :desc)
|
|
42
47
|
else
|
|
43
48
|
order_tokens = Array(params[:order_tokens])
|
|
44
49
|
return Spree::Order.none if order_tokens.empty?
|
|
45
50
|
|
|
46
|
-
Spree::Order.not_archived.without_user.where(token: order_tokens)
|
|
51
|
+
Spree::Order.not_archived.without_user.where(token: order_tokens).order(id: :desc)
|
|
47
52
|
end
|
|
48
53
|
end
|
|
49
54
|
|
|
@@ -13,25 +13,31 @@ module SpreeCmCommissioner
|
|
|
13
13
|
FIRESTORE_BATCH_SIZE = (ENV['WAITING_ROOM_FIRESTORE_BATCH_SIZE'] || 500).to_i
|
|
14
14
|
|
|
15
15
|
def call
|
|
16
|
+
started_at = Time.zone.now
|
|
16
17
|
available_slots = fetch_available_slots
|
|
17
|
-
return unless available_slots.positive?
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# Always run through to mark_as — even with no slots — so the lobby keeps a fresh heartbeat
|
|
20
|
+
# (logs.waiting_guests_caller_job) during peak/full periods instead of going stale
|
|
21
|
+
# and looking like a dead cron.
|
|
22
|
+
long_waiting_guests = available_slots.positive? ? fetch_long_waiting_guests(available_slots) : []
|
|
23
|
+
calling_all(long_waiting_guests) if long_waiting_guests.any?
|
|
21
24
|
|
|
22
25
|
mark_as(
|
|
23
26
|
full: long_waiting_guests.size >= available_slots,
|
|
24
27
|
available_slots: available_slots - long_waiting_guests.size,
|
|
25
28
|
avg_wait_to_enter_seconds: compute_avg_wait_to_enter_seconds(long_waiting_guests),
|
|
26
29
|
avg_queue_to_enter_seconds: compute_avg_queue_to_enter_seconds(long_waiting_guests),
|
|
27
|
-
|
|
30
|
+
recent_slots_per_call: available_slots.positive? ? available_slots : nil,
|
|
31
|
+
active_sessions: @active_sessions,
|
|
32
|
+
max_sessions: @max_sessions,
|
|
33
|
+
logs: call_logs(started_at: started_at, called_count: long_waiting_guests.size)
|
|
28
34
|
)
|
|
29
35
|
end
|
|
30
36
|
|
|
31
37
|
def fetch_available_slots
|
|
32
|
-
max_sessions = fetch_max_sessions
|
|
33
|
-
active_sessions = SpreeCmCommissioner::WaitingRoomSession.active.count
|
|
34
|
-
max_sessions - active_sessions
|
|
38
|
+
@max_sessions = fetch_max_sessions
|
|
39
|
+
@active_sessions = SpreeCmCommissioner::WaitingRoomSession.active.count
|
|
40
|
+
@max_sessions - @active_sessions
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
# This query requires an index; create it in Firebase beforehand.
|
|
@@ -100,21 +106,44 @@ module SpreeCmCommissioner
|
|
|
100
106
|
end
|
|
101
107
|
|
|
102
108
|
# merge: true so we preserve the published `waiting_guests_records_path` on the lobby doc.
|
|
103
|
-
|
|
109
|
+
# App-facing fields plus live capacity state (full / available_slots / active_sessions /
|
|
110
|
+
# max_sessions / ETAs) stay top-level; this run's observability snapshot is namespaced under
|
|
111
|
+
# `logs.waiting_guests_caller_job` (see #call_logs). merge: true deep-merges nested maps, so
|
|
112
|
+
# each job owns its own key under `logs` without clobbering the others.
|
|
113
|
+
def mark_as( # rubocop:disable Metrics/ParameterLists
|
|
104
114
|
full:,
|
|
105
115
|
available_slots:,
|
|
106
116
|
avg_wait_to_enter_seconds: nil,
|
|
107
117
|
avg_queue_to_enter_seconds: nil,
|
|
108
|
-
|
|
118
|
+
recent_slots_per_call: nil,
|
|
119
|
+
active_sessions: nil,
|
|
120
|
+
max_sessions: nil,
|
|
121
|
+
logs: nil
|
|
109
122
|
)
|
|
110
123
|
data = { full: full, available_slots: available_slots }
|
|
111
124
|
data[:avg_wait_to_enter_seconds] = avg_wait_to_enter_seconds if avg_wait_to_enter_seconds
|
|
112
125
|
data[:avg_queue_to_enter_seconds] = avg_queue_to_enter_seconds if avg_queue_to_enter_seconds
|
|
113
|
-
data[:
|
|
126
|
+
data[:recent_slots_per_call] = recent_slots_per_call if recent_slots_per_call
|
|
127
|
+
data[:active_sessions] = active_sessions unless active_sessions.nil?
|
|
128
|
+
data[:max_sessions] = max_sessions unless max_sessions.nil?
|
|
129
|
+
data[:logs] = { waiting_guests_caller_job: logs } if logs
|
|
114
130
|
|
|
115
131
|
lobby_document.set(data, merge: true)
|
|
116
132
|
end
|
|
117
133
|
|
|
134
|
+
# Point-in-time snapshot (overwritten each run, never cumulative) of this caller run: last-run
|
|
135
|
+
# timing (heartbeat) and how many guests were called. Capacity state (active/max sessions,
|
|
136
|
+
# available_slots) lives top-level, not here.
|
|
137
|
+
def call_logs(started_at:, called_count:)
|
|
138
|
+
finished_at = Time.zone.now
|
|
139
|
+
{
|
|
140
|
+
called_count: called_count,
|
|
141
|
+
last_started_at: started_at,
|
|
142
|
+
last_finished_at: finished_at,
|
|
143
|
+
last_duration_ms: ((finished_at - started_at) * 1000).round
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
|
|
118
147
|
# Average total wait (queued_at → allow_to_enter_room_at) for just-called guests.
|
|
119
148
|
# Used for the Waiting Room step ETA: how long until I get in from when I joined.
|
|
120
149
|
def compute_avg_wait_to_enter_seconds(guests)
|
|
@@ -33,6 +33,7 @@ module Spree
|
|
|
33
33
|
@brand_color = @order.tenant.preferred_brand_primary_color
|
|
34
34
|
end
|
|
35
35
|
@product_type = @order.products.first&.product_type || 'accommodation'
|
|
36
|
+
@is_email = true
|
|
36
37
|
|
|
37
38
|
subject = build_subject(resend)
|
|
38
39
|
|
|
@@ -86,6 +87,7 @@ module Spree
|
|
|
86
87
|
|
|
87
88
|
@current_store = @order.store
|
|
88
89
|
@product_type = @line_item.product_type
|
|
90
|
+
@is_email = true
|
|
89
91
|
|
|
90
92
|
subject = "#{@current_store&.name} Booking Confirmation ##{@order.number}"
|
|
91
93
|
|
|
@@ -271,7 +271,18 @@ module SpreeCmCommissioner
|
|
|
271
271
|
|
|
272
272
|
def send_order_request_telegram_confirmation_alert_to_vendor; end
|
|
273
273
|
|
|
274
|
+
# Telegram order alerts are sent unless EVERY purchased product opts out.
|
|
275
|
+
# Default is true (see product_decorator), so existing orders are unaffected.
|
|
276
|
+
# Checked at enqueue time so disabled products create no telegram jobs at all.
|
|
277
|
+
def telegram_alert_enabled?
|
|
278
|
+
return @telegram_alert_enabled if defined?(@telegram_alert_enabled)
|
|
279
|
+
|
|
280
|
+
@telegram_alert_enabled = line_items.any?(&:enable_telegram_alert?)
|
|
281
|
+
end
|
|
282
|
+
|
|
274
283
|
def send_order_complete_telegram_alert_to_vendors
|
|
284
|
+
return unless telegram_alert_enabled?
|
|
285
|
+
|
|
275
286
|
vendor_list.each do |vendor|
|
|
276
287
|
title = '🎫 --- [NEW ORDER FROM BOOKME+] ---'
|
|
277
288
|
chat_id = vendor.preferred_telegram_chat_id
|
|
@@ -287,6 +298,8 @@ module SpreeCmCommissioner
|
|
|
287
298
|
end
|
|
288
299
|
|
|
289
300
|
def notify_order_complete_telegram_notification_to_user
|
|
301
|
+
return unless telegram_alert_enabled?
|
|
302
|
+
|
|
290
303
|
SpreeCmCommissioner::OrderCompleteTelegramSenderJob.perform_later(order_id: id) if user_id.present?
|
|
291
304
|
end
|
|
292
305
|
|
|
@@ -303,6 +316,8 @@ module SpreeCmCommissioner
|
|
|
303
316
|
end
|
|
304
317
|
|
|
305
318
|
def send_order_requested_telegram_alert_to_store
|
|
319
|
+
return unless telegram_alert_enabled?
|
|
320
|
+
|
|
306
321
|
title = '🔔 --- [NEW REQUESTED BY USER] ---'
|
|
307
322
|
chat_id = store.preferred_telegram_order_request_alert_chat_id
|
|
308
323
|
factory = OrderTelegramMessageFactory.new(title: title, order: self)
|
|
@@ -310,6 +325,8 @@ module SpreeCmCommissioner
|
|
|
310
325
|
end
|
|
311
326
|
|
|
312
327
|
def send_order_accepted_telegram_alert_to_store
|
|
328
|
+
return unless telegram_alert_enabled?
|
|
329
|
+
|
|
313
330
|
title = '✅ --- [ORDER ACCEPTED BY VENDOR] ---'
|
|
314
331
|
chat_id = store.preferred_telegram_order_request_alert_chat_id
|
|
315
332
|
factory = OrderTelegramMessageFactory.new(title: title, order: self)
|
|
@@ -317,6 +334,8 @@ module SpreeCmCommissioner
|
|
|
317
334
|
end
|
|
318
335
|
|
|
319
336
|
def send_order_rejected_telegram_alert_to_store
|
|
337
|
+
return unless telegram_alert_enabled?
|
|
338
|
+
|
|
320
339
|
title = '❌ --- [ORDER REJECTED BY VENDOR] ---'
|
|
321
340
|
chat_id = store.preferred_telegram_order_request_alert_chat_id
|
|
322
341
|
factory = OrderTelegramMessageFactory.new(title: title, order: self)
|
|
@@ -325,6 +344,7 @@ module SpreeCmCommissioner
|
|
|
325
344
|
|
|
326
345
|
def run_order_complete_alerts
|
|
327
346
|
return unless ENV['ORDER_INTEGRITY_TELEGRAM_ALERT_ENABLED'] == 'yes'
|
|
347
|
+
return unless telegram_alert_enabled?
|
|
328
348
|
|
|
329
349
|
# Allow async payment processors time to settle before checking for alerts
|
|
330
350
|
SpreeCmCommissioner::TelegramAlerts::OrderIntegrityCheckJob
|
|
@@ -333,6 +353,8 @@ module SpreeCmCommissioner
|
|
|
333
353
|
end
|
|
334
354
|
|
|
335
355
|
def send_order_complete_telegram_alert_to_store
|
|
356
|
+
return unless telegram_alert_enabled?
|
|
357
|
+
|
|
336
358
|
title = '🎫 --- [NEW ORDER] ---'
|
|
337
359
|
chat_id = store.preferred_telegram_order_alert_chat_id
|
|
338
360
|
factory = OrderTelegramMessageFactory.new(title: title, order: self)
|
|
@@ -85,6 +85,7 @@ module SpreeCmCommissioner
|
|
|
85
85
|
|
|
86
86
|
base.store_public_metadata :open_dated_validity_days, :integer, default: 90
|
|
87
87
|
base.store_public_metadata :enable_inventory_hold, :boolean, default: false
|
|
88
|
+
base.store_public_metadata :enable_telegram_alert, :boolean, default: true
|
|
88
89
|
|
|
89
90
|
base.after_update :update_variants_vendor_id, if: :saved_change_to_vendor_id?
|
|
90
91
|
base.after_update :sync_event_id_to_children, if: :saved_change_to_event_id?
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- insert_after "[data-hook='admin_product_form_promotionable']" -->
|
|
2
|
+
|
|
3
|
+
<div>
|
|
4
|
+
<%= f.field_container :enable_telegram_alert do %>
|
|
5
|
+
<%= f.label :enable_telegram_alert do %>
|
|
6
|
+
<%= f.check_box :enable_telegram_alert %>
|
|
7
|
+
<%= Spree.t(:enable_telegram_alert) %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<small class="form-text text-muted">
|
|
10
|
+
When enabled, Telegram order alerts are sent for this product. Turn off for
|
|
11
|
+
high-volume products to avoid flooding Telegram with order notifications.
|
|
12
|
+
</small>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
@@ -13,9 +13,23 @@ module SpreeCmCommissioner
|
|
|
13
13
|
create_blank_guest(line_item)
|
|
14
14
|
increase_quantity(line_item) if should_increase_quantity?(line_item)
|
|
15
15
|
recalculate_cart(order, line_item)
|
|
16
|
-
|
|
17
|
-
success(order: order, line_item: line_item)
|
|
18
16
|
end
|
|
17
|
+
|
|
18
|
+
success(order: order, line_item: line_item)
|
|
19
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
20
|
+
# Convert ONLY the out-of-stock failure into a failure result. The stock validator
|
|
21
|
+
# tags its error with the type :selected_quantity_not_available (see
|
|
22
|
+
# Spree::Stock::AvailabilityValidator), so we match on that type rather than on any
|
|
23
|
+
# RecordInvalid. The raise rolls back the lock transaction (so the blank guest is
|
|
24
|
+
# not persisted); returning it as a failure lets the caller render the overridden
|
|
25
|
+
# availability message instead of a 500.
|
|
26
|
+
#
|
|
27
|
+
# Anything else a different line item validation, or an invalid order/shipment
|
|
28
|
+
# raised by recalculate_cart is re-raised so genuine backend errors are not
|
|
29
|
+
# masked behind an empty quantity error.
|
|
30
|
+
raise unless e.record == line_item && e.record.errors.of_kind?(:quantity, :selected_quantity_not_available)
|
|
31
|
+
|
|
32
|
+
failure(e.record)
|
|
19
33
|
end
|
|
20
34
|
|
|
21
35
|
private
|
|
@@ -17,8 +17,17 @@ module SpreeCmCommissioner
|
|
|
17
17
|
extend SpreeCmCommissioner::ServiceModuleThrowable
|
|
18
18
|
|
|
19
19
|
def call
|
|
20
|
-
# merge: true so we never clobber the lobby's `full` / `available_slots` fields
|
|
21
|
-
|
|
20
|
+
# merge: true so we never clobber the lobby's `full` / `available_slots` fields, and it
|
|
21
|
+
# deep-merges the nested `logs` map so each job keeps its own namespaced key.
|
|
22
|
+
# logs.publish_lobby_path_job.last_published_at is a point-in-time heartbeat: it confirms
|
|
23
|
+
# the 5-min publisher cron is alive and (with the path above) shows the date partition.
|
|
24
|
+
lobby_document.set(
|
|
25
|
+
{
|
|
26
|
+
waiting_guests_records_path: records_path,
|
|
27
|
+
logs: { publish_lobby_path_job: { last_published_at: Time.zone.now } }
|
|
28
|
+
},
|
|
29
|
+
merge: true
|
|
30
|
+
)
|
|
22
31
|
|
|
23
32
|
success(records_path: records_path)
|
|
24
33
|
rescue StandardError => e
|
|
@@ -23,7 +23,7 @@ module SpreeCmCommissioner
|
|
|
23
23
|
CALLER_INTERVAL_SECONDS = (ENV['WAITING_ROOM_CALLER_INTERVAL_SECONDS'] || 60).to_i
|
|
24
24
|
|
|
25
25
|
# Caps the batch size assumed when estimating the ETA (not a release limit). The lobby's
|
|
26
|
-
#
|
|
26
|
+
# recent_slots_per_call is a single run's available_slots, which spikes on cold starts / lulls
|
|
27
27
|
# (active_sessions low → available_slots ≈ max_sessions). An uncapped spike collapses hundreds
|
|
28
28
|
# of positions into batch 1 and under-promises the wait, so we bound it to keep ETAs
|
|
29
29
|
# conservative. Tune to roughly the real per-minute release capacity.
|
|
@@ -39,11 +39,11 @@ module SpreeCmCommissioner
|
|
|
39
39
|
private
|
|
40
40
|
|
|
41
41
|
def stamp_positions
|
|
42
|
+
started_at = Time.zone.now
|
|
42
43
|
paths = stamp_paths
|
|
43
44
|
total = paths.sum { |path| waiting_count(path) }
|
|
44
|
-
return if total.zero?
|
|
45
45
|
|
|
46
|
-
@
|
|
46
|
+
@recent_slots_per_call = lobby_data[:recent_slots_per_call].to_i
|
|
47
47
|
@flat_wait = lobby_data[:avg_queue_to_enter_seconds].to_i
|
|
48
48
|
@stamped_at = Time.zone.now
|
|
49
49
|
@stamped = 0
|
|
@@ -60,6 +60,31 @@ module SpreeCmCommissioner
|
|
|
60
60
|
firestore.batch { |batch| slice.each { |doc| stamp_doc(batch, doc) } }
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
|
+
|
|
64
|
+
publish_logs(started_at: started_at, waiting_total: total)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Point-in-time heartbeat for the position-stamper cron, mirroring the caller/publisher writers.
|
|
68
|
+
# Written every run (even when nobody is waiting) so a stale timestamp signals a dead cron rather
|
|
69
|
+
# than an empty queue. merge: true deep-merges into the shared lobby `logs` map, so this job's
|
|
70
|
+
# key sits alongside the caller's and publisher's without clobbering them.
|
|
71
|
+
def publish_logs(started_at:, waiting_total:)
|
|
72
|
+
finished_at = Time.zone.now
|
|
73
|
+
lobby_document.set(
|
|
74
|
+
{
|
|
75
|
+
logs: {
|
|
76
|
+
stamp_queue_positions_job: {
|
|
77
|
+
stamped_count: @stamped,
|
|
78
|
+
waiting_total: waiting_total,
|
|
79
|
+
stamp_limit_reached: waiting_total > STAMP_LIMIT,
|
|
80
|
+
last_started_at: started_at,
|
|
81
|
+
last_finished_at: finished_at,
|
|
82
|
+
last_duration_ms: ((finished_at - started_at) * 1000).round
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
merge: true
|
|
87
|
+
)
|
|
63
88
|
end
|
|
64
89
|
|
|
65
90
|
def stamp_doc(batch, doc)
|
|
@@ -98,16 +123,16 @@ module SpreeCmCommissioner
|
|
|
98
123
|
end
|
|
99
124
|
|
|
100
125
|
# Groups positions into batches that enter together, so everyone in a batch gets the same ETA.
|
|
101
|
-
# The batch size is
|
|
102
|
-
# from under-promising. nil when
|
|
126
|
+
# The batch size is recent_slots_per_call, capped by MAX_ETA_BATCH_SIZE to keep a spiking release rate
|
|
127
|
+
# from under-promising. nil when recent_slots_per_call is unknown (cold start) → falls back to the
|
|
103
128
|
# flat lobby average. e.g. batch size 50, 1 call/min:
|
|
104
129
|
# flat estimate: position 1 and position 50 both see ~20 min ❌
|
|
105
130
|
# batch estimate: positions 1–50 → batch 1 → ~10 min
|
|
106
131
|
# positions 51–100 → batch 2 → ~20 min ✅
|
|
107
132
|
def batch_wait(position)
|
|
108
|
-
return nil unless @
|
|
133
|
+
return nil unless @recent_slots_per_call.positive?
|
|
109
134
|
|
|
110
|
-
batch_size = [@
|
|
135
|
+
batch_size = [@recent_slots_per_call, MAX_ETA_BATCH_SIZE].min
|
|
111
136
|
batch_number = ((position - 1) / batch_size) + 1
|
|
112
137
|
[batch_number * CALLER_INTERVAL_SECONDS, min_queue_floor].max
|
|
113
138
|
end
|
|
@@ -141,7 +166,11 @@ module SpreeCmCommissioner
|
|
|
141
166
|
end
|
|
142
167
|
|
|
143
168
|
def lobby_data
|
|
144
|
-
@lobby_data ||=
|
|
169
|
+
@lobby_data ||= lobby_document.get.data || {}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def lobby_document
|
|
173
|
+
@lobby_document ||= firestore.col('waiting_rooms').doc('lobby')
|
|
145
174
|
end
|
|
146
175
|
|
|
147
176
|
def firestore
|
|
@@ -61,15 +61,18 @@
|
|
|
61
61
|
<% end %>
|
|
62
62
|
</td>
|
|
63
63
|
</tr>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
<%# In the email only, hide the ticket link for app-only products. %>
|
|
65
|
+
<% unless @is_email && line_item&.product&.purchasable_on_app? %>
|
|
66
|
+
<tr>
|
|
67
|
+
<td></td>
|
|
68
|
+
<td align="left" style="padding-top:6px;">
|
|
69
|
+
<%= link_to(
|
|
70
|
+
raw('<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 8px;"><rect width="5" height="5" x="3" y="3" rx="1"/><rect width="5" height="5" x="16" y="3" rx="1"/><rect width="5" height="5" x="3" y="16" rx="1"/><path d="M21 16h-3a2 2 0 0 0-2 2v3"/><path d="M21 21v.01"/><path d="M12 7v3a2 2 0 0 1-2 2H7"/><path d="M3 12h.01"/><path d="M12 3h.01"/><path d="M12 16v.01"/><path d="M16 12h1"/><path d="M21 12v.01"/><path d="M12 21v-1"/></svg> View Your Digital Ticket'),
|
|
71
|
+
custom_product_line_item_url(line_item), class: "btn-primary", target: :_blank
|
|
72
|
+
) %>
|
|
73
|
+
</td>
|
|
74
|
+
</tr>
|
|
75
|
+
<% end %>
|
|
73
76
|
</table>
|
|
74
77
|
</td>
|
|
75
78
|
</tr>
|
|
@@ -6,6 +6,9 @@ module Spree
|
|
|
6
6
|
@@vendor_attributes << :logo
|
|
7
7
|
@@vendor_attributes << :service_origin_id
|
|
8
8
|
|
|
9
|
+
# store_accessor key (public_metadata), not a DB column, so it must be permitted explicitly
|
|
10
|
+
@@product_attributes << :enable_telegram_alert unless @@product_attributes.include?(:enable_telegram_alert)
|
|
11
|
+
|
|
9
12
|
# Permitted all guest attributes for now as permitting only some guest attributes is not working by design
|
|
10
13
|
# of spree add_item service, if we want to only permit some guest attributes,
|
|
11
14
|
# we need to override most part of add_item service.
|
data/config/locales/en.yml
CHANGED
|
@@ -217,6 +217,7 @@ en:
|
|
|
217
217
|
|
|
218
218
|
spree:
|
|
219
219
|
description: Description
|
|
220
|
+
enable_telegram_alert: Enable Telegram Alert
|
|
220
221
|
registered_by:
|
|
221
222
|
system_registered: "System Registered"
|
|
222
223
|
self_registered: "Self Registered"
|
|
@@ -414,6 +415,9 @@ en:
|
|
|
414
415
|
zero: "Rooms are not available on %{date}"
|
|
415
416
|
one: "Only 1 room available on %{date}"
|
|
416
417
|
other: "Only %{count} rooms available on %{date}"
|
|
418
|
+
# Overrides Spree core's "selected of %{item} is not available." Must stay under
|
|
419
|
+
# `spree:` -- Spree::Stock::AvailabilityValidator looks it up as Spree.t(:selected_quantity_not_available).
|
|
420
|
+
selected_quantity_not_available: "Not enough %{item} are available for the quantity you selected. Please try again."
|
|
417
421
|
auto_apply: "Auto apply"
|
|
418
422
|
auto_apply_info: "Path & code will be removed"
|
|
419
423
|
transit:
|
|
@@ -715,7 +719,7 @@ en:
|
|
|
715
719
|
inventory_hold:
|
|
716
720
|
acquire:
|
|
717
721
|
success: "Item reserved for %{minutes} minutes"
|
|
718
|
-
insufficient_stock: "
|
|
722
|
+
insufficient_stock: "Some items in your cart are currently held by another customer or no longer available. Please update your cart or try again shortly."
|
|
719
723
|
limit_exceeded: "You have too many pending reservations. Please complete or cancel a reservation before trying another."
|
|
720
724
|
system_error: "We encountered an issue reserving this item. Please try again."
|
|
721
725
|
lock_timeout: "Another reservation is in progress for this order. Please wait and try again."
|
data/config/locales/km.yml
CHANGED
|
@@ -357,6 +357,7 @@ km:
|
|
|
357
357
|
unknown: "មិនស្គាល់"
|
|
358
358
|
upsupported_payment: "មិនស្គាល់ការបង់ប្រាក់"
|
|
359
359
|
selected_item_not_available_on_date: "Selected item not available on %{date}"
|
|
360
|
+
selected_quantity_not_available: "%{item} នៅសល់តិចជាងការអ្នកស្នើសុំរបស់អ្នក ឬអស់ពីស្តុក សូមព្យាយាមម្ដងទៀត។"
|
|
360
361
|
auto_apply: "ដាក់ប្រើដោយស្វ័យប្រវត្តិ"
|
|
361
362
|
auto_apply_info: "Path & code will be removed"
|
|
362
363
|
device_tokens: "Device Tokens"
|
|
@@ -518,7 +519,7 @@ km:
|
|
|
518
519
|
inventory_hold:
|
|
519
520
|
acquire:
|
|
520
521
|
success: "ទំនិញត្រូវបានកក់រយៈពេល %{minutes} នាទី"
|
|
521
|
-
insufficient_stock: "
|
|
522
|
+
insufficient_stock: "ទំនិញមួយចំនួននៅក្នុងកន្ត្រករបស់អ្នកកំពុងត្រូវបានកក់ដោយអតិថិជនផ្សេងទៀត ឬលែងមានស្តុក។ សូមកែប្រែកន្ត្រករបស់អ្នក ឬព្យាយាមម្តងទៀតក្នុងពេលបន្តិចទៀត។"
|
|
522
523
|
limit_exceeded: "អ្នកមានការកក់ដែលរង់ចាំច្រើនពេក។ សូមបញ្ចប់ ឬលុបការកក់មួយមុនពេលព្យាយាមវិញ។"
|
|
523
524
|
system_error: "មានបញ្ហាក្នុងការកក់ទំនិញនេះ។ សូមព្យាយាមម្តងទៀត។"
|
|
524
525
|
lock_timeout: "កំពុងកក់សម្រាប់ការបញ្ជាទិញនេះ។ សូមរង់ចាំ ហើយព្យាយាមម្តងទៀត។"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_cm_commissioner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.8.
|
|
4
|
+
version: 2.8.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- You
|
|
@@ -1734,6 +1734,7 @@ files:
|
|
|
1734
1734
|
- app/overrides/spree/admin/orders/_search/intel_phone_number_search_field.html.erb.deface
|
|
1735
1735
|
- app/overrides/spree/admin/orders/_search/line_item_number_search_field.html.erb.deface
|
|
1736
1736
|
- app/overrides/spree/admin/orders/_search/payment_method_search_field.html.erb.deface
|
|
1737
|
+
- app/overrides/spree/admin/orders/_search/payment_number_search_field.html.erb.deface
|
|
1737
1738
|
- app/overrides/spree/admin/orders/_search/vendor_search_field.html.erb.deface
|
|
1738
1739
|
- app/overrides/spree/admin/orders/customer_details/_form/phone_number_field.html.erb.deface
|
|
1739
1740
|
- app/overrides/spree/admin/payment_methods/_form/display_on_field.html.erb.deface
|
|
@@ -1746,6 +1747,7 @@ files:
|
|
|
1746
1747
|
- app/overrides/spree/admin/products/_form/available_on.html.erb.deface
|
|
1747
1748
|
- app/overrides/spree/admin/products/_form/discontinue_on.html.erb.deface
|
|
1748
1749
|
- app/overrides/spree/admin/products/_form/enable_inventory_hold.html.erb.deface
|
|
1750
|
+
- app/overrides/spree/admin/products/_form/enable_telegram_alert.html.erb.deface
|
|
1749
1751
|
- app/overrides/spree/admin/products/_form/industry_taxons.html.erb.deface
|
|
1750
1752
|
- app/overrides/spree/admin/products/_form/need_confirmation_checkbox.html.erb.deface
|
|
1751
1753
|
- app/overrides/spree/admin/products/_form/option_types.html.erb.deface
|