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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/blazer/queries_controller_decorator.rb +1 -1
  4. data/app/controllers/spree/admin/blazer_queries_controller.rb +43 -0
  5. data/app/controllers/spree/admin/vendor_authorized_users_controller.rb +15 -0
  6. data/app/factory/spree_cm_commissioner/order_telegram_message_factory.rb +19 -4
  7. data/app/models/blazer/query_decorator.rb +9 -0
  8. data/app/models/concerns/spree_cm_commissioner/order_channel_bitwise.rb +70 -0
  9. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +21 -6
  10. data/app/models/concerns/spree_cm_commissioner/ticket_transferable.rb +2 -2
  11. data/app/models/concerns/spree_cm_commissioner/vendor_preference.rb +19 -0
  12. data/app/models/spree_cm_commissioner/order_decorator.rb +1 -13
  13. data/app/overrides/spree/admin/shared/sub_menu/_configuration/blazer_queries_tab.html.erb.deface +3 -0
  14. data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +6 -1
  15. data/app/services/spree_cm_commissioner/intercity_taxi_order/create.rb +4 -2
  16. data/app/services/spree_cm_commissioner/show/clone.rb +145 -0
  17. data/app/services/spree_cm_commissioner/ticket_transfers/accept.rb +1 -1
  18. data/app/services/spree_cm_commissioner/transit_order/create.rb +10 -4
  19. data/app/services/spree_cm_commissioner/waiting_room/stamp_queue_positions.rb +10 -25
  20. data/app/views/blazer/queries/_content.html.erb +0 -2
  21. data/app/views/spree/admin/blazer_queries/_form.html.erb +37 -0
  22. data/app/views/spree/admin/blazer_queries/edit.html.erb +11 -0
  23. data/app/views/spree/admin/blazer_queries/index.html.erb +72 -0
  24. data/app/views/spree/admin/blazer_queries/new.html.erb +11 -0
  25. data/app/views/spree/admin/vendor_authorized_users/index.html.erb +15 -0
  26. data/config/locales/en.yml +5 -0
  27. data/config/routes.rb +1 -0
  28. data/lib/spree_cm_commissioner/version.rb +1 -1
  29. metadata +11 -3
  30. 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: ed84dcf2382e8d3720366acd62551189adfa4edba9533d8ca5e94b0704942836
4
- data.tar.gz: aa37bf6b43593f7f7ff91d71d1839b1ceeeb8c9271d5db371451f94958399366
3
+ metadata.gz: 742162ce28f156432f76f071c6538e682fecfa8008758acc698d283cb2a3742c
4
+ data.tar.gz: bfafd91f275bc28a16234e3153ee8d83aa5b5040be06acca7850770b2573fe01
5
5
  SHA512:
6
- metadata.gz: a5b89052a82aedeed1144cf9a5892e3d8ec2428c3a4cd26cc5806010c1bbcc3ad353a768e6854c3fdaf69a2ae2fdb5f6f049616b7cac1b2a9d3cd532344708d3
7
- data.tar.gz: 7842ce22211f5e072e308efda5f4f5a0b95625729d18388ec6c3c22406fd76b4e5577daca6d5a9f717fd28990639d3897b3a0d58c1d18bebd2c1959f152b7b80
6
+ metadata.gz: f8c8857a35fe48099949496f9e69c0741ca32a18a05fbc05afafdc1cb5cd7feb3394b3b0158cbb27dea96f585e378f9a4a14c0d2fbac1b8cee6e54c8a51be443
7
+ data.tar.gz: d8e013c7a5172fabbfea21e83ac52d5231351ed380960e1308b458ad1e7b6cd960bb2ca7a6204b82448f3d494c17cb3eb3ebb9a5d704228b901c789dacef09f2
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.12.6)
37
+ spree_cm_commissioner (2.12.8)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -1,7 +1,7 @@
1
1
  module Blazer
2
2
  module QueriesControllerDecorator
3
3
  def query_params
4
- params.require(:query).permit(:name, :description, :statement, :data_source, :preset)
4
+ params.require(:query).permit(:name, :description, :statement, :data_source)
5
5
  end
6
6
  end
7
7
  end
@@ -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
- parts = [origin, destination].compact_blank.join(' → ')
171
- vehicle.present? ? "#{parts} (#{vehicle})" : parts
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
- title = '🎫 --- [NEW ORDER FROM BOOKME+] ---'
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.present?
277
- factory = OrderTelegramMessageFactory.new(title: title, order: self, vendor: vendor)
278
- TelegramNotificationSenderJob.perform_later(chat_id: chat_id, message: factory.message, parse_mode: factory.parse_mode)
279
- end
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::OrderDecorator::TICKET_TRANSFER_CHANNEL) }
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::OrderDecorator::TICKET_TRANSFER_CHANNEL
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
@@ -0,0 +1,3 @@
1
+ <!-- insert_bottom "[data-hook='admin_configurations_sidebar_menu']" -->
2
+
3
+ <%= tab :blazer_queries, match_path: '/blazer_queries', label: 'Blazer Queries' if can? :manage, Blazer::Query %>
@@ -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' => ['/api/v2/storefront/taxons/:id*'],
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::OrderDecorator::TICKET_TRANSFER_CHANNEL,
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, include_return: false) # rubocop:disable Metrics/ParameterLists
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) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/ParameterLists
31
- order = Spree::Order.new(state: 'cart', use_billing: true, user: user)
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 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.
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) + left_without_entry_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 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
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
@@ -58,8 +58,6 @@
58
58
  <% end %>
59
59
  </div>
60
60
 
61
- <%= render partial: 'blazer/queries/preset' %>
62
-
63
61
  <% if flash[:success] %>
64
62
  <div class="alert alert-success">
65
63
  <%= flash[:success] %>
@@ -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 %>
@@ -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]
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.12.6'.freeze
2
+ VERSION = '2.12.8'.freeze
3
3
 
4
4
  module_function
5
5
 
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.6
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-02 00:00:00.000000000 Z
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 %>