spree_cm_commissioner 2.12.5 → 2.12.6
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/private_sales_controller.rb +0 -15
- data/app/helpers/spree_cm_commissioner/admin/inventory_locks_helper.rb +7 -1
- data/app/services/spree_cm_commissioner/events/publish_stock_statuses_to_firestore.rb +9 -0
- data/app/services/spree_cm_commissioner/waiting_room/stamp_queue_positions.rb +25 -10
- data/app/views/spree/admin/private_sales/_stock.html.erb +5 -3
- data/app/views/spree/admin/private_sales/index.html.erb +4 -14
- data/config/locales/en.yml +3 -3
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed84dcf2382e8d3720366acd62551189adfa4edba9533d8ca5e94b0704942836
|
|
4
|
+
data.tar.gz: aa37bf6b43593f7f7ff91d71d1839b1ceeeb8c9271d5db371451f94958399366
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5b89052a82aedeed1144cf9a5892e3d8ec2428c3a4cd26cc5806010c1bbcc3ad353a768e6854c3fdaf69a2ae2fdb5f6f049616b7cac1b2a9d3cd532344708d3
|
|
7
|
+
data.tar.gz: 7842ce22211f5e072e308efda5f4f5a0b95625729d18388ec6c3c22406fd76b4e5577daca6d5a9f717fd28990639d3897b3a0d58c1d18bebd2c1959f152b7b80
|
data/Gemfile.lock
CHANGED
|
@@ -20,8 +20,6 @@ module Spree
|
|
|
20
20
|
# Off `collection`, which ResourceController already scoped with accessible_by(current_ability)
|
|
21
21
|
# — going through model_class directly would discard that.
|
|
22
22
|
@private_sales = collection.order(created_at: :desc).page(params[:page]).per(25)
|
|
23
|
-
|
|
24
|
-
load_audience_names
|
|
25
23
|
end
|
|
26
24
|
|
|
27
25
|
# PATCH /admin/private_sales/:id/revoke
|
|
@@ -38,19 +36,6 @@ module Spree
|
|
|
38
36
|
|
|
39
37
|
private
|
|
40
38
|
|
|
41
|
-
# Names for every product, user and role named by the sales ON THIS PAGE, in three queries.
|
|
42
|
-
# The ids live in jsonb rather than join tables, so there is no association for `includes` to
|
|
43
|
-
# preload — asking each row for its own would be 3 queries per row.
|
|
44
|
-
def load_audience_names
|
|
45
|
-
sales = @private_sales.to_a
|
|
46
|
-
|
|
47
|
-
@product_names = Spree::Product.where(id: sales.flat_map(&:product_ids)).pluck(:id, :name).to_h
|
|
48
|
-
@user_emails = Spree::User.where(id: sales.flat_map(&:user_ids)).pluck(:id, :email).to_h
|
|
49
|
-
@role_names = Spree::Role.where(id: sales.flat_map(&:role_ids))
|
|
50
|
-
.pluck(:id, :presentation, :name)
|
|
51
|
-
.to_h { |id, presentation, name| [id, presentation.presence || name] }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
39
|
# Straight to edit rather than back to the list: a sale is not usable until stock is withheld
|
|
55
40
|
# for it, and edit is where that happens. Landing on the index would hide the next step.
|
|
56
41
|
# non_vendor: a private sale grants access across the store, and this repo's roles include
|
|
@@ -15,7 +15,13 @@ module SpreeCmCommissioner
|
|
|
15
15
|
capacity = inventory_item.max_capacity.to_i
|
|
16
16
|
return nil if capacity.zero?
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
percentage = inventory_item.quantity_available.to_f / capacity * 100
|
|
19
|
+
return percentage.round if percentage <= 0 || percentage >= 1
|
|
20
|
+
|
|
21
|
+
# A few seats left out of a large capacity is a fraction of a percent but is NOT sold out;
|
|
22
|
+
# rounding it whole would print 0% and read as exactly that. Two decimals, floored at 0.01 so
|
|
23
|
+
# capacities past 10k still show something non-zero.
|
|
24
|
+
[percentage.round(2), 0.01].max
|
|
19
25
|
end
|
|
20
26
|
|
|
21
27
|
# High/medium/low as risk of running out of public stock, not as a grade — a bare number in a
|
|
@@ -123,11 +123,20 @@ module SpreeCmCommissioner
|
|
|
123
123
|
# table-unqualified `available_on`/`discontinue_on` strings below become ambiguous between
|
|
124
124
|
# `spree_products` and `spree_variants` and the whole query raises `PG::AmbiguousColumn`. A
|
|
125
125
|
# plain status check needs no join at all, so it can't collide with these.
|
|
126
|
+
#
|
|
127
|
+
# `event_id` alone isn't enough to tell "belongs to this event" from "organizer unlisted it
|
|
128
|
+
# from its section but hasn't moved it off the event" — those look identical on the product
|
|
129
|
+
# row. `children_classifications` (Classification rows for the event's section children,
|
|
130
|
+
# taxon_decorator.rb) carries the actual unlist toggle: `visible: false` is how an organizer
|
|
131
|
+
# hides a product from a section without touching `event_id`, same flag
|
|
132
|
+
# `Products::FindDecorator` already checks for storefront listings, so an unlisted product
|
|
133
|
+
# is excluded here too rather than still getting a live stock-status entry in Firestore.
|
|
126
134
|
def active_products(taxon)
|
|
127
135
|
now = Time.zone.now
|
|
128
136
|
taxon.event_products.ecommerce.where(status: 'active')
|
|
129
137
|
.where('available_on IS NULL OR available_on <= ?', now)
|
|
130
138
|
.where('discontinue_on IS NULL OR discontinue_on > ?', now)
|
|
139
|
+
.where(id: taxon.children_classifications.where(visible: true).select(:product_id))
|
|
131
140
|
end
|
|
132
141
|
|
|
133
142
|
# Sums, per product id, of the live Redis-sourced quantities for every active inventory item
|
|
@@ -92,23 +92,38 @@ module SpreeCmCommissioner
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
# Live count of guests no longer waiting: granted entry (allow_to_enter_room_at set) or left
|
|
95
|
-
# the queue
|
|
96
|
-
# assigned — e.g. if the highest
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
95
|
+
# the queue without ever being granted (left_at set, allow_to_enter_room_at still nil). Both
|
|
96
|
+
# matter because #stamp_doc's position is permanent once assigned — e.g. if the highest
|
|
97
|
+
# stamped position is 4200 but this only counts 3714, every guest stamped after 3714 shows
|
|
98
|
+
# ~486 phantom people "ahead of them" that never resolve.
|
|
99
|
+
#
|
|
100
|
+
# allow_to_enter_room_at and left_at are not mutually exclusive: rejoinQueueAfterExpiry()
|
|
101
|
+
# sets left_at on a doc that was already granted (session renewal failed past the expiry
|
|
102
|
+
# ceiling after entry), so a doc can carry both fields non-nil at once. #allowed_count is
|
|
103
|
+
# unconditional on left_at so it always claims that doc; #left_without_entry_count excludes
|
|
104
|
+
# anything #allowed_count already claimed (allow_to_enter_room_at == nil) so the two counts
|
|
105
|
+
# partition every exited doc into exactly one bucket — summing plain `left_at != nil` here
|
|
106
|
+
# instead would double-count that doc, same as summing plain `allow_to_enter_room_at == nil`
|
|
107
|
+
# without excluding it here would drop the doc from both counts entirely.
|
|
108
|
+
#
|
|
109
|
+
# Two counts rather than one OR query so each stays index-light — #allowed_count needs no
|
|
110
|
+
# composite index, #left_without_entry_count needs only a two-field one (allow_to_enter_room_at
|
|
111
|
+
# ==, left_at !=) — and #waiting_query / WaitingGuestsCaller#eligible_guests_in never need to
|
|
112
|
+
# know about `left_at`. Recomputed fresh every run rather than incremented, so a crashed run
|
|
113
|
+
# can't leave it permanently understated.
|
|
102
114
|
def exited_count(path)
|
|
103
|
-
allowed_count(path) +
|
|
115
|
+
allowed_count(path) + left_without_entry_count(path)
|
|
104
116
|
end
|
|
105
117
|
|
|
106
118
|
def allowed_count(path)
|
|
107
119
|
firestore.col(path).where('allow_to_enter_room_at', '!=', nil).aggregate_query.add_count.get.first.get.to_i
|
|
108
120
|
end
|
|
109
121
|
|
|
110
|
-
def
|
|
111
|
-
firestore.col(path)
|
|
122
|
+
def left_without_entry_count(path)
|
|
123
|
+
firestore.col(path)
|
|
124
|
+
.where('allow_to_enter_room_at', '==', nil)
|
|
125
|
+
.where('left_at', '!=', nil)
|
|
126
|
+
.aggregate_query.add_count.get.first.get.to_i
|
|
112
127
|
end
|
|
113
128
|
|
|
114
129
|
# merge: true so total_exited_queue coexists with the fields WaitingGuestsCaller publishes to
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
<th><%= Spree.t(:variant) %></th>
|
|
32
32
|
<th class="text-center"><%= t('.max_capacity') %></th>
|
|
33
33
|
<th class="text-center"><%= t('.quantity_available') %></th>
|
|
34
|
+
<th class="text-center"><%= t('.quantity_on_hold') %></th>
|
|
34
35
|
<th class="text-center"><%= t('.quantity_locked') %></th>
|
|
35
36
|
<th class="text-center"><%= t('.quantity_locked_on_hold') %></th>
|
|
36
37
|
<th class="text-center"><%= t('.lock_release') %></th>
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
<% next if product.nil? %>
|
|
44
45
|
|
|
45
46
|
<tr class="bg-light">
|
|
46
|
-
<td colspan="
|
|
47
|
+
<td colspan="7"><strong><%= product.name %></strong></td>
|
|
47
48
|
<td class="text-right">
|
|
48
49
|
<%# Own column so these line up under the same x-position for every product, rather than
|
|
49
50
|
sharing space with the name — icon-only, matching the with-tip pattern used for row
|
|
@@ -69,12 +70,13 @@
|
|
|
69
70
|
<% if item.nil? %>
|
|
70
71
|
<%# No undated row means this product's stock is per date — hundreds of rows a single
|
|
71
72
|
input cannot express. Those are managed on the product's own screen. %>
|
|
72
|
-
<td colspan="
|
|
73
|
+
<td colspan="7" class="text-center text-muted">
|
|
73
74
|
<%= link_to t('.per_date_stock'), admin_product_inventory_locks_path(product) %>
|
|
74
75
|
</td>
|
|
75
76
|
<% else %>
|
|
76
77
|
<td class="text-center text-muted"><%= item.max_capacity %></td>
|
|
77
78
|
<td class="text-center"><%= render partial: 'spree/admin/shared/availability_percentage', locals: { inventory_item: item } %></td>
|
|
79
|
+
<td class="text-center"><%= item.quantity_on_hold %></td>
|
|
78
80
|
<td class="text-center"><strong><%= item.quantity_locked %></strong></td>
|
|
79
81
|
<td class="text-center"><%= item.quantity_locked_on_hold %></td>
|
|
80
82
|
<td class="text-center">
|
|
@@ -84,7 +86,7 @@
|
|
|
84
86
|
<div class="input-group input-group-sm">
|
|
85
87
|
<%= number_field_tag :quantity, nil, class: 'form-control text-center p-0', id: nil %>
|
|
86
88
|
<div class="input-group-append">
|
|
87
|
-
<%= button_tag(class: 'btn btn-outline-success pl-2 pr-1') do %>
|
|
89
|
+
<%= button_tag(class: 'btn btn-outline-success pl-2 pr-1', data: { confirm: t('.lock_release_confirm') }) do %>
|
|
88
90
|
<%= svg_icon(name: 'arrow-left-right.svg', classes: 'icon', width: 14, height: 14) %>
|
|
89
91
|
<% end %>
|
|
90
92
|
</div>
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
<thead class="text-muted">
|
|
15
15
|
<tr>
|
|
16
16
|
<th><%= Spree.t(:name) %></th>
|
|
17
|
-
<th><%= t('.products') %></th>
|
|
18
|
-
<th><%= t('.audience') %></th>
|
|
19
17
|
<th><%= t('.expires_at') %></th>
|
|
20
18
|
<th><%= Spree.t(:status) %></th>
|
|
19
|
+
<th><%= Spree.t(:created_at) %></th>
|
|
20
|
+
<th><%= t('.updated_at') %></th>
|
|
21
21
|
<th class="text-right"></th>
|
|
22
22
|
</tr>
|
|
23
23
|
</thead>
|
|
@@ -27,18 +27,6 @@
|
|
|
27
27
|
answer, and a vanished row cannot give it. %>
|
|
28
28
|
<tr class="<%= 'text-muted' unless private_sale.usable? %>">
|
|
29
29
|
<td><%= private_sale.name %></td>
|
|
30
|
-
<%# Names come from the page-wide lookups built in the controller — asking each row for
|
|
31
|
-
its own products/users/roles is 3 queries per row. %>
|
|
32
|
-
<td><%= private_sale.product_ids.filter_map { |id| @product_names[id] }.join(', ').presence || '—' %></td>
|
|
33
|
-
<td>
|
|
34
|
-
<% if private_sale.open_to_anyone? %>
|
|
35
|
-
<%= t('.anyone') %>
|
|
36
|
-
<% else %>
|
|
37
|
-
<%= [private_sale.user_ids.filter_map { |id| @user_emails[id] }.presence&.join(', '),
|
|
38
|
-
private_sale.role_ids.filter_map { |id| @role_names[id] }.presence&.join(', ')]
|
|
39
|
-
.compact.join(' · ') %>
|
|
40
|
-
<% end %>
|
|
41
|
-
</td>
|
|
42
30
|
<td><%= private_sale.expires_at.to_date %></td>
|
|
43
31
|
<td>
|
|
44
32
|
<% if private_sale.revoked? %>
|
|
@@ -51,6 +39,8 @@
|
|
|
51
39
|
<span class="badge badge-success"><%= t('.active') %></span>
|
|
52
40
|
<% end %>
|
|
53
41
|
</td>
|
|
42
|
+
<td><%= l(private_sale.created_at) %></td>
|
|
43
|
+
<td><%= l(private_sale.updated_at) %></td>
|
|
54
44
|
<td class="text-right">
|
|
55
45
|
<% if private_sale.usable? %>
|
|
56
46
|
<div class="d-inline-block align-middle" style="width: 280px;">
|
data/config/locales/en.yml
CHANGED
|
@@ -559,10 +559,8 @@ en:
|
|
|
559
559
|
index:
|
|
560
560
|
title: "Private Sales"
|
|
561
561
|
new: "New Private Sale"
|
|
562
|
-
products: "Products"
|
|
563
|
-
audience: "Who can buy"
|
|
564
562
|
expires_at: "Expires"
|
|
565
|
-
|
|
563
|
+
updated_at: "Updated"
|
|
566
564
|
active: "Active"
|
|
567
565
|
revoked: "Revoked"
|
|
568
566
|
expired: "Expired"
|
|
@@ -593,9 +591,11 @@ en:
|
|
|
593
591
|
no_products: "Pick at least one product above and save, then set how much to hold back."
|
|
594
592
|
max_capacity: "Max capacity"
|
|
595
593
|
quantity_available: "Quantity available"
|
|
594
|
+
quantity_on_hold: "Quantity on hold"
|
|
596
595
|
quantity_locked: "Quantity locked"
|
|
597
596
|
quantity_locked_on_hold: "Quantity locked on hold"
|
|
598
597
|
lock_release: "Lock (+) / Release (−)"
|
|
598
|
+
lock_release_confirm: "Apply this change to locked stock? This takes effect immediately."
|
|
599
599
|
manage_product_stock: "Product stock"
|
|
600
600
|
lock_history: "Lock history"
|
|
601
601
|
per_date_stock: "This product's stock is per date — manage it on the product"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_cm_commissioner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.12.
|
|
4
|
+
version: 2.12.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- You
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: spree
|