spree_cm_commissioner 2.12.6 → 2.12.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/blazer/queries_controller_decorator.rb +1 -1
- data/app/controllers/spree/admin/blazer_queries_controller.rb +43 -0
- data/app/controllers/spree/admin/vendor_authorized_users_controller.rb +15 -0
- data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +19 -4
- data/app/models/blazer/query_decorator.rb +9 -0
- data/app/models/concerns/spree_cm_commissioner/order_channel_bitwise.rb +70 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +21 -6
- data/app/models/concerns/spree_cm_commissioner/ticket_transferable.rb +2 -2
- data/app/models/concerns/spree_cm_commissioner/vendor_preference.rb +19 -0
- data/app/models/spree_cm_commissioner/order_decorator.rb +1 -13
- data/app/overrides/spree/admin/shared/sub_menu/_configuration/blazer_queries_tab.html.erb.deface +3 -0
- data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +6 -1
- data/app/services/spree_cm_commissioner/intercity_taxi_order/create.rb +4 -2
- data/app/services/spree_cm_commissioner/show/clone.rb +145 -0
- data/app/services/spree_cm_commissioner/ticket_transfers/accept.rb +1 -1
- data/app/services/spree_cm_commissioner/transit_order/create.rb +10 -4
- data/app/services/spree_cm_commissioner/waiting_room/stamp_queue_positions.rb +10 -25
- data/app/views/blazer/queries/_content.html.erb +0 -2
- data/app/views/spree/admin/blazer_queries/_form.html.erb +37 -0
- data/app/views/spree/admin/blazer_queries/edit.html.erb +11 -0
- data/app/views/spree/admin/blazer_queries/index.html.erb +72 -0
- data/app/views/spree/admin/blazer_queries/new.html.erb +11 -0
- data/app/views/spree/admin/vendor_authorized_users/index.html.erb +15 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +1 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +11 -3
- data/app/views/blazer/queries/_preset.html.erb +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 742162ce28f156432f76f071c6538e682fecfa8008758acc698d283cb2a3742c
|
|
4
|
+
data.tar.gz: bfafd91f275bc28a16234e3153ee8d83aa5b5040be06acca7850770b2573fe01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f8c8857a35fe48099949496f9e69c0741ca32a18a05fbc05afafdc1cb5cd7feb3394b3b0158cbb27dea96f585e378f9a4a14c0d2fbac1b8cee6e54c8a51be443
|
|
7
|
+
data.tar.gz: d8e013c7a5172fabbfea21e83ac52d5231351ed380960e1308b458ad1e7b6cd960bb2ca7a6204b82448f3d494c17cb3eb3ebb9a5d704228b901c789dacef09f2
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class BlazerQueriesController < Spree::Admin::ResourceController
|
|
4
|
+
STATEMENT_PLACEHOLDER = '-- write your SQL for this query in Blazer'.freeze
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
@search = model_class.ransack(params[:q])
|
|
8
|
+
@collection = @search.result
|
|
9
|
+
.order(created_at: :desc)
|
|
10
|
+
.page(params[:page])
|
|
11
|
+
.per(25)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def model_class
|
|
17
|
+
Blazer::Query
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def object_name
|
|
21
|
+
'blazer_query'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def collection_url
|
|
25
|
+
admin_blazer_queries_url
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def location_after_save
|
|
29
|
+
return blazer.edit_query_path(@object) if action_name == 'create'
|
|
30
|
+
|
|
31
|
+
edit_admin_blazer_query_path(@object)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def build_resource
|
|
35
|
+
model_class.new(statement: STATEMENT_PLACEHOLDER, data_source: Blazer.data_sources.keys.first)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def permitted_resource_params
|
|
39
|
+
params.require(:blazer_query).permit(:name, :description, :data_source, :preset)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -14,6 +14,8 @@ module Spree
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def update_telegram_chat
|
|
17
|
+
@vendor.update(preferred_telegram_alert_channels: submitted_alert_channels_mask)
|
|
18
|
+
|
|
17
19
|
context = ::SpreeCmCommissioner::TelegramChatsFinder.call(
|
|
18
20
|
telegram_chat_name: params['vendor']['preferred_telegram_chat_name'],
|
|
19
21
|
telegram_chat_type: params['vendor']['preferred_telegram_chat_type']
|
|
@@ -34,6 +36,19 @@ module Spree
|
|
|
34
36
|
redirect_to collection_url(@vendor)
|
|
35
37
|
end
|
|
36
38
|
|
|
39
|
+
# OR the bits, never sum them. Sum is not idempotent: if the same bit arrives twice
|
|
40
|
+
# (duplicated checkbox, double-submit, hand-crafted post) 1 + 1 = 2, which silently means
|
|
41
|
+
# google_form instead of spree. ["0","1","1","16"].sum => 18 => [google_form, offline_sell];
|
|
42
|
+
# the same input OR'd => 17 => [spree, offline_sell].
|
|
43
|
+
#
|
|
44
|
+
# Masking against the known bits also drops junk from a crafted post (999 => 7).
|
|
45
|
+
def submitted_alert_channels_mask
|
|
46
|
+
known = SpreeCmCommissioner::OrderChannelBitwise::CHANNEL_ALERT_BITS.values.reduce(:|)
|
|
47
|
+
|
|
48
|
+
Array(params.dig(:vendor, :telegram_alert_channels))
|
|
49
|
+
.reduce(0) { |acc, value| acc | value.to_i } & known
|
|
50
|
+
end
|
|
51
|
+
|
|
37
52
|
# @overrided
|
|
38
53
|
def model_class
|
|
39
54
|
Spree::User
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
# Delivery Address: #123, St. 456, Phnom Penh
|
|
23
23
|
|
|
24
24
|
module SpreeCmCommissioner
|
|
25
|
-
class OrderTelegramMessageFactory < TelegramMessageFactory
|
|
25
|
+
class OrderTelegramMessageFactory < TelegramMessageFactory # rubocop:disable Metrics/ClassLength
|
|
26
26
|
attr_reader :order, :vendor, :show_details_link
|
|
27
27
|
|
|
28
28
|
def initialize(title:, order:, subtitle: nil, show_details_link: nil, vendor: nil)
|
|
@@ -57,10 +57,20 @@ module SpreeCmCommissioner
|
|
|
57
57
|
text << italic("👉 #{line_item.options_text}") if line_item.options_text.present?
|
|
58
58
|
text << italic(pretty_date_for(line_item)) if pretty_date_for(line_item).present?
|
|
59
59
|
text << italic("🏪 #{line_item.vendor.name}") if line_item.vendor&.name.present? && vendor.blank?
|
|
60
|
+
text << seat_labels_text(line_item) if seat_labels_text(line_item).present?
|
|
60
61
|
|
|
61
62
|
text.compact.join("\n")
|
|
62
63
|
end
|
|
63
64
|
|
|
65
|
+
def seat_labels_text(line_item)
|
|
66
|
+
return nil unless line_item.required_seat_selection?
|
|
67
|
+
|
|
68
|
+
labels = line_item.guests_with_blocks.includes(:block).map { |guest| guest.block&.label }.compact
|
|
69
|
+
return nil if labels.empty?
|
|
70
|
+
|
|
71
|
+
"Seat: #{labels.join(', ')}"
|
|
72
|
+
end
|
|
73
|
+
|
|
64
74
|
def pretty_date_for(line_item)
|
|
65
75
|
return nil unless line_item.date_present?
|
|
66
76
|
|
|
@@ -150,11 +160,14 @@ module SpreeCmCommissioner
|
|
|
150
160
|
def transit_line_item_content(line_item)
|
|
151
161
|
text = []
|
|
152
162
|
route_text = transit_route_text(line_item)
|
|
163
|
+
vehicle_text = transit_vehicle_text(line_item)
|
|
153
164
|
departure_text = transit_departure_text(line_item)
|
|
154
165
|
text << bold('🚗 Trip Details')
|
|
155
166
|
text << "Route: #{route_text}" if route_text.present?
|
|
167
|
+
text << "Vehicle: #{vehicle_text}" if vehicle_text.present?
|
|
156
168
|
text << "Passengers: #{line_item.passenger_count || line_item.quantity}"
|
|
157
169
|
text << "Departure: #{departure_text}" if departure_text.present?
|
|
170
|
+
text << seat_labels_text(line_item) if seat_labels_text(line_item).present?
|
|
158
171
|
|
|
159
172
|
text.compact.join("\n")
|
|
160
173
|
end
|
|
@@ -165,10 +178,12 @@ module SpreeCmCommissioner
|
|
|
165
178
|
|
|
166
179
|
origin = trip.origin_place&.name
|
|
167
180
|
destination = trip.destination_place&.name
|
|
168
|
-
vehicle = trip.vehicle_type&.name
|
|
169
181
|
|
|
170
|
-
|
|
171
|
-
|
|
182
|
+
[origin, destination].compact_blank.join(' → ')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def transit_vehicle_text(line_item)
|
|
186
|
+
trip_for(line_item)&.vehicle_type&.name
|
|
172
187
|
end
|
|
173
188
|
|
|
174
189
|
def transit_departure_text(line_item)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
module QueryDecorator
|
|
3
|
+
def ransackable_attributes(auth_object = nil)
|
|
4
|
+
super & %w[name description data_source preset created_at updated_at]
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Blazer::Query.singleton_class.prepend(Blazer::QueryDecorator) unless Blazer::Query.singleton_class.ancestors.include?(Blazer::QueryDecorator)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Owns everything about an order's `channel`: the allowed values, how a stored value
|
|
2
|
+
# resolves back to one of them, and the bitmask vendors use to choose which channels
|
|
3
|
+
# may alert their Telegram group.
|
|
4
|
+
#
|
|
5
|
+
# `channel` is validated by PREFIX, not equality, because the CSV importer stores
|
|
6
|
+
# "google_form-<import_order_id>-<slug>". Always compare via #channel_key, never `==`.
|
|
7
|
+
module SpreeCmCommissioner
|
|
8
|
+
module OrderChannelBitwise
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
SPREE_CHANNEL = 'spree'.freeze
|
|
12
|
+
GOOGLE_FORM_CHANNEL = 'google_form'.freeze
|
|
13
|
+
TELEGRAM_CHANNEL = 'telegram'.freeze
|
|
14
|
+
TICKET_TRANSFER_CHANNEL = 'ticket_transfer'.freeze
|
|
15
|
+
OFFLINE_SELL_CHANNEL = 'offline_sell'.freeze
|
|
16
|
+
|
|
17
|
+
ALLOWED_CHANNEL_PREFIXES = [
|
|
18
|
+
SPREE_CHANNEL, GOOGLE_FORM_CHANNEL, TELEGRAM_CHANNEL, TICKET_TRANSFER_CHANNEL, OFFLINE_SELL_CHANNEL
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
# Matched longest-first so a future 'spree_x' can never be swallowed by 'spree'.
|
|
22
|
+
SORTED_CHANNEL_PREFIXES = ALLOWED_CHANNEL_PREFIXES.sort_by { |prefix| -prefix.length }.freeze
|
|
23
|
+
|
|
24
|
+
# Bit values are PERSISTED in vendor preferences and are a permanent contract.
|
|
25
|
+
# Never renumber, reorder, or reuse a bit. To retire a channel, leave its bit
|
|
26
|
+
# burned and take the next free power of two.
|
|
27
|
+
#
|
|
28
|
+
# Deliberately literal, never derived from ALLOWED_CHANNEL_PREFIXES.index —
|
|
29
|
+
# reordering that array would silently remap every vendor's saved setting.
|
|
30
|
+
CHANNEL_ALERT_BITS = {
|
|
31
|
+
SPREE_CHANNEL => 1, # 1 << 0
|
|
32
|
+
GOOGLE_FORM_CHANNEL => 2, # 1 << 1
|
|
33
|
+
TELEGRAM_CHANNEL => 4, # 1 << 2
|
|
34
|
+
TICKET_TRANSFER_CHANNEL => 8, # 1 << 3
|
|
35
|
+
OFFLINE_SELL_CHANNEL => 16 # 1 << 4
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
# Every bit currently in use, for masking untrusted input down to known channels.
|
|
39
|
+
KNOWN_CHANNEL_ALERT_BITS = CHANNEL_ALERT_BITS.values.reduce(:|).freeze
|
|
40
|
+
|
|
41
|
+
# Only online orders alert by default. google_form & ticket_transfer set the state column
|
|
42
|
+
# directly instead of firing the state machine, so they have never sent an alert; this
|
|
43
|
+
# default therefore encodes today's real behaviour rather than changing it.
|
|
44
|
+
DEFAULT_TELEGRAM_ALERT_CHANNELS = CHANNEL_ALERT_BITS[SPREE_CHANNEL]
|
|
45
|
+
|
|
46
|
+
included do
|
|
47
|
+
validate :validate_channel_prefix, if: :channel_changed?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The stored channel may carry a suffix — the importer writes
|
|
51
|
+
# "google_form-<import_order_id>-<slug>" — so the prefix, not the whole
|
|
52
|
+
# string, is the channel's identity. Returns nil for an unrecognized channel.
|
|
53
|
+
def channel_key
|
|
54
|
+
SORTED_CHANNEL_PREFIXES.find { |prefix| channel&.start_with?(prefix) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Order was created by an operator through the back-office Sell Ticket console.
|
|
58
|
+
def offline_sell?
|
|
59
|
+
channel_key == OFFLINE_SELL_CHANNEL
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def validate_channel_prefix
|
|
65
|
+
return if channel_key.present?
|
|
66
|
+
|
|
67
|
+
errors.add(:channel, "must start with one of the following: #{ALLOWED_CHANNEL_PREFIXES.join(', ')}")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Make sure to put this concern below other concern or methods that generating additional order info like guests, seat number, etc.
|
|
2
2
|
# This will ensure that when each notification is sent with neccessary data.
|
|
3
3
|
module SpreeCmCommissioner
|
|
4
|
-
module OrderStateMachine
|
|
4
|
+
module OrderStateMachine # rubocop:disable Metrics/ModuleLength
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
6
|
|
|
7
7
|
included do # rubocop:disable Metrics/BlockLength
|
|
@@ -271,15 +271,30 @@ module SpreeCmCommissioner
|
|
|
271
271
|
return unless telegram_alert_enabled?
|
|
272
272
|
|
|
273
273
|
vendor_list.each do |vendor|
|
|
274
|
-
|
|
274
|
+
# `next`, not `return`: the mask is per-vendor, so one opted-out vendor on a
|
|
275
|
+
# multi-vendor order must not suppress the others.
|
|
276
|
+
next unless vendor.alerts_telegram_for_channel?(channel_key)
|
|
277
|
+
|
|
275
278
|
chat_id = vendor.preferred_telegram_chat_id
|
|
276
|
-
if chat_id.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
279
|
+
next if chat_id.blank?
|
|
280
|
+
|
|
281
|
+
factory = OrderTelegramMessageFactory.new(title: vendor_alert_title(vendor), order: self, vendor: vendor)
|
|
282
|
+
TelegramNotificationSenderJob.perform_later(chat_id: chat_id, message: factory.message, parse_mode: factory.parse_mode)
|
|
280
283
|
end
|
|
281
284
|
end
|
|
282
285
|
|
|
286
|
+
# White-label vendors announce their own brand; platform vendors stay BookMe+.
|
|
287
|
+
#
|
|
288
|
+
# vendor.name is operator-supplied data interpolated into an HTML parse_mode message
|
|
289
|
+
# (TelegramMessageFactory#header wraps the title in <b>), so it MUST be escaped —
|
|
290
|
+
# Telegram rejects unescaped &, < and > with "400 can't parse entities", and
|
|
291
|
+
# TelegramNotificationSender swallows that error, so the alert would vanish silently.
|
|
292
|
+
def vendor_alert_title(vendor)
|
|
293
|
+
return '🎫 --- [NEW ORDER FROM BOOKME+] ---' unless vendor.tenant_present?
|
|
294
|
+
|
|
295
|
+
"🎫 --- [NEW ORDER FROM #{CGI.escapeHTML(vendor.name.to_s)}] ---"
|
|
296
|
+
end
|
|
297
|
+
|
|
283
298
|
def notify_order_complete_app_notification_to_user
|
|
284
299
|
return if products.all?(&:vote_package?) # disable app push notification for vote package orders
|
|
285
300
|
|
|
@@ -5,11 +5,11 @@ module SpreeCmCommissioner
|
|
|
5
5
|
included do
|
|
6
6
|
has_one :ticket_transfer, class_name: 'SpreeCmCommissioner::TicketTransfer'
|
|
7
7
|
|
|
8
|
-
scope :not_ticket_transfer, -> { where.not(channel: SpreeCmCommissioner::
|
|
8
|
+
scope :not_ticket_transfer, -> { where.not(channel: SpreeCmCommissioner::OrderChannelBitwise::TICKET_TRANSFER_CHANNEL) }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def ticket_transfer?
|
|
12
|
-
channel == SpreeCmCommissioner::
|
|
12
|
+
channel == SpreeCmCommissioner::OrderChannelBitwise::TICKET_TRANSFER_CHANNEL
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# Completes an accepted ticket transfer for this order. Called from
|
|
@@ -8,6 +8,25 @@ module SpreeCmCommissioner
|
|
|
8
8
|
preference :telegram_chat_id, :string
|
|
9
9
|
preference :telegram_chat_name, :string
|
|
10
10
|
preference :telegram_chat_type, :string
|
|
11
|
+
preference :telegram_alert_channels, :integer, default: 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Should this vendor's Telegram group be alerted for an order on this channel?
|
|
15
|
+
# An unmapped channel alerts — a stray message is a better failure than silence.
|
|
16
|
+
def alerts_telegram_for_channel?(channel_key)
|
|
17
|
+
bit = SpreeCmCommissioner::OrderChannelBitwise::CHANNEL_ALERT_BITS[channel_key]
|
|
18
|
+
return true if bit.nil?
|
|
19
|
+
|
|
20
|
+
telegram_alert_channels_mask.anybits?(bit)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Spree's getter is `preferences.fetch(name) { default }`, which falls back only when the
|
|
24
|
+
# key is ABSENT. A key present with a nil value returns nil, and nil.to_i == 0 would mute
|
|
25
|
+
# EVERY channel silently. Fall back to the default instead; a deliberate 0 survives
|
|
26
|
+
# because 0 is truthy in Ruby.
|
|
27
|
+
def telegram_alert_channels_mask
|
|
28
|
+
(preferred_telegram_alert_channels ||
|
|
29
|
+
SpreeCmCommissioner::OrderChannelBitwise::DEFAULT_TELEGRAM_ALERT_CHANNELS).to_i
|
|
11
30
|
end
|
|
12
31
|
end
|
|
13
32
|
end
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
module OrderDecorator
|
|
3
|
-
SPREE_CHANNEL = 'spree'.freeze
|
|
4
|
-
GOOGLE_FORM_CHANNEL = 'google_form'.freeze
|
|
5
|
-
TELEGRAM_CHANNEL = 'telegram'.freeze
|
|
6
|
-
TICKET_TRANSFER_CHANNEL = 'ticket_transfer'.freeze
|
|
7
|
-
ALLOWED_CHANNEL_PREFIXES = [SPREE_CHANNEL, GOOGLE_FORM_CHANNEL, TELEGRAM_CHANNEL, TICKET_TRANSFER_CHANNEL].freeze
|
|
8
|
-
|
|
9
3
|
def self.prepended(base) # rubocop:disable Metrics/MethodLength
|
|
4
|
+
base.include SpreeCmCommissioner::OrderChannelBitwise
|
|
10
5
|
base.include SpreeCmCommissioner::StoreMetadata
|
|
11
6
|
base.include SpreeCmCommissioner::PhoneNumberSanitizer
|
|
12
7
|
base.include SpreeCmCommissioner::OrderIntegration
|
|
@@ -27,7 +22,6 @@ module SpreeCmCommissioner
|
|
|
27
22
|
base.store_public_metadata :order_options, :array, default: []
|
|
28
23
|
|
|
29
24
|
base.validates :promo_total, base::MONEY_VALIDATION
|
|
30
|
-
base.validate :validate_channel_prefix, if: :channel_changed?
|
|
31
25
|
base.validates :phone_number, presence: true, if: :require_phone_number
|
|
32
26
|
|
|
33
27
|
base.has_one :invoice, dependent: :destroy, class_name: 'SpreeCmCommissioner::Invoice'
|
|
@@ -363,12 +357,6 @@ module SpreeCmCommissioner
|
|
|
363
357
|
self.ship_address ||= customer.ship_address.try(:clone)
|
|
364
358
|
end
|
|
365
359
|
|
|
366
|
-
def validate_channel_prefix
|
|
367
|
-
return if ALLOWED_CHANNEL_PREFIXES.any? { |prefix| channel&.start_with?(prefix) }
|
|
368
|
-
|
|
369
|
-
errors.add(:channel, "must start with one of the following: #{ALLOWED_CHANNEL_PREFIXES.join(', ')}")
|
|
370
|
-
end
|
|
371
|
-
|
|
372
360
|
def set_tenant_id
|
|
373
361
|
self.tenant_id = MultiTenant.current_tenant_id
|
|
374
362
|
end
|
|
@@ -70,7 +70,12 @@ module SpreeCmCommissioner
|
|
|
70
70
|
'/api/v2/storefront/menus/:id*',
|
|
71
71
|
'/api/v2/storefront/homepage_data*'
|
|
72
72
|
],
|
|
73
|
-
'Spree::Taxon' => [
|
|
73
|
+
'Spree::Taxon' => [
|
|
74
|
+
'/api/v2/tenant/taxons/:id*',
|
|
75
|
+
'/api/v2/tenant/taxons/:permalink*',
|
|
76
|
+
'/api/v2/storefront/taxons/:id*',
|
|
77
|
+
'/api/v2/storefront/taxons/:permalink*'
|
|
78
|
+
],
|
|
74
79
|
'Spree::Product' => [
|
|
75
80
|
'/api/v2/tenant/products/:id*',
|
|
76
81
|
'/api/v2/tenant/products/:slug*',
|
|
@@ -3,7 +3,9 @@ module SpreeCmCommissioner
|
|
|
3
3
|
class Create
|
|
4
4
|
prepend ::Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
|
-
def call(trip_id:, from_date:, to_date:, user: nil, quantity: 1
|
|
6
|
+
def call(trip_id:, from_date:, to_date:, user: nil, quantity: 1, created_by: nil, # rubocop:disable Metrics/ParameterLists
|
|
7
|
+
channel: SpreeCmCommissioner::OrderChannelBitwise::SPREE_CHANNEL
|
|
8
|
+
)
|
|
7
9
|
validate!(trip_id: trip_id, from_date: from_date, to_date: to_date, quantity: quantity)
|
|
8
10
|
|
|
9
11
|
quantity = quantity.to_i
|
|
@@ -13,7 +15,7 @@ module SpreeCmCommissioner
|
|
|
13
15
|
variant = trip.product.variants.first
|
|
14
16
|
raise ArgumentError, 'No variant found for trip' if variant.blank?
|
|
15
17
|
|
|
16
|
-
order = Spree::Order.new(state: 'cart', use_billing: true, user: user)
|
|
18
|
+
order = Spree::Order.new(state: 'cart', use_billing: true, user: user, created_by: created_by, channel: channel)
|
|
17
19
|
|
|
18
20
|
metadata = {
|
|
19
21
|
trip_id: trip.id,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class Show
|
|
3
|
+
class Clone
|
|
4
|
+
prepend ::Spree::ServiceModule::Base
|
|
5
|
+
|
|
6
|
+
def call(source_season:, vendor:, attributes: {})
|
|
7
|
+
raise ArgumentError, 'source_season must be a season' unless source_season.season?
|
|
8
|
+
|
|
9
|
+
ActiveRecord::Base.transaction do
|
|
10
|
+
new_show = clone_show(source_season.show, vendor, attributes)
|
|
11
|
+
new_season = clone_season(new_show, vendor, source_season, attributes)
|
|
12
|
+
clone_contestants(source_season, new_season)
|
|
13
|
+
clone_show_people_assignments(source_season, new_season)
|
|
14
|
+
clone_episodes(source_season, new_season, vendor)
|
|
15
|
+
clone_vote_packages(source_season, new_season)
|
|
16
|
+
|
|
17
|
+
success(show: new_show, season: new_season)
|
|
18
|
+
end
|
|
19
|
+
rescue StandardError => e
|
|
20
|
+
CmAppLogger.error(
|
|
21
|
+
label: 'SpreeCmCommissioner::Show::Clone#call',
|
|
22
|
+
data: { error_class: e.class.name, error_message: e.message }
|
|
23
|
+
)
|
|
24
|
+
failure(nil, e.message)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def clone_show(source_show, vendor, attributes)
|
|
30
|
+
new_show = SpreeCmCommissioner::Show.new(
|
|
31
|
+
name: attributes[:name].presence || "Copy of #{source_show.name}",
|
|
32
|
+
vendor: vendor,
|
|
33
|
+
show_type: source_show.show_type,
|
|
34
|
+
voting_config: source_show.voting_config,
|
|
35
|
+
fraud_config: source_show.fraud_config
|
|
36
|
+
)
|
|
37
|
+
new_show.save!
|
|
38
|
+
new_show.vendors << vendor
|
|
39
|
+
new_show
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def clone_season(new_show, vendor, source_season, attributes)
|
|
43
|
+
new_season = new_show.build_next_season(
|
|
44
|
+
name: source_season.name,
|
|
45
|
+
season_number: source_season.season_number,
|
|
46
|
+
season_year: source_season.season_year
|
|
47
|
+
)
|
|
48
|
+
new_season.vendor = vendor
|
|
49
|
+
new_season.show_type = source_season.show_type
|
|
50
|
+
new_season.voting_config = source_season.voting_config
|
|
51
|
+
new_season.fraud_config = source_season.fraud_config
|
|
52
|
+
new_season.voting_credit_scope = attributes[:voting_credit_scope].presence || source_season.voting_credit_scope
|
|
53
|
+
new_season.save!
|
|
54
|
+
new_season.vendors << vendor
|
|
55
|
+
new_season
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def clone_contestants(source_season, new_season)
|
|
59
|
+
source_season.show_contestants.ordered.each do |contestant|
|
|
60
|
+
cloned = contestant.dup
|
|
61
|
+
cloned.show_contestant_images = []
|
|
62
|
+
cloned.show_contestant_videos = []
|
|
63
|
+
cloned.show_id = new_season.id
|
|
64
|
+
cloned.status = :active
|
|
65
|
+
cloned.eliminated_at = nil
|
|
66
|
+
cloned.save!
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def clone_show_people_assignments(source_season, new_season)
|
|
71
|
+
source_season.show_people_assignments.order(:position).each do |assignment|
|
|
72
|
+
cloned = assignment.dup
|
|
73
|
+
cloned.show_id = new_season.id
|
|
74
|
+
cloned.save!
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def clone_episodes(source_season, new_season, vendor)
|
|
79
|
+
source_season.episodes.where(product_type: :show_episode).ordered_by_episode_number.each do |episode|
|
|
80
|
+
cloned = duplicate_episode(episode)
|
|
81
|
+
cloned.event_id = new_season.id
|
|
82
|
+
cloned.status = 'draft'
|
|
83
|
+
cloned.scheduled_at = nil
|
|
84
|
+
cloned.ends_at = 1.day.from_now
|
|
85
|
+
cloned.aired_at = nil
|
|
86
|
+
cloned.save!
|
|
87
|
+
|
|
88
|
+
clone_vote_variant(episode, cloned)
|
|
89
|
+
clone_voting_sessions(episode, cloned, new_season, vendor)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Carries over only the session name, status, session_type and time window
|
|
94
|
+
# (opens_at/closes_at); everything else is left at defaults and a fresh session_key is
|
|
95
|
+
# generated by the model.
|
|
96
|
+
def clone_voting_sessions(source_episode, cloned_episode, new_season, vendor)
|
|
97
|
+
source_episode.voting_sessions.order(:position).each do |session|
|
|
98
|
+
SpreeCmCommissioner::VotingSession.create!(
|
|
99
|
+
episode: cloned_episode,
|
|
100
|
+
show_id: new_season.id,
|
|
101
|
+
vendor: vendor,
|
|
102
|
+
name: session.name,
|
|
103
|
+
status: session.status,
|
|
104
|
+
session_type: session.session_type,
|
|
105
|
+
opens_at: session.opens_at,
|
|
106
|
+
closes_at: session.closes_at
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def clone_vote_packages(source_season, new_season)
|
|
112
|
+
source_season.vote_packages.each do |vote_package|
|
|
113
|
+
cloned = duplicate_episode(vote_package)
|
|
114
|
+
cloned.event_id = new_season.id
|
|
115
|
+
cloned.taxons = [new_season]
|
|
116
|
+
cloned.status = 'draft'
|
|
117
|
+
cloned.save!
|
|
118
|
+
|
|
119
|
+
clone_vote_variant(vote_package, cloned)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def duplicate_episode(original)
|
|
124
|
+
original.dup.tap do |cloned|
|
|
125
|
+
cloned.variants = []
|
|
126
|
+
cloned.stores = original.stores
|
|
127
|
+
cloned.price = original.price
|
|
128
|
+
cloned.compare_at_price = original.compare_at_price
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def clone_vote_variant(original_episode, cloned_episode)
|
|
133
|
+
original_variant = original_episode.vote_variant
|
|
134
|
+
return unless original_variant
|
|
135
|
+
|
|
136
|
+
cloned_episode.variants.create!(
|
|
137
|
+
price: original_variant.price,
|
|
138
|
+
compare_at_price: original_variant.compare_at_price,
|
|
139
|
+
option_value_ids: original_variant.option_value_ids,
|
|
140
|
+
sku: ''
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -71,7 +71,7 @@ module SpreeCmCommissioner
|
|
|
71
71
|
order = Spree::Order.create!(
|
|
72
72
|
user_id: buyer.id,
|
|
73
73
|
store_id: Spree::Store.default.id,
|
|
74
|
-
channel: SpreeCmCommissioner::
|
|
74
|
+
channel: SpreeCmCommissioner::OrderChannelBitwise::TICKET_TRANSFER_CHANNEL,
|
|
75
75
|
email: buyer.email,
|
|
76
76
|
phone_number: buyer.phone_number,
|
|
77
77
|
currency: ticket_transfer.currency,
|
|
@@ -16,19 +16,25 @@ module SpreeCmCommissioner
|
|
|
16
16
|
class Create
|
|
17
17
|
prepend ::Spree::ServiceModule::Base
|
|
18
18
|
|
|
19
|
-
def call(outbound_date:, inbound_date:, outbound_legs:, inbound_legs: [], user: nil,
|
|
19
|
+
def call(outbound_date:, inbound_date:, outbound_legs:, inbound_legs: [], user: nil, created_by: nil, # rubocop:disable Metrics/ParameterLists
|
|
20
|
+
include_return: false, channel: SpreeCmCommissioner::OrderChannelBitwise::SPREE_CHANNEL
|
|
21
|
+
)
|
|
20
22
|
return failure(nil, 'Outbound legs are missing') if outbound_legs.blank?
|
|
21
23
|
|
|
22
24
|
begin
|
|
23
|
-
order = create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user, include_return
|
|
25
|
+
order = create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user, include_return,
|
|
26
|
+
created_by: created_by, channel: channel
|
|
27
|
+
)
|
|
24
28
|
success(order: order)
|
|
25
29
|
rescue StandardError => e
|
|
26
30
|
failure(nil, e.message)
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
|
|
30
|
-
def create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user, include_return
|
|
31
|
-
|
|
34
|
+
def create_order!(outbound_date, inbound_date, outbound_legs, inbound_legs, user, include_return, # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/ParameterLists
|
|
35
|
+
created_by: nil, channel: SpreeCmCommissioner::OrderChannelBitwise::SPREE_CHANNEL
|
|
36
|
+
)
|
|
37
|
+
order = Spree::Order.new(state: 'cart', use_billing: true, user: user, created_by: created_by, channel: channel)
|
|
32
38
|
|
|
33
39
|
all_legs = outbound_legs + inbound_legs
|
|
34
40
|
preload_trips_and_stops!(all_legs)
|
|
@@ -92,38 +92,23 @@ 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
|
-
#
|
|
97
|
-
# stamped
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
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.
|
|
95
|
+
# the queue (left_at set). Both matter because #stamp_doc's position is permanent once
|
|
96
|
+
# assigned — e.g. if the highest stamped position is 4200 but this only counts 3714, every
|
|
97
|
+
# guest stamped after 3714 shows ~486 phantom people "ahead of them" that never resolve.
|
|
98
|
+
# Summed as two single-field counts rather than one OR query so each stays index-free (no
|
|
99
|
+
# composite index needed), and #waiting_query / WaitingGuestsCaller#eligible_guests_in never
|
|
100
|
+
# need to know about `left_at`. Recomputed fresh every run rather than incremented, so a
|
|
101
|
+
# crashed run can't leave it permanently understated.
|
|
114
102
|
def exited_count(path)
|
|
115
|
-
allowed_count(path) +
|
|
103
|
+
allowed_count(path) + left_count(path)
|
|
116
104
|
end
|
|
117
105
|
|
|
118
106
|
def allowed_count(path)
|
|
119
107
|
firestore.col(path).where('allow_to_enter_room_at', '!=', nil).aggregate_query.add_count.get.first.get.to_i
|
|
120
108
|
end
|
|
121
109
|
|
|
122
|
-
def
|
|
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
|
|
110
|
+
def left_count(path)
|
|
111
|
+
firestore.col(path).where('left_at', '!=', nil).aggregate_query.add_count.get.first.get.to_i
|
|
127
112
|
end
|
|
128
113
|
|
|
129
114
|
# merge: true so total_exited_queue coexists with the fields WaitingGuestsCaller publishes to
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
|
|
2
|
+
|
|
3
|
+
<div class="card mb-4">
|
|
4
|
+
<div class="card-body">
|
|
5
|
+
<%= f.field_container :name do %>
|
|
6
|
+
<%= f.label :name, Spree.t(:name) %>
|
|
7
|
+
<%= f.text_field :name, class: 'form-control', required: true %>
|
|
8
|
+
<%= f.error_message_on :name %>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<%= f.field_container :description do %>
|
|
12
|
+
<%= f.label :description, Spree.t(:description) %>
|
|
13
|
+
<%= f.text_area :description, class: 'form-control', rows: 3 %>
|
|
14
|
+
<%= f.error_message_on :description %>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
17
|
+
<%= f.field_container :data_source do %>
|
|
18
|
+
<%= f.label :data_source, 'Data source' %>
|
|
19
|
+
<%= f.select :data_source, Blazer.data_sources.keys, {}, class: 'fullwidth select2' %>
|
|
20
|
+
<%= f.error_message_on :data_source %>
|
|
21
|
+
<% end %>
|
|
22
|
+
|
|
23
|
+
<div class="form-check">
|
|
24
|
+
<%= f.check_box :preset, class: 'form-check-input' %>
|
|
25
|
+
<%= f.label :preset, Spree.t(:preset), class: 'form-check-label' %>
|
|
26
|
+
<small class="form-text text-muted d-block">
|
|
27
|
+
Preset reports are generated automatically for all new organizers and vendors once created.
|
|
28
|
+
</small>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<% if @object.persisted? %>
|
|
32
|
+
<div class="form-group mt-3">
|
|
33
|
+
<%= link_to 'View / edit SQL in Blazer', blazer.edit_query_path(@object), class: 'btn btn-outline-secondary' %>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= content_for :page_title do %>
|
|
2
|
+
<%= link_to 'Blazer Queries', admin_blazer_queries_path %> / <%= @object.name %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<%= form_with model: @object, url: { action: 'update' }, method: :patch, scope: 'blazer_query', local: true do |f| %>
|
|
6
|
+
<%= render 'form', f: f %>
|
|
7
|
+
|
|
8
|
+
<fieldset>
|
|
9
|
+
<%= render partial: 'spree/admin/shared/edit_resource_links' %>
|
|
10
|
+
</fieldset>
|
|
11
|
+
<% end %>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<% content_for :page_title do %>
|
|
2
|
+
<%= link_to 'Blazer Queries', spree.admin_blazer_queries_path %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<% content_for :page_actions do %>
|
|
6
|
+
<%= button_link_to 'New Query', new_admin_blazer_query_path, class: "btn-outline-primary", icon: 'add.svg' %>
|
|
7
|
+
<% end if can? :create, Blazer::Query %>
|
|
8
|
+
|
|
9
|
+
<% content_for :table_filter do %>
|
|
10
|
+
<div data-hook="admin_blazer_queries_index_search">
|
|
11
|
+
<%= search_form_for [:admin, @search], url: spree.admin_blazer_queries_path do |f| %>
|
|
12
|
+
<div class="row">
|
|
13
|
+
<div class="col-md-6">
|
|
14
|
+
<div class="form-group">
|
|
15
|
+
<%= label_tag :q_name_cont, Spree.t(:name) %>
|
|
16
|
+
<%= f.text_field :name_cont, tabindex: 1, class: "form-control js-quick-search-target" %>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<div data-hook="admin_blazer_queries_index_search_buttons" class="form-actions">
|
|
21
|
+
<%= button Spree.t(:filter_results), 'search' %>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
</div>
|
|
25
|
+
<% end %>
|
|
26
|
+
|
|
27
|
+
<% if @collection.any? %>
|
|
28
|
+
<div class="table-responsive border rounded bg-white mb-3">
|
|
29
|
+
<table class="table" id="listing_blazer_queries" data-hook>
|
|
30
|
+
<thead class="text-muted">
|
|
31
|
+
<tr data-hook="admin_blazer_queries_index_headers">
|
|
32
|
+
<th><%= Spree.t(:name) %></th>
|
|
33
|
+
<th>Data source</th>
|
|
34
|
+
<th><%= sort_link @search, :created_at, Spree.t(:created_at) %></th>
|
|
35
|
+
<th><%= sort_link @search, :updated_at, Spree.t(:updated_at) %></th>
|
|
36
|
+
<th>Created by</th>
|
|
37
|
+
<th data-hook="admin_blazer_queries_index_header_actions" class="actions"></th>
|
|
38
|
+
</tr>
|
|
39
|
+
</thead>
|
|
40
|
+
<tbody>
|
|
41
|
+
<% @collection.each do |blazer_query| %>
|
|
42
|
+
<tr id="<%= spree_dom_id blazer_query %>" data-hook="admin_blazer_queries_index_rows">
|
|
43
|
+
<td>
|
|
44
|
+
<% if can?(:edit, blazer_query) %>
|
|
45
|
+
<%= link_to blazer_query.name, edit_admin_blazer_query_path(blazer_query) %>
|
|
46
|
+
<% else %>
|
|
47
|
+
<%= blazer_query.name %>
|
|
48
|
+
<% end %>
|
|
49
|
+
</td>
|
|
50
|
+
<td><%= blazer_query.data_source %></td>
|
|
51
|
+
<td><%= l(blazer_query.created_at) %></td>
|
|
52
|
+
<td><%= l(blazer_query.updated_at) %></td>
|
|
53
|
+
<td><%= blazer_query.creator&.full_name || content_tag(:span, '—', class: 'text-muted') %></td>
|
|
54
|
+
<td data-hook="admin_blazer_queries_index_row_actions" class="actions">
|
|
55
|
+
<span class="d-flex justify-content-end">
|
|
56
|
+
<%= link_to_with_icon 'link.svg', 'View in Blazer', blazer.query_path(blazer_query), no_text: true, class: 'btn btn-outline-secondary' %>
|
|
57
|
+
<%= link_to_edit blazer_query, url: edit_admin_blazer_query_path(blazer_query), no_text: true if can?(:edit, blazer_query) %>
|
|
58
|
+
<%= link_to_delete blazer_query, url: admin_blazer_query_path(blazer_query), no_text: true if can?(:delete, blazer_query) %>
|
|
59
|
+
</span>
|
|
60
|
+
</td>
|
|
61
|
+
</tr>
|
|
62
|
+
<% end %>
|
|
63
|
+
</tbody>
|
|
64
|
+
</table>
|
|
65
|
+
</div>
|
|
66
|
+
<% else %>
|
|
67
|
+
<div class="alert alert-info alpha twelve columns no-objects-found">
|
|
68
|
+
<%= Spree.t(:no_resource_found, resource: plural_resource_name(Blazer::Query)) %>
|
|
69
|
+
</div>
|
|
70
|
+
<% end %>
|
|
71
|
+
|
|
72
|
+
<%= render partial: 'spree/admin/shared/index_table_options', locals: { collection: @collection } %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= content_for :page_title do %>
|
|
2
|
+
<%= link_to 'Blazer Queries', admin_blazer_queries_path %> / <%= Spree.t(:new) %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<%= form_with model: @object, url: { action: 'create' }, scope: 'blazer_query', local: true do |f| %>
|
|
6
|
+
<%= render 'form', f: f %>
|
|
7
|
+
|
|
8
|
+
<fieldset>
|
|
9
|
+
<%= render partial: 'spree/admin/shared/new_resource_links' %>
|
|
10
|
+
</fieldset>
|
|
11
|
+
<% end %>
|
|
@@ -16,6 +16,21 @@
|
|
|
16
16
|
<%= f.label :preferred_telegram_chat_id, Spree.t(:telegram_chat_id) %>
|
|
17
17
|
<%= f.text_field :preferred_telegram_chat_id, class: 'form-control', disabled: true %>
|
|
18
18
|
<% end %>
|
|
19
|
+
<%= f.field_container :preferred_telegram_alert_channels do %>
|
|
20
|
+
<%= f.label :preferred_telegram_alert_channels, Spree.t(:telegram_alert_channels) %>
|
|
21
|
+
<%# Without this, unticking every box submits no key at all and the mask would keep its old value. %>
|
|
22
|
+
<%= hidden_field_tag 'vendor[telegram_alert_channels][]', 0 %>
|
|
23
|
+
<% SpreeCmCommissioner::OrderChannelBitwise::CHANNEL_ALERT_BITS.each do |channel, bit| %>
|
|
24
|
+
<div class="form-check">
|
|
25
|
+
<%= check_box_tag 'vendor[telegram_alert_channels][]', bit,
|
|
26
|
+
@vendor.telegram_alert_channels_mask.anybits?(bit),
|
|
27
|
+
id: "telegram_alert_channel_#{channel}", class: 'form-check-input' %>
|
|
28
|
+
<%= label_tag "telegram_alert_channel_#{channel}",
|
|
29
|
+
I18n.t("views.organizer.order.channel_values.#{channel}", default: channel.humanize),
|
|
30
|
+
class: 'form-check-label' %>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% end %>
|
|
19
34
|
<small class="form-text text-muted">
|
|
20
35
|
<ul>
|
|
21
36
|
<% allow_extensions = SpreeCmCommissioner::VectorIcon::ACCEPTED_EXTENSIONS.to_sentence %>
|
data/config/locales/en.yml
CHANGED
|
@@ -287,9 +287,14 @@ en:
|
|
|
287
287
|
reversion_error: "Cannot eliminate here yet. This contestant appears in a later session."
|
|
288
288
|
is_open_dated: "Is Open Dated Trip?"
|
|
289
289
|
description: "Description"
|
|
290
|
+
preset: "Preset"
|
|
290
291
|
enable_telegram_alert: "Enable Telegram Alert"
|
|
291
292
|
enable_send_receipt: "Enable Send Receipt"
|
|
292
293
|
enable_social_sharing_email: "Enable Social Sharing Email"
|
|
294
|
+
telegram_alert_channels: "Telegram Alert Channels"
|
|
295
|
+
telegram_chat_name: "Telegram Chat Name"
|
|
296
|
+
telegram_chat_type: "Telegram Chat Type"
|
|
297
|
+
telegram_chat_id: "Telegram Chat ID"
|
|
293
298
|
registered_by:
|
|
294
299
|
system_registered: "System Registered"
|
|
295
300
|
self_registered: "Self Registered"
|
data/config/routes.rb
CHANGED
|
@@ -394,6 +394,7 @@ Spree::Core::Engine.add_routes do
|
|
|
394
394
|
end
|
|
395
395
|
resources :event_blazer_queries
|
|
396
396
|
resources :trip_blazer_queries
|
|
397
|
+
resources :blazer_queries, except: [:show]
|
|
397
398
|
|
|
398
399
|
resources :menus do
|
|
399
400
|
resource :sprite_import, controller: 'sprite_import', only: %i[show new create edit update destroy]
|
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.8
|
|
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-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: spree
|
|
@@ -837,6 +837,7 @@ files:
|
|
|
837
837
|
- app/controllers/spree/admin/account_deletions_controller.rb
|
|
838
838
|
- app/controllers/spree/admin/base_controller_decorator.rb
|
|
839
839
|
- app/controllers/spree/admin/base_import_orders_controller.rb
|
|
840
|
+
- app/controllers/spree/admin/blazer_queries_controller.rb
|
|
840
841
|
- app/controllers/spree/admin/calendars/base_controller.rb
|
|
841
842
|
- app/controllers/spree/admin/calendars/orders_controller.rb
|
|
842
843
|
- app/controllers/spree/admin/classifications_controller.rb
|
|
@@ -1472,6 +1473,7 @@ files:
|
|
|
1472
1473
|
- app/mailers/spree_cm_commissioner/pin_code_mailer.rb
|
|
1473
1474
|
- app/mailers/spree_cm_commissioner/team_invite_mailer.rb
|
|
1474
1475
|
- app/models/.gitkeep
|
|
1476
|
+
- app/models/blazer/query_decorator.rb
|
|
1475
1477
|
- app/models/concerns/spree_cm_commissioner/event_metadata.rb
|
|
1476
1478
|
- app/models/concerns/spree_cm_commissioner/firestore_connection.rb
|
|
1477
1479
|
- app/models/concerns/spree_cm_commissioner/guest_data_fill_stage_concern.rb
|
|
@@ -1490,6 +1492,7 @@ files:
|
|
|
1490
1492
|
- app/models/concerns/spree_cm_commissioner/metafield.rb
|
|
1491
1493
|
- app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb
|
|
1492
1494
|
- app/models/concerns/spree_cm_commissioner/option_value_attr_type.rb
|
|
1495
|
+
- app/models/concerns/spree_cm_commissioner/order_channel_bitwise.rb
|
|
1493
1496
|
- app/models/concerns/spree_cm_commissioner/order_holdable.rb
|
|
1494
1497
|
- app/models/concerns/spree_cm_commissioner/order_integration.rb
|
|
1495
1498
|
- app/models/concerns/spree_cm_commissioner/order_qr_codeable.rb
|
|
@@ -1898,6 +1901,7 @@ files:
|
|
|
1898
1901
|
- app/overrides/spree/admin/shared/_product_tabs/video_on_demand.html.erb.deface
|
|
1899
1902
|
- app/overrides/spree/admin/shared/_store_switcher/store_logo.html.erb.deface
|
|
1900
1903
|
- app/overrides/spree/admin/shared/_version/deploy_tag_version_badge.html.erb.deface
|
|
1904
|
+
- app/overrides/spree/admin/shared/sub_menu/_configuration/blazer_queries_tab.html.erb.deface
|
|
1901
1905
|
- app/overrides/spree/admin/shared/sub_menu/_configuration/locations_tab.html.erb.deface
|
|
1902
1906
|
- app/overrides/spree/admin/shared/sub_menu/_configuration/menu_items.html.erb.deface
|
|
1903
1907
|
- app/overrides/spree/admin/shared/sub_menu/_integrations/external_integrations.html.erb.deface
|
|
@@ -2386,6 +2390,7 @@ files:
|
|
|
2386
2390
|
- app/services/spree_cm_commissioner/seats/unlock_blocks.rb
|
|
2387
2391
|
- app/services/spree_cm_commissioner/seeds/roles_decorator.rb
|
|
2388
2392
|
- app/services/spree_cm_commissioner/seeds/user_usernames.rb
|
|
2393
|
+
- app/services/spree_cm_commissioner/show/clone.rb
|
|
2389
2394
|
- app/services/spree_cm_commissioner/show_contestants/normalize_video_highlights.rb
|
|
2390
2395
|
- app/services/spree_cm_commissioner/show_persons/contestant_assigner.rb
|
|
2391
2396
|
- app/services/spree_cm_commissioner/signing/sign_data.rb
|
|
@@ -2479,7 +2484,6 @@ files:
|
|
|
2479
2484
|
- app/services/spree_cm_commissioner/webhooks/subscribers/queue_requests_decorator.rb
|
|
2480
2485
|
- app/services/telegram_gateway_adapter/client.rb
|
|
2481
2486
|
- app/views/blazer/queries/_content.html.erb
|
|
2482
|
-
- app/views/blazer/queries/_preset.html.erb
|
|
2483
2487
|
- app/views/blazer/queries/embed/_content.html.erb
|
|
2484
2488
|
- app/views/blazer/queries/embed/_download_button.html.erb
|
|
2485
2489
|
- app/views/blazer/queries/show.html.erb
|
|
@@ -2492,6 +2496,10 @@ files:
|
|
|
2492
2496
|
- app/views/shared/_product_type_field.erb
|
|
2493
2497
|
- app/views/shared/calendar/_order_popover.html.erb
|
|
2494
2498
|
- app/views/shared/calendar/_orders_sidebar.html.erb
|
|
2499
|
+
- app/views/spree/admin/blazer_queries/_form.html.erb
|
|
2500
|
+
- app/views/spree/admin/blazer_queries/edit.html.erb
|
|
2501
|
+
- app/views/spree/admin/blazer_queries/index.html.erb
|
|
2502
|
+
- app/views/spree/admin/blazer_queries/new.html.erb
|
|
2495
2503
|
- app/views/spree/admin/calendars/orders/index.html.erb
|
|
2496
2504
|
- app/views/spree/admin/classifications/edit.html.erb
|
|
2497
2505
|
- app/views/spree/admin/classifications/index.html.erb
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<%= form_for @query do |f| %>
|
|
2
|
-
<div>
|
|
3
|
-
<%= f.check_box :preset, onchange: "this.form.submit()" %>
|
|
4
|
-
<%= f.label :preset, Spree.t(:preset) %>
|
|
5
|
-
</div>
|
|
6
|
-
<small style="margin-bottom: 6px; display: inline-block;">
|
|
7
|
-
Preset reports are generated automatically for all new organizers and vendors once created.
|
|
8
|
-
</small>
|
|
9
|
-
<% end %>
|