kaui 4.0.17 → 4.0.19
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/app/assets/javascripts/kaui/kaui_override.js +5 -62
- data/app/controllers/kaui/subscriptions_controller.rb +218 -24
- data/app/helpers/kaui/subscription_helper.rb +28 -0
- data/app/models/kaui/usage.rb +6 -0
- data/app/views/kaui/bundles/index.html.erb +2 -2
- data/app/views/kaui/components/menu_dropdown/_menu_dropdown.html.erb +9 -15
- data/app/views/kaui/invoices/_multi_functions_bar.html.erb +8 -9
- data/app/views/kaui/payments/_multi_functions_bar.html.erb +8 -9
- data/app/views/kaui/subscriptions/_edit_form.html.erb +137 -3
- data/app/views/kaui/subscriptions/_form.html.erb +137 -3
- data/app/views/kaui/subscriptions/_subscriptions_table.html.erb +50 -4
- data/app/views/kaui/subscriptions/_view_json_modal.html.erb +100 -0
- data/app/views/kaui/subscriptions/edit_quantity.erb +59 -0
- data/app/views/kaui/subscriptions/record_usage.erb +110 -0
- data/config/routes.rb +5 -0
- data/lib/kaui/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f667be5cb34e03c3d1b4da18ef422934d9794643606c998cf1ac84f2e6d99f60
|
|
4
|
+
data.tar.gz: e202ccb38314d766896d3b72235de05c2818b74b985cb8b2717ff4850ab0b4ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18c8742fd7498cab0431aa9758b7f7ebda0e225d7fba76c9dd8a1fa97bd52a8678812b3a6e0397e1d15fc8128e59f451ee455ac7ebdfe8cb13de888a86101501
|
|
7
|
+
data.tar.gz: ee27c185159e1b564e0a0d16322178ddf3fbbce4c78929a0dfd3747c4c198084f1269f3e7d261047b0a2589b5f08d70be3ab07f8d727dfcc969e372929f7395a
|
|
@@ -392,69 +392,12 @@ function isBlank(value) {
|
|
|
392
392
|
// data-id = content of the popover,object id; required
|
|
393
393
|
// title = title of the popover; not required
|
|
394
394
|
// id = (must be {{id}}-popover) used to close popover when the copy image is clicked; if present; if not present a timeout of 5s will apply; not required
|
|
395
|
+
//
|
|
396
|
+
// Deprecated: the underlying Bootstrap 4 popover('destroy') API was removed in
|
|
397
|
+
// Bootstrap 5 and this implementation has been superseded by setObjectIdTooltip().
|
|
398
|
+
// Kept as a thin shim so existing call sites continue to work.
|
|
395
399
|
function setObjectIdPopover(){
|
|
396
|
-
|
|
397
|
-
$(this).popover('destroy');
|
|
398
|
-
$(this).off("shown.bs.popover");
|
|
399
|
-
$(this).data("index", idx);
|
|
400
|
-
|
|
401
|
-
$(this).popover({
|
|
402
|
-
html: true,
|
|
403
|
-
content: function() {
|
|
404
|
-
var template = '<div class="{{id}}-content" >' +
|
|
405
|
-
'{{id}} <i id="{{id}}-copy" class="fa fa-clipboard copy-icon" aria-hidden="true"></i> ' +
|
|
406
|
-
'</div>';
|
|
407
|
-
|
|
408
|
-
var popover_html = Mustache.render( template , { id: $(this).data("id") });
|
|
409
|
-
return popover_html;
|
|
410
|
-
},
|
|
411
|
-
container: 'body',
|
|
412
|
-
trigger: 'hover',
|
|
413
|
-
delay: { "show": 100, "hide": 4000 }
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
$(this).on("show.bs.popover", function(e) {
|
|
417
|
-
var currentPopoverIndex = $(this).data('index');
|
|
418
|
-
$(".object-id-popover").each(function(idx, e){
|
|
419
|
-
var index = $(this).data('index');
|
|
420
|
-
|
|
421
|
-
if (currentPopoverIndex != index) {
|
|
422
|
-
$(this).popover('hide');
|
|
423
|
-
}
|
|
424
|
-
});
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
$(this).on("shown.bs.popover", function(e) {
|
|
428
|
-
var objectId = $(this).data('id');
|
|
429
|
-
var copyIdImg = $("#" + objectId + "-copy");
|
|
430
|
-
|
|
431
|
-
copyIdImg.data("popover",$(this).attr("id"));
|
|
432
|
-
copyIdImg.click(function(e){
|
|
433
|
-
var id = ($(this).attr("id")).replace('-copy','');
|
|
434
|
-
navigator.clipboard.writeText(id);
|
|
435
|
-
ajaxInfoAlert("Id [" + id + "] was copied into the clipboard!", 4000);
|
|
436
|
-
|
|
437
|
-
if (!isBlank(popover)) {
|
|
438
|
-
popover.popover('hide');
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
// close all object id popover on modal show
|
|
448
|
-
$(".modal").on('show.bs.modal',function(e){
|
|
449
|
-
$(".object-id-popover").each(function(idx, e) {
|
|
450
|
-
$(this).popover('destroy');
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
// check if object id must be restored
|
|
455
|
-
$(".modal").on('hide.bs.modal',function(e){
|
|
456
|
-
setObjectIdPopover();
|
|
457
|
-
});
|
|
400
|
+
setObjectIdTooltip();
|
|
458
401
|
}
|
|
459
402
|
|
|
460
403
|
// Custom tooltip function for object IDs
|
|
@@ -14,6 +14,7 @@ module Kaui
|
|
|
14
14
|
|
|
15
15
|
@bundle, plans_details = lookup_bundle_and_plan_details(@subscription, @base_product_name)
|
|
16
16
|
@plans = plans_details.map(&:plan)
|
|
17
|
+
@plan_phases = build_plan_phases_map(plans_details)
|
|
17
18
|
|
|
18
19
|
return unless @plans.empty?
|
|
19
20
|
|
|
@@ -30,6 +31,7 @@ module Kaui
|
|
|
30
31
|
_, plans_details = lookup_bundle_and_plan_details(@subscription)
|
|
31
32
|
# Use a Set to deal with multiple pricelists
|
|
32
33
|
@plans = Set.new.merge(plans_details.map(&:plan))
|
|
34
|
+
@plan_phases = build_plan_phases_map(plans_details)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def create
|
|
@@ -46,15 +48,7 @@ module Kaui
|
|
|
46
48
|
@subscription.plan_name = plan_name
|
|
47
49
|
requested_date = params[:type_change] == 'DATE' ? params[:requested_date].presence : nil
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
override_fixed_price = begin
|
|
51
|
-
plan_details.phases.first.prices.blank?
|
|
52
|
-
rescue StandardError
|
|
53
|
-
false
|
|
54
|
-
end
|
|
55
|
-
override_recurring_price = !override_fixed_price
|
|
56
|
-
phase_type = @bundle.nil? ? plan_details.phases.first.type : @bundle.subscriptions.first.phase_type
|
|
57
|
-
overrides = price_overrides(phase_type, override_fixed_price, override_recurring_price)
|
|
51
|
+
overrides = price_overrides(plan_details)
|
|
58
52
|
@subscription.price_overrides = overrides if overrides.present?
|
|
59
53
|
@subscription.quantity = params[:quantity].to_i if params[:quantity].present? && params[:quantity].to_i.positive?
|
|
60
54
|
|
|
@@ -95,11 +89,9 @@ module Kaui
|
|
|
95
89
|
|
|
96
90
|
input = { planName: plan_name }
|
|
97
91
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
override_recurring_price = !override_fixed_price
|
|
102
|
-
overrides = price_overrides(subscription.phase_type, override_fixed_price, override_recurring_price)
|
|
92
|
+
_, plans_details = lookup_bundle_and_plan_details(subscription)
|
|
93
|
+
plan_details = plans_details.find { |p| p.plan == plan_name }
|
|
94
|
+
overrides = plan_details ? price_overrides(plan_details) : nil
|
|
103
95
|
input[:priceOverrides] = overrides if overrides.present?
|
|
104
96
|
|
|
105
97
|
subscription.change_plan(input,
|
|
@@ -158,11 +150,113 @@ module Kaui
|
|
|
158
150
|
redirect_to kaui_engine.account_bundles_path(input_subscription['account_id']), notice: 'Subscription BCD was successfully changed'
|
|
159
151
|
end
|
|
160
152
|
|
|
153
|
+
def edit_quantity
|
|
154
|
+
@subscription = Kaui::Subscription.find_by_id(params.require(:id), 'NONE', options_for_klient)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def update_quantity
|
|
158
|
+
id = params.require(:id)
|
|
159
|
+
input_subscription = params.require(:subscription)
|
|
160
|
+
|
|
161
|
+
quantity_raw = input_subscription['quantity'].to_s.strip
|
|
162
|
+
quantity = Integer(quantity_raw, exception: false)
|
|
163
|
+
if quantity.nil? || quantity <= 0
|
|
164
|
+
flash.now[:error] = 'Quantity must be a positive integer'
|
|
165
|
+
@subscription = Kaui::Subscription.find_by_id(id, 'NONE', options_for_klient)
|
|
166
|
+
@subscription.quantity = quantity_raw
|
|
167
|
+
render :edit_quantity and return
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
subscription = Kaui::Subscription.new
|
|
171
|
+
subscription.subscription_id = id
|
|
172
|
+
subscription.quantity = quantity
|
|
173
|
+
|
|
174
|
+
effective_from_date = params['effective_from_date']
|
|
175
|
+
|
|
176
|
+
subscription.update_quantity(current_user.kb_username, params[:reason], params[:comment], effective_from_date, nil, options_for_klient)
|
|
177
|
+
redirect_to kaui_engine.account_bundles_path(input_subscription['account_id']), notice: 'Subscription quantity was successfully changed'
|
|
178
|
+
rescue ActionController::ParameterMissing
|
|
179
|
+
redirect_to kaui_engine.edit_quantity_path(params[:id]), flash: { error: 'Required parameter missing: subscription' }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def record_usage
|
|
183
|
+
@subscription = Kaui::Subscription.find_by_id(params.require(:id), 'NONE', options_for_klient)
|
|
184
|
+
@unit_types = fetch_unit_types_from_subscription(@subscription)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def create_usage
|
|
188
|
+
subscription_id = params.require(:id)
|
|
189
|
+
unit_type = params[:unit_type].to_s.strip
|
|
190
|
+
amount_raw = params[:amount].to_s.strip
|
|
191
|
+
record_date = params[:record_date].to_s.strip
|
|
192
|
+
|
|
193
|
+
# Input validation
|
|
194
|
+
errors = []
|
|
195
|
+
errors << 'Unit type is required' if unit_type.blank?
|
|
196
|
+
errors << 'Amount is required' if amount_raw.blank?
|
|
197
|
+
amount = Integer(amount_raw, exception: false)
|
|
198
|
+
errors << 'Amount must be a positive integer' if amount.nil? || amount <= 0
|
|
199
|
+
errors << 'Date/time of usage is required' if record_date.blank?
|
|
200
|
+
parsed_date = parse_usage_date(record_date) if record_date.present?
|
|
201
|
+
errors << 'Date/time of usage must be a valid date or datetime' if record_date.present? && parsed_date.nil?
|
|
202
|
+
|
|
203
|
+
if errors.any?
|
|
204
|
+
flash.now[:error] = errors.join('. ')
|
|
205
|
+
@subscription = Kaui::Subscription.find_by_id(subscription_id, 'NONE', options_for_klient)
|
|
206
|
+
@unit_types = fetch_unit_types_from_subscription(@subscription)
|
|
207
|
+
@unit_type = unit_type
|
|
208
|
+
@amount = amount_raw
|
|
209
|
+
@record_date = record_date
|
|
210
|
+
@tracking_id = params[:tracking_id]
|
|
211
|
+
render :record_usage and return
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
begin
|
|
215
|
+
usage_record = KillBillClient::Model::UsageRecordAttributes.new
|
|
216
|
+
usage_record.record_date = parsed_date.utc.iso8601
|
|
217
|
+
usage_record.amount = amount
|
|
218
|
+
|
|
219
|
+
unit_usage_record = KillBillClient::Model::UnitUsageRecordAttributes.new
|
|
220
|
+
unit_usage_record.unit_type = unit_type
|
|
221
|
+
unit_usage_record.usage_records = [usage_record]
|
|
222
|
+
|
|
223
|
+
usage = Kaui::Usage.new
|
|
224
|
+
usage.subscription_id = subscription_id
|
|
225
|
+
usage.tracking_id = params[:tracking_id].presence
|
|
226
|
+
usage.unit_usage_records = [unit_usage_record]
|
|
227
|
+
|
|
228
|
+
usage.create(current_user.kb_username, nil, nil, options_for_klient)
|
|
229
|
+
|
|
230
|
+
subscription = Kaui::Subscription.find_by_id(subscription_id, 'NONE', options_for_klient)
|
|
231
|
+
redirect_to kaui_engine.account_bundles_path(subscription.account_id), notice: 'Usage was successfully recorded'
|
|
232
|
+
rescue StandardError => e
|
|
233
|
+
Rails.logger.error("Failed to record usage for subscription #{subscription_id}: #{e.class}: #{e.message}")
|
|
234
|
+
Rails.logger.error(e.backtrace.join("\n")) if e.backtrace
|
|
235
|
+
flash.now[:error] = "Error while recording usage: #{as_string(e)}"
|
|
236
|
+
@subscription = Kaui::Subscription.find_by_id(subscription_id, 'NONE', options_for_klient)
|
|
237
|
+
@unit_types = fetch_unit_types_from_subscription(@subscription)
|
|
238
|
+
@unit_type = unit_type
|
|
239
|
+
@amount = amount_raw
|
|
240
|
+
@record_date = record_date
|
|
241
|
+
@tracking_id = params[:tracking_id]
|
|
242
|
+
render :record_usage
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
161
246
|
def restful_show
|
|
162
247
|
subscription = Kaui::Subscription.find_by_id(params.require(:id), 'NONE', options_for_klient)
|
|
163
248
|
redirect_to kaui_engine.account_bundles_path(subscription.account_id)
|
|
164
249
|
end
|
|
165
250
|
|
|
251
|
+
def show_json
|
|
252
|
+
raw_body = Kaui::Subscription.find_raw_by_id(params.require(:id), 'NONE', options_for_klient)
|
|
253
|
+
render body: raw_body, content_type: 'application/json'
|
|
254
|
+
rescue KillBillClient::API::ResponseError => e
|
|
255
|
+
render body: e.response.body, content_type: 'application/json', status: e.code
|
|
256
|
+
rescue StandardError => e
|
|
257
|
+
render json: { error: e.message }, status: :internal_server_error
|
|
258
|
+
end
|
|
259
|
+
|
|
166
260
|
def validate_bundle_external_key
|
|
167
261
|
json_response do
|
|
168
262
|
external_key = params.require(:external_key)
|
|
@@ -254,19 +348,119 @@ module Kaui
|
|
|
254
348
|
plans
|
|
255
349
|
end
|
|
256
350
|
|
|
257
|
-
def price_overrides(
|
|
258
|
-
|
|
351
|
+
def price_overrides(plan_details)
|
|
352
|
+
raw = params[:price_overrides]
|
|
353
|
+
return nil if raw.blank?
|
|
354
|
+
|
|
355
|
+
entries = raw.respond_to?(:values) ? raw.values : Array(raw)
|
|
356
|
+
phase_meta = (plan_details.phases || []).index_by(&:type)
|
|
357
|
+
|
|
358
|
+
overrides = entries.filter_map do |entry|
|
|
359
|
+
entry = entry.to_unsafe_h if entry.respond_to?(:to_unsafe_h)
|
|
360
|
+
entry = entry.with_indifferent_access if entry.respond_to?(:with_indifferent_access)
|
|
361
|
+
|
|
362
|
+
price_str = entry[:price].to_s.strip
|
|
363
|
+
phase_type = entry[:phase_type].to_s.strip
|
|
364
|
+
|
|
365
|
+
price = BigDecimal(price_str, exception: false)
|
|
366
|
+
|
|
367
|
+
next if price.nil? || phase_type.blank? || price.negative?
|
|
368
|
+
|
|
369
|
+
phase = phase_meta[phase_type]
|
|
370
|
+
next if phase.nil?
|
|
371
|
+
|
|
372
|
+
override = KillBillClient::Model::PhasePriceAttributes.new
|
|
373
|
+
override.phase_type = phase_type
|
|
374
|
+
if phase_uses_fixed_price?(phase)
|
|
375
|
+
override.fixed_price = price.to_s('F')
|
|
376
|
+
else
|
|
377
|
+
override.recurring_price = price.to_s('F')
|
|
378
|
+
end
|
|
379
|
+
override
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
overrides.presence
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def build_plan_phases_map(plans_details)
|
|
386
|
+
(plans_details || []).to_h do |pd|
|
|
387
|
+
phases = (pd.phases || []).map do |ph|
|
|
388
|
+
fixed = phase_uses_fixed_price?(ph)
|
|
389
|
+
prices = ph.prices || []
|
|
390
|
+
price_label = if fixed
|
|
391
|
+
'$0.00'
|
|
392
|
+
elsif prices.any?
|
|
393
|
+
format('$%.2f', prices.first.value.to_f)
|
|
394
|
+
else
|
|
395
|
+
''
|
|
396
|
+
end
|
|
397
|
+
{ type: ph.type, fixed: fixed, priceLabel: price_label }
|
|
398
|
+
end
|
|
399
|
+
[pd.plan, phases]
|
|
400
|
+
end
|
|
401
|
+
end
|
|
259
402
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
403
|
+
def phase_uses_fixed_price?(phase)
|
|
404
|
+
(phase.prices || []).empty?
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def fetch_unit_types_from_subscription(subscription)
|
|
408
|
+
unit_types = []
|
|
409
|
+
(subscription.prices || []).each do |phase_price|
|
|
410
|
+
usage_prices = if phase_price.is_a?(Hash)
|
|
411
|
+
phase_price['usagePrices'] || phase_price[:usagePrices] || []
|
|
412
|
+
elsif phase_price.respond_to?(:usage_prices)
|
|
413
|
+
phase_price.usage_prices || []
|
|
414
|
+
else
|
|
415
|
+
[]
|
|
416
|
+
end
|
|
417
|
+
(usage_prices || []).each do |usage_price|
|
|
418
|
+
tier_prices = if usage_price.is_a?(Hash)
|
|
419
|
+
usage_price['tierPrices'] || usage_price[:tierPrices] || []
|
|
420
|
+
elsif usage_price.respond_to?(:tier_prices)
|
|
421
|
+
usage_price.tier_prices || []
|
|
422
|
+
else
|
|
423
|
+
[]
|
|
424
|
+
end
|
|
425
|
+
(tier_prices || []).each do |tier_price|
|
|
426
|
+
block_prices = if tier_price.is_a?(Hash)
|
|
427
|
+
tier_price['blockPrices'] || tier_price[:blockPrices] || []
|
|
428
|
+
elsif tier_price.respond_to?(:block_prices)
|
|
429
|
+
tier_price.block_prices || []
|
|
430
|
+
else
|
|
431
|
+
[]
|
|
432
|
+
end
|
|
433
|
+
(block_prices || []).each do |block_price|
|
|
434
|
+
unit_name = if block_price.is_a?(Hash)
|
|
435
|
+
block_price['unitName'] || block_price[:unitName] || block_price['unit_name']
|
|
436
|
+
elsif block_price.respond_to?(:unit_name)
|
|
437
|
+
block_price.unit_name
|
|
438
|
+
end
|
|
439
|
+
unit_types << unit_name if unit_name.present?
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
unit_types.uniq
|
|
445
|
+
rescue StandardError => e
|
|
446
|
+
Rails.logger.warn("Failed to extract unit types from subscription #{subscription&.subscription_id}: #{e.class}: #{e.message}")
|
|
447
|
+
[]
|
|
448
|
+
end
|
|
266
449
|
|
|
267
|
-
|
|
450
|
+
def parse_usage_date(str)
|
|
451
|
+
str = str.to_s.strip
|
|
452
|
+
return nil if str.empty?
|
|
268
453
|
|
|
269
|
-
|
|
454
|
+
if str.match?(/^\d{4}-\d{2}-\d{2}$/)
|
|
455
|
+
year, month, day = str.split('-').map(&:to_i)
|
|
456
|
+
Time.utc(year, month, day)
|
|
457
|
+
elsif str.match?(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/)
|
|
458
|
+
Time.iso8601("#{str}:00Z")
|
|
459
|
+
else
|
|
460
|
+
Time.iso8601(str)
|
|
461
|
+
end
|
|
462
|
+
rescue ArgumentError
|
|
463
|
+
nil
|
|
270
464
|
end
|
|
271
465
|
end
|
|
272
466
|
end
|
|
@@ -189,6 +189,34 @@ module Kaui
|
|
|
189
189
|
sub.present? and sub.billing_end_date.present?
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
+
def subscription_has_usage?(sub, catalog = nil)
|
|
193
|
+
return false if sub.blank?
|
|
194
|
+
|
|
195
|
+
# First try the subscription's resolved per-phase prices
|
|
196
|
+
# (populated by GET /subscriptions/{id} but not by the bundles listing endpoint).
|
|
197
|
+
(sub.prices || []).each do |phase_price|
|
|
198
|
+
usage_prices = if phase_price.is_a?(Hash)
|
|
199
|
+
phase_price['usagePrices'] || phase_price[:usagePrices] ||
|
|
200
|
+
phase_price['usage_prices'] || phase_price[:usage_prices]
|
|
201
|
+
elsif phase_price.respond_to?(:usage_prices)
|
|
202
|
+
phase_price.usage_prices
|
|
203
|
+
end
|
|
204
|
+
return true if usage_prices.present?
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Fall back to the catalog plan definition
|
|
208
|
+
return false if catalog.blank? || sub.plan_name.blank?
|
|
209
|
+
|
|
210
|
+
product = catalog.products&.find { |p| p.name == sub.product_name }
|
|
211
|
+
plan = product&.plans&.find { |p| p.name == sub.plan_name }
|
|
212
|
+
return false if plan.nil?
|
|
213
|
+
|
|
214
|
+
(plan.phases || []).any? do |phase|
|
|
215
|
+
usages = phase.respond_to?(:usages) ? phase.usages : nil
|
|
216
|
+
usages.present?
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
192
220
|
def paging_button_class(num, current_page)
|
|
193
221
|
num == current_page ? 'btn btn-primary' : 'btn btn-custom'
|
|
194
222
|
end
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
<% end %>
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
<% if @bundles.empty? %>
|
|
64
|
+
<% if @bundles.empty? && @search_query.present? %>
|
|
65
65
|
<div class="custom-alert custom-alert-info mt-3">
|
|
66
|
-
<span>No bundles found
|
|
66
|
+
<span>No bundles found for the given search query.</span>
|
|
67
67
|
</div>
|
|
68
68
|
<% end %>
|
|
69
69
|
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
|
|
18
18
|
<ul class="dropdown-menu header-menu shadow border-0 rounded-3"
|
|
19
19
|
id="<%= dropdown_id %>_menu"
|
|
20
|
+
aria-labelledby="<%= dropdown_id %>_button"
|
|
20
21
|
style="min-width: 6.25rem; display: none;">
|
|
21
22
|
<% menu_items&.each do |item| %>
|
|
22
23
|
<li>
|
|
23
|
-
<%= link_to item[:path], class: "dropdown-item d-flex align-items-center gap-2" do %>
|
|
24
|
+
<%= link_to item[:path], class: "dropdown-item d-flex align-items-center gap-2", data: (item[:data] || {}), method: item[:method] do %>
|
|
24
25
|
<%= image_tag(item[:icon], alt: "#{item[:label]} Icon", size: "16x16") if item[:icon].present? %>
|
|
25
26
|
<span class="text-black-700 fs-6 text-normal" style="font-weight: 500; font-size: 0.875rem !important; line-height: 1.25rem;"><%= item[:label] %></span>
|
|
26
27
|
<% end %>
|
|
@@ -31,28 +32,21 @@
|
|
|
31
32
|
|
|
32
33
|
<%= javascript_tag do %>
|
|
33
34
|
document.addEventListener('DOMContentLoaded', function () {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
var button = document.getElementById('<%= dropdown_id %>_button');
|
|
36
|
+
var menu = document.getElementById('<%= dropdown_id %>_menu');
|
|
37
|
+
var wrapper = document.getElementById('<%= dropdown_id %>_wrapper');
|
|
37
38
|
|
|
38
39
|
button.addEventListener('click', function (e) {
|
|
39
40
|
e.stopPropagation();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Close all other open dropdowns first (including other dots menus)
|
|
43
|
-
document.querySelectorAll('.dropdown-menu').forEach(function (m) {
|
|
44
|
-
if (m !== menu) {
|
|
45
|
-
m.style.display = 'none';
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Toggle this one
|
|
41
|
+
var isShown = menu.style.display === 'block';
|
|
50
42
|
menu.style.display = isShown ? 'none' : 'block';
|
|
43
|
+
button.setAttribute('aria-expanded', !isShown);
|
|
51
44
|
});
|
|
52
45
|
|
|
53
46
|
document.addEventListener('click', function (e) {
|
|
54
|
-
if (!wrapper.contains(e.target)) {
|
|
47
|
+
if (!wrapper.contains(e.target) && menu.style.display === 'block') {
|
|
55
48
|
menu.style.display = 'none';
|
|
49
|
+
button.setAttribute('aria-expanded', 'false');
|
|
56
50
|
}
|
|
57
51
|
});
|
|
58
52
|
});
|
|
@@ -45,10 +45,7 @@
|
|
|
45
45
|
type: "button",
|
|
46
46
|
html_class: "kaui-button custom-hover",
|
|
47
47
|
html_options: {
|
|
48
|
-
id: "dropdownMenu1"
|
|
49
|
-
data: {
|
|
50
|
-
bs_toggle: "dropdown"
|
|
51
|
-
}
|
|
48
|
+
id: "dropdownMenu1"
|
|
52
49
|
}
|
|
53
50
|
} %>
|
|
54
51
|
<ul class="dropdown-menu" id="column-visibility" aria-labelledby="dropdownMenu1">
|
|
@@ -213,13 +210,15 @@
|
|
|
213
210
|
|
|
214
211
|
<%= javascript_tag do %>
|
|
215
212
|
$(document).ready(function() {
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
213
|
+
// Toggle Edit Columns dropdown
|
|
214
|
+
$('#dropdownMenu1').on('click', function(e) {
|
|
215
|
+
e.stopPropagation();
|
|
216
|
+
var $menu = $('#column-visibility');
|
|
217
|
+
$menu.toggleClass('show');
|
|
218
|
+
$(this).attr('aria-expanded', $menu.hasClass('show'));
|
|
220
219
|
});
|
|
221
220
|
|
|
222
|
-
$('
|
|
221
|
+
$('#column-visibility').on('click', 'input[type="checkbox"], label', function(event) {
|
|
223
222
|
event.stopPropagation();
|
|
224
223
|
});
|
|
225
224
|
|
|
@@ -45,10 +45,7 @@
|
|
|
45
45
|
type: "button",
|
|
46
46
|
html_class: "kaui-button custom-hover",
|
|
47
47
|
html_options: {
|
|
48
|
-
id: "dropdownMenu1"
|
|
49
|
-
data: {
|
|
50
|
-
bs_toggle: "dropdown"
|
|
51
|
-
}
|
|
48
|
+
id: "dropdownMenu1"
|
|
52
49
|
}
|
|
53
50
|
} %>
|
|
54
51
|
<ul class="dropdown-menu" id="column-visibility" aria-labelledby="dropdownMenu1">
|
|
@@ -213,13 +210,15 @@
|
|
|
213
210
|
|
|
214
211
|
<%= javascript_tag do %>
|
|
215
212
|
$(document).ready(function() {
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
213
|
+
// Toggle Edit Columns dropdown
|
|
214
|
+
$('#dropdownMenu1').on('click', function(e) {
|
|
215
|
+
e.stopPropagation();
|
|
216
|
+
var $menu = $('#column-visibility');
|
|
217
|
+
$menu.toggleClass('show');
|
|
218
|
+
$(this).attr('aria-expanded', $menu.hasClass('show'));
|
|
220
219
|
});
|
|
221
220
|
|
|
222
|
-
$('
|
|
221
|
+
$('#column-visibility').on('click', 'input[type="checkbox"], label', function(event) {
|
|
223
222
|
event.stopPropagation();
|
|
224
223
|
});
|
|
225
224
|
|
|
@@ -7,9 +7,49 @@
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
9
|
<div class="form-group d-flex pb-3 border-bottom mb-3">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
<div class="col-sm-3 control-label" style="padding-top: 2px;">
|
|
11
|
+
<div id="toggle_phase_overrides" style="cursor: pointer; user-select: none;">
|
|
12
|
+
<span id="toggle_phase_overrides_caret">▾</span>
|
|
13
|
+
<strong>Phase Overrides</strong>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="col-sm-9" id="phase_overrides_section">
|
|
17
|
+
<p class="text-muted mb-2" style="font-size: 13px;">Override prices for specific phases. All other phases will use catalog pricing.</p>
|
|
18
|
+
<div id="phase_overrides_headers" class="d-flex mb-1 gap-2" style="display: none;">
|
|
19
|
+
<div style="flex: 1; padding-left: 2px;"><small><strong>Phase</strong></small></div>
|
|
20
|
+
<div style="flex: 1; padding-left: 2px;">
|
|
21
|
+
<small><strong>Override Price</strong></small>
|
|
22
|
+
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-left: 3px;" title="Leave blank to use catalog pricing">
|
|
23
|
+
<circle cx="8" cy="8" r="7" stroke="#A4A7AE" stroke-width="1.25"/>
|
|
24
|
+
<path d="M8 7v5M8 5.5v-.5" stroke="#A4A7AE" stroke-width="1.5" stroke-linecap="round"/>
|
|
25
|
+
</svg>
|
|
26
|
+
</div>
|
|
27
|
+
<div style="width: 40px;"></div>
|
|
28
|
+
</div>
|
|
29
|
+
<div id="phase_overrides_rows"></div>
|
|
30
|
+
<button type="button" id="add_phase_override" class="btn w-100 mt-2 d-flex align-items-center justify-content-center gap-1"
|
|
31
|
+
style="border: 1px dashed #d0d5dd; background: white; color: #2563eb; font-size: 14px;">
|
|
32
|
+
<span style="font-size: 16px; line-height: 1;">+</span> Add another phase
|
|
33
|
+
</button>
|
|
34
|
+
<script type="application/json" id="plan_phases_data"><%= raw json_escape((@plan_phases || {}).to_json) %></script>
|
|
35
|
+
<style>.phase-override-remove:hover svg path { stroke: #D92D20; }</style>
|
|
36
|
+
<template id="phase_override_row_template">
|
|
37
|
+
<div class="phase-override-row d-flex align-items-center mb-2 gap-2">
|
|
38
|
+
<select name="price_overrides[][phase_type]" class="form-control phase-override-type" style="flex: 1;"></select>
|
|
39
|
+
<div class="input-group phase-override-price-group" style="flex: 1;">
|
|
40
|
+
<span class="input-group-text">$</span>
|
|
41
|
+
<input type="number" name="price_overrides[][price]" step="any" min="0" placeholder="0.00" class="form-control phase-override-price">
|
|
42
|
+
</div>
|
|
43
|
+
<button type="button" class="btn btn-outline-secondary phase-override-remove" style="padding: 6px 10px; flex-shrink: 0;" aria-label="Remove">
|
|
44
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
45
|
+
<path d="M13 3.66699L12.4093 13.4143C12.3666 14.1181 11.7834 14.667 11.0783 14.667H4.92164C4.21659 14.667 3.6334 14.1181 3.59075 13.4143L3 3.66699" stroke="#A4A7AE" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
46
|
+
<path d="M2 3.66634H5.33333M5.33333 3.66634L6.16017 1.73706C6.26522 1.49194 6.50625 1.33301 6.77293 1.33301H9.22707C9.49373 1.33301 9.7348 1.49194 9.8398 1.73706L10.6667 3.66634M5.33333 3.66634H10.6667M14 3.66634H10.6667" stroke="#A4A7AE" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
47
|
+
<path d="M6.33325 11V7" stroke="#A4A7AE" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
48
|
+
<path d="M9.66675 11V7" stroke="#A4A7AE" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
49
|
+
</svg>
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|
|
13
53
|
</div>
|
|
14
54
|
</div>
|
|
15
55
|
<div class="form-group d-flex pb-3" id="radio_group">
|
|
@@ -106,5 +146,99 @@ $(document).ready(function() {
|
|
|
106
146
|
$( "input[type=radio]").on("click", function() {
|
|
107
147
|
onTypeChange();
|
|
108
148
|
});
|
|
149
|
+
initPhaseOverrides();
|
|
109
150
|
});
|
|
151
|
+
|
|
152
|
+
function initPhaseOverrides() {
|
|
153
|
+
var planPhases = {};
|
|
154
|
+
try { planPhases = JSON.parse($('#plan_phases_data').text() || '{}'); } catch (e) { planPhases = {}; }
|
|
155
|
+
|
|
156
|
+
var $section = $('#phase_overrides_section');
|
|
157
|
+
var $rows = $('#phase_overrides_rows');
|
|
158
|
+
var $addBtn = $('#add_phase_override');
|
|
159
|
+
var $headers = $('#phase_overrides_headers');
|
|
160
|
+
var $template = $('#phase_override_row_template');
|
|
161
|
+
var $planSelect = $('select[name="plan_name"]');
|
|
162
|
+
|
|
163
|
+
function currentPhases() {
|
|
164
|
+
return planPhases[$planSelect.val()] || [];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function phaseLabel(p) {
|
|
168
|
+
var title = p.type.charAt(0) + p.type.slice(1).toLowerCase();
|
|
169
|
+
return p.priceLabel ? title + ' (' + p.priceLabel + ')' : title;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function selectedTypes($exceptRow) {
|
|
173
|
+
return $rows.find('.phase-override-row').filter(function() {
|
|
174
|
+
return !$exceptRow || this !== $exceptRow[0];
|
|
175
|
+
}).map(function() { return $(this).find('.phase-override-type').val(); }).get();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function refreshAddBtn() {
|
|
179
|
+
var phases = currentPhases();
|
|
180
|
+
var used = selectedTypes();
|
|
181
|
+
var hasMore = phases.length > 0 && used.length < phases.length;
|
|
182
|
+
$addBtn.toggle(hasMore);
|
|
183
|
+
$headers.css('display', $rows.find('.phase-override-row').length > 0 ? 'flex' : 'none');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function rebuildRowOptions() {
|
|
187
|
+
var phases = currentPhases();
|
|
188
|
+
$rows.find('.phase-override-row').each(function() {
|
|
189
|
+
var $row = $(this);
|
|
190
|
+
var $select = $row.find('.phase-override-type');
|
|
191
|
+
var current = $select.val();
|
|
192
|
+
var used = selectedTypes($row);
|
|
193
|
+
$select.empty();
|
|
194
|
+
phases.forEach(function(p) {
|
|
195
|
+
if (used.indexOf(p.type) === -1) {
|
|
196
|
+
$select.append($('<option>', { value: p.type, text: phaseLabel(p) }));
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
var currentStillValid = phases.some(function(p) { return p.type === current; }) && used.indexOf(current) === -1;
|
|
200
|
+
if (currentStillValid) $select.val(current);
|
|
201
|
+
});
|
|
202
|
+
refreshAddBtn();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function addRow(preferredType) {
|
|
206
|
+
var phases = currentPhases();
|
|
207
|
+
if (phases.length === 0) return;
|
|
208
|
+
var used = selectedTypes();
|
|
209
|
+
var next = preferredType && phases.some(function(p) { return p.type === preferredType; }) && used.indexOf(preferredType) === -1
|
|
210
|
+
? preferredType
|
|
211
|
+
: (phases.find(function(p) { return used.indexOf(p.type) === -1; }) || {}).type;
|
|
212
|
+
if (!next) return;
|
|
213
|
+
|
|
214
|
+
var $row = $($template.html());
|
|
215
|
+
$rows.append($row);
|
|
216
|
+
rebuildRowOptions();
|
|
217
|
+
$row.find('.phase-override-type').val(next);
|
|
218
|
+
rebuildRowOptions();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function resetRows() {
|
|
222
|
+
$rows.empty();
|
|
223
|
+
var phases = currentPhases();
|
|
224
|
+
if (phases.length > 0) addRow(phases[0].type);
|
|
225
|
+
refreshAddBtn();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
$('#toggle_phase_overrides').on('click', function() {
|
|
229
|
+
$section.toggle();
|
|
230
|
+
$('#toggle_phase_overrides_caret').html($section.is(':visible') ? '▾' : '▸');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
$addBtn.on('click', function() { addRow(); });
|
|
234
|
+
|
|
235
|
+
$rows.on('change', '.phase-override-type', rebuildRowOptions);
|
|
236
|
+
$rows.on('click', '.phase-override-remove', function() {
|
|
237
|
+
$(this).closest('.phase-override-row').remove();
|
|
238
|
+
refreshAddBtn();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
$planSelect.on('change', resetRows);
|
|
242
|
+
resetRows();
|
|
243
|
+
}
|
|
110
244
|
<% end %>
|
|
@@ -35,9 +35,49 @@
|
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
37
37
|
<div class="form-group d-flex pb-3">
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
<div class="col-sm-3 control-label" style="padding-top: 2px;">
|
|
39
|
+
<div id="toggle_phase_overrides" style="cursor: pointer; user-select: none;">
|
|
40
|
+
<span id="toggle_phase_overrides_caret">▾</span>
|
|
41
|
+
<strong>Phase Overrides</strong>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="col-sm-9" id="phase_overrides_section">
|
|
45
|
+
<p class="text-muted mb-2" style="font-size: 13px;">Override prices for specific phases. All other phases will use catalog pricing.</p>
|
|
46
|
+
<div id="phase_overrides_headers" class="d-flex mb-1 gap-2" style="display: none;">
|
|
47
|
+
<div style="flex: 1; padding-left: 2px;"><small><strong>Phase</strong></small></div>
|
|
48
|
+
<div style="flex: 1; padding-left: 2px;">
|
|
49
|
+
<small><strong>Override Price</strong></small>
|
|
50
|
+
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-left: 3px;" title="Leave blank to use catalog pricing">
|
|
51
|
+
<circle cx="8" cy="8" r="7" stroke="#A4A7AE" stroke-width="1.25"/>
|
|
52
|
+
<path d="M8 7v5M8 5.5v-.5" stroke="#A4A7AE" stroke-width="1.5" stroke-linecap="round"/>
|
|
53
|
+
</svg>
|
|
54
|
+
</div>
|
|
55
|
+
<div style="width: 40px;"></div>
|
|
56
|
+
</div>
|
|
57
|
+
<div id="phase_overrides_rows"></div>
|
|
58
|
+
<button type="button" id="add_phase_override" class="btn w-100 mt-2 d-flex align-items-center justify-content-center gap-1"
|
|
59
|
+
style="border: 1px dashed #d0d5dd; background: white; color: #2563eb; font-size: 14px;">
|
|
60
|
+
<span style="font-size: 16px; line-height: 1;">+</span> Add another phase
|
|
61
|
+
</button>
|
|
62
|
+
<script type="application/json" id="plan_phases_data"><%= raw json_escape((@plan_phases || {}).to_json) %></script>
|
|
63
|
+
<style>.phase-override-remove:hover svg path { stroke: #D92D20; }</style>
|
|
64
|
+
<template id="phase_override_row_template">
|
|
65
|
+
<div class="phase-override-row d-flex align-items-center mb-2 gap-2">
|
|
66
|
+
<select name="price_overrides[][phase_type]" class="form-control phase-override-type" style="flex: 1;"></select>
|
|
67
|
+
<div class="input-group phase-override-price-group" style="flex: 1;">
|
|
68
|
+
<span class="input-group-text">$</span>
|
|
69
|
+
<input type="number" name="price_overrides[][price]" step="any" min="0" placeholder="0.00" class="form-control phase-override-price">
|
|
70
|
+
</div>
|
|
71
|
+
<button type="button" class="btn btn-outline-secondary phase-override-remove" style="padding: 6px 10px; flex-shrink: 0;" aria-label="Remove">
|
|
72
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
73
|
+
<path d="M13 3.66699L12.4093 13.4143C12.3666 14.1181 11.7834 14.667 11.0783 14.667H4.92164C4.21659 14.667 3.6334 14.1181 3.59075 13.4143L3 3.66699" stroke="#A4A7AE" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
74
|
+
<path d="M2 3.66634H5.33333M5.33333 3.66634L6.16017 1.73706C6.26522 1.49194 6.50625 1.33301 6.77293 1.33301H9.22707C9.49373 1.33301 9.7348 1.49194 9.8398 1.73706L10.6667 3.66634M5.33333 3.66634H10.6667M14 3.66634H10.6667" stroke="#A4A7AE" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
75
|
+
<path d="M6.33325 11V7" stroke="#A4A7AE" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
76
|
+
<path d="M9.66675 11V7" stroke="#A4A7AE" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
77
|
+
</svg>
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
41
81
|
</div>
|
|
42
82
|
</div>
|
|
43
83
|
<div class="form-group d-flex pb-3 border-bottom mb-3">
|
|
@@ -123,5 +163,99 @@ $(document).ready(function() {
|
|
|
123
163
|
$( "input[type=radio]").on("click", function() {
|
|
124
164
|
onTypeChange();
|
|
125
165
|
});
|
|
166
|
+
initPhaseOverrides();
|
|
126
167
|
});
|
|
168
|
+
|
|
169
|
+
function initPhaseOverrides() {
|
|
170
|
+
var planPhases = {};
|
|
171
|
+
try { planPhases = JSON.parse($('#plan_phases_data').text() || '{}'); } catch (e) { planPhases = {}; }
|
|
172
|
+
|
|
173
|
+
var $section = $('#phase_overrides_section');
|
|
174
|
+
var $rows = $('#phase_overrides_rows');
|
|
175
|
+
var $addBtn = $('#add_phase_override');
|
|
176
|
+
var $headers = $('#phase_overrides_headers');
|
|
177
|
+
var $template = $('#phase_override_row_template');
|
|
178
|
+
var $planSelect = $('select[name="plan_name"]');
|
|
179
|
+
|
|
180
|
+
function currentPhases() {
|
|
181
|
+
return planPhases[$planSelect.val()] || [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function phaseLabel(p) {
|
|
185
|
+
var title = p.type.charAt(0) + p.type.slice(1).toLowerCase();
|
|
186
|
+
return p.priceLabel ? title + ' (' + p.priceLabel + ')' : title;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function selectedTypes($exceptRow) {
|
|
190
|
+
return $rows.find('.phase-override-row').filter(function() {
|
|
191
|
+
return !$exceptRow || this !== $exceptRow[0];
|
|
192
|
+
}).map(function() { return $(this).find('.phase-override-type').val(); }).get();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function refreshAddBtn() {
|
|
196
|
+
var phases = currentPhases();
|
|
197
|
+
var used = selectedTypes();
|
|
198
|
+
var hasMore = phases.length > 0 && used.length < phases.length;
|
|
199
|
+
$addBtn.toggle(hasMore);
|
|
200
|
+
$headers.css('display', $rows.find('.phase-override-row').length > 0 ? 'flex' : 'none');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function rebuildRowOptions() {
|
|
204
|
+
var phases = currentPhases();
|
|
205
|
+
$rows.find('.phase-override-row').each(function() {
|
|
206
|
+
var $row = $(this);
|
|
207
|
+
var $select = $row.find('.phase-override-type');
|
|
208
|
+
var current = $select.val();
|
|
209
|
+
var used = selectedTypes($row);
|
|
210
|
+
$select.empty();
|
|
211
|
+
phases.forEach(function(p) {
|
|
212
|
+
if (used.indexOf(p.type) === -1) {
|
|
213
|
+
$select.append($('<option>', { value: p.type, text: phaseLabel(p) }));
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
var currentStillValid = phases.some(function(p) { return p.type === current; }) && used.indexOf(current) === -1;
|
|
217
|
+
if (currentStillValid) $select.val(current);
|
|
218
|
+
});
|
|
219
|
+
refreshAddBtn();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function addRow(preferredType) {
|
|
223
|
+
var phases = currentPhases();
|
|
224
|
+
if (phases.length === 0) return;
|
|
225
|
+
var used = selectedTypes();
|
|
226
|
+
var next = preferredType && phases.some(function(p) { return p.type === preferredType; }) && used.indexOf(preferredType) === -1
|
|
227
|
+
? preferredType
|
|
228
|
+
: (phases.find(function(p) { return used.indexOf(p.type) === -1; }) || {}).type;
|
|
229
|
+
if (!next) return;
|
|
230
|
+
|
|
231
|
+
var $row = $($template.html());
|
|
232
|
+
$rows.append($row);
|
|
233
|
+
rebuildRowOptions();
|
|
234
|
+
$row.find('.phase-override-type').val(next);
|
|
235
|
+
rebuildRowOptions();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function resetRows() {
|
|
239
|
+
$rows.empty();
|
|
240
|
+
var phases = currentPhases();
|
|
241
|
+
if (phases.length > 0) addRow(phases[0].type);
|
|
242
|
+
refreshAddBtn();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
$('#toggle_phase_overrides').on('click', function() {
|
|
246
|
+
$section.toggle();
|
|
247
|
+
$('#toggle_phase_overrides_caret').html($section.is(':visible') ? '▾' : '▸');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
$addBtn.on('click', function() { addRow(); });
|
|
251
|
+
|
|
252
|
+
$rows.on('change', '.phase-override-type', rebuildRowOptions);
|
|
253
|
+
$rows.on('click', '.phase-override-remove', function() {
|
|
254
|
+
$(this).closest('.phase-override-row').remove();
|
|
255
|
+
refreshAddBtn();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
$planSelect.on('change', resetRows);
|
|
259
|
+
resetRows();
|
|
260
|
+
}
|
|
127
261
|
<% end %>
|
|
@@ -111,8 +111,26 @@
|
|
|
111
111
|
</span>
|
|
112
112
|
</td>
|
|
113
113
|
<td><span class="phase-type"><%= humanized_subscription_phase_type(sub).downcase %></span></td>
|
|
114
|
-
<td
|
|
115
|
-
|
|
114
|
+
<td>
|
|
115
|
+
<% if sub.start_date.present? %>
|
|
116
|
+
<span class="object-id-popover" data-id="<%= sub.start_date %>" data-title="Start Date (ISO)">
|
|
117
|
+
<%= humanized_subscription_start_date(sub, account) %>
|
|
118
|
+
</span>
|
|
119
|
+
<% end %>
|
|
120
|
+
</td>
|
|
121
|
+
<td>
|
|
122
|
+
<% if sub.cancelled_date.present? %>
|
|
123
|
+
<span class="object-id-popover" data-id="<%= sub.cancelled_date %>" data-title="Entitlement Date (ISO)">
|
|
124
|
+
<%= humanized_subscription_cancelled_date(sub, account) %>
|
|
125
|
+
</span>
|
|
126
|
+
<% if sub.billing_end_date.present? %>
|
|
127
|
+
<br>
|
|
128
|
+
<span class="object-id-popover" data-id="<%= sub.billing_end_date %>" data-title="Billing End Date (ISO)">
|
|
129
|
+
<%= humanized_subscription_billing_end_date(sub, account) %>
|
|
130
|
+
</span>
|
|
131
|
+
<% end %>
|
|
132
|
+
<% end %>
|
|
133
|
+
</td>
|
|
116
134
|
<td><%= humanized_subscription_charged_through_date(sub, account) %></td>
|
|
117
135
|
<td><%= sub.quantity || 1 %></td>
|
|
118
136
|
<td class="text-center">
|
|
@@ -141,9 +159,9 @@
|
|
|
141
159
|
</div>
|
|
142
160
|
<% end %>
|
|
143
161
|
|
|
144
|
-
<%
|
|
145
|
-
<% menu_items = [] %>
|
|
162
|
+
<% menu_items = [] %>
|
|
146
163
|
|
|
164
|
+
<% if can?(:create, Kaui::Subscription) || can?(:pause_resume, Kaui::Subscription) || can?(:change_plan, Kaui::Subscription) || can?(:transfer, Kaui::Bundle) || (can?(:record, Kaui::Usage) && subscription_has_usage?(sub, catalog_for_subscription(sub, catalogs))) %>
|
|
147
165
|
<% if can?(:change_plan, Kaui::Subscription) %>
|
|
148
166
|
<% menu_items << {
|
|
149
167
|
label: "Change Plan",
|
|
@@ -183,6 +201,22 @@
|
|
|
183
201
|
} %>
|
|
184
202
|
<% end %>
|
|
185
203
|
|
|
204
|
+
<% if can?(:create, Kaui::Subscription) %>
|
|
205
|
+
<% menu_items << {
|
|
206
|
+
label: "Update Quantity",
|
|
207
|
+
path: kaui_engine.edit_quantity_path(id: sub.subscription_id),
|
|
208
|
+
icon: "kaui/subscription/change.svg"
|
|
209
|
+
} %>
|
|
210
|
+
<% end %>
|
|
211
|
+
|
|
212
|
+
<% if can?(:record, Kaui::Usage) && subscription_has_usage?(sub, catalog_for_subscription(sub, catalogs)) %>
|
|
213
|
+
<% menu_items << {
|
|
214
|
+
label: "Record Usage",
|
|
215
|
+
path: kaui_engine.record_usage_path(id: sub.subscription_id),
|
|
216
|
+
icon: "kaui/subscription/change.svg"
|
|
217
|
+
} %>
|
|
218
|
+
<% end %>
|
|
219
|
+
|
|
186
220
|
<% if can?(:transfer, Kaui::Bundle) && sub.product_category != 'ADD_ON' %>
|
|
187
221
|
<% menu_items << {
|
|
188
222
|
label: I18n.translate('transfer'),
|
|
@@ -190,7 +224,18 @@
|
|
|
190
224
|
icon: "kaui/subscription/transfer.svg"
|
|
191
225
|
} %>
|
|
192
226
|
<% end %>
|
|
227
|
+
<% end %>
|
|
228
|
+
|
|
229
|
+
<% if can?(:read, Kaui::Subscription) %>
|
|
230
|
+
<% menu_items << {
|
|
231
|
+
label: "View JSON",
|
|
232
|
+
path: "#view_subscription_json_modal",
|
|
233
|
+
icon: "kaui/view-doc.svg",
|
|
234
|
+
data: { bs_toggle: 'modal', id: sub.subscription_id }
|
|
235
|
+
} %>
|
|
236
|
+
<% end %>
|
|
193
237
|
|
|
238
|
+
<% if menu_items.any? %>
|
|
194
239
|
<%= render partial: "kaui/components/menu_dropdown/menu_dropdown", locals: {
|
|
195
240
|
variant: "btn-light",
|
|
196
241
|
label: "",
|
|
@@ -210,6 +255,7 @@
|
|
|
210
255
|
</table>
|
|
211
256
|
|
|
212
257
|
<%= render :partial => 'kaui/subscriptions/cancel_by_date_modal' %>
|
|
258
|
+
<%= render :partial => 'kaui/subscriptions/view_json_modal' %>
|
|
213
259
|
|
|
214
260
|
<%= javascript_tag do %>
|
|
215
261
|
$(document).ready(function() {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<div class="modal fade view_subscription_json_modal" id="view_subscription_json_modal" tabindex="-1" role="dialog" aria-labelledby="viewSubscriptionJsonLabel" aria-hidden="true">
|
|
2
|
+
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
|
|
3
|
+
<div class="modal-content">
|
|
4
|
+
<div class="modal-header">
|
|
5
|
+
<h5 class="modal-title d-flex align-items-center gap-3" id="viewSubscriptionJsonLabel">
|
|
6
|
+
<span class="icon-container">
|
|
7
|
+
<%= image_tag("kaui/view-doc.svg", width: 20, height: 20) %>
|
|
8
|
+
</span>
|
|
9
|
+
Subscription JSON
|
|
10
|
+
</h5>
|
|
11
|
+
<button type="button" class="close close-button custom-hover" data-bs-dismiss="modal" aria-label="Close">
|
|
12
|
+
<span aria-hidden="true">
|
|
13
|
+
<%= image_tag("kaui/modal/close.svg", width: 20, height: 20) %>
|
|
14
|
+
</span>
|
|
15
|
+
</button>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="modal-body">
|
|
18
|
+
<div id="view_subscription_json_loading">Loading...</div>
|
|
19
|
+
<div id="view_subscription_json_error" class="text-danger" style="display: none;"></div>
|
|
20
|
+
<pre id="view_subscription_json_content" style="display: none; max-height: 60vh; overflow: auto; background-color: #f8f9fa; padding: 1rem; border-radius: 0.375rem; font-size: 0.8125rem; line-height: 1.4;"></pre>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="modal-footer">
|
|
23
|
+
<%= render "kaui/components/button/button", {
|
|
24
|
+
label: 'Close',
|
|
25
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
26
|
+
type: "button",
|
|
27
|
+
html_class: "kaui-button custom-hover",
|
|
28
|
+
html_options: {
|
|
29
|
+
data: {
|
|
30
|
+
bs_dismiss: "modal"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} %>
|
|
34
|
+
<%= render "kaui/components/button/button", {
|
|
35
|
+
label: 'Copy JSON',
|
|
36
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
37
|
+
type: "button",
|
|
38
|
+
html_class: "kaui-dropdown custom-hover",
|
|
39
|
+
html_options: {
|
|
40
|
+
id: "copy_subscription_json_btn"
|
|
41
|
+
}
|
|
42
|
+
} %>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<%= javascript_tag do %>
|
|
49
|
+
$(document).ready(function() {
|
|
50
|
+
var $modal = $('#view_subscription_json_modal');
|
|
51
|
+
if (!$modal.length) return;
|
|
52
|
+
|
|
53
|
+
$modal.on('show.bs.modal', function (e) {
|
|
54
|
+
var subId = e.relatedTarget && e.relatedTarget.dataset ? e.relatedTarget.dataset['id'] : null;
|
|
55
|
+
var $loading = $('#view_subscription_json_loading');
|
|
56
|
+
var $error = $('#view_subscription_json_error');
|
|
57
|
+
var $content = $('#view_subscription_json_content');
|
|
58
|
+
|
|
59
|
+
$loading.show();
|
|
60
|
+
$error.hide().text('');
|
|
61
|
+
$content.hide().text('');
|
|
62
|
+
|
|
63
|
+
if (!subId) {
|
|
64
|
+
$loading.hide();
|
|
65
|
+
$error.text('Missing subscription id').show();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
$.ajax({
|
|
70
|
+
url: Routes.kaui_engine_show_subscription_json_path(subId),
|
|
71
|
+
type: 'GET',
|
|
72
|
+
dataType: 'json'
|
|
73
|
+
})
|
|
74
|
+
.done(function (data) {
|
|
75
|
+
$content.text(JSON.stringify(data, null, 2)).show();
|
|
76
|
+
$loading.hide();
|
|
77
|
+
})
|
|
78
|
+
.fail(function (xhr) {
|
|
79
|
+
$loading.hide();
|
|
80
|
+
var message = 'Failed to load subscription JSON';
|
|
81
|
+
if (xhr && xhr.responseText) {
|
|
82
|
+
message += ': ' + xhr.responseText;
|
|
83
|
+
}
|
|
84
|
+
$error.text(message).show();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
$('#copy_subscription_json_btn').on('click', function () {
|
|
89
|
+
var text = $('#view_subscription_json_content').text();
|
|
90
|
+
if (!text) return;
|
|
91
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
92
|
+
navigator.clipboard.writeText(text).then(function () {
|
|
93
|
+
if (typeof ajaxInfoAlert === 'function') {
|
|
94
|
+
ajaxInfoAlert('JSON copied to clipboard!', 3000);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
<% end %>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<div class="register kaui-subscription-new">
|
|
2
|
+
<div class="">
|
|
3
|
+
<div class="mx-auto" style="max-width: 37.5rem;">
|
|
4
|
+
<h5 class="add-account-title border-bottom pb-3 mb-3">
|
|
5
|
+
<span class="icon-container">
|
|
6
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
7
|
+
<path d="M9.16675 11.6665H13.3334M6.66675 11.6665H6.67423M10.8334 14.9998H6.66675M13.3334 14.9998H13.3259" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8
|
+
<path d="M13.75 1.6665V4.99984M6.25 1.6665V4.99984" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
9
|
+
<path d="M15.8333 3.3335H4.16667C3.24619 3.3335 2.5 4.07969 2.5 5.00016V16.6668C2.5 17.5873 3.24619 18.3335 4.16667 18.3335H15.8333C16.7538 18.3335 17.5 17.5873 17.5 16.6668V5.00016C17.5 4.07969 16.7538 3.3335 15.8333 3.3335Z" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
10
|
+
<path d="M2.5 8.3335H17.5" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
11
|
+
</svg>
|
|
12
|
+
</span>
|
|
13
|
+
Update Subscription Quantity
|
|
14
|
+
</h5>
|
|
15
|
+
<%= form_for @subscription, :as => :subscription, :url => update_quantity_path(@subscription.subscription_id), :html => {:method => :put, :class => 'form-horizontal'} do |f| %>
|
|
16
|
+
<%= f.hidden_field :account_id %>
|
|
17
|
+
|
|
18
|
+
<div class="form-group d-flex pb-3">
|
|
19
|
+
<%= f.label :quantity, 'Quantity', :class => 'col-sm-3 control-label' %>
|
|
20
|
+
<div class="col-sm-9">
|
|
21
|
+
<%= f.number_field :quantity, { :min => 1, :required => true, :class => 'form-control'} %>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="form-group d-flex pb-3 border-bottom mb-3" id="form_effective_from_date">
|
|
25
|
+
<%= label_tag :effective_from_date, 'Effective Date', :class => 'col-sm-3 control-label' %>
|
|
26
|
+
<div class="col-sm-9">
|
|
27
|
+
<div class="custom-date-input-wrapper kaui-button custom-hover">
|
|
28
|
+
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
29
|
+
<path d="M14.9167 12.5V18.3333M17.8333 15.4167H12" stroke="#A4A7AE" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
30
|
+
<path d="M14.0833 1.66675V5.00008M6.58325 1.66675V5.00008" stroke="#A4A7AE" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
31
|
+
<path d="M17.8333 10.8333V4.99992C17.8333 4.07944 17.0871 3.33325 16.1666 3.33325H4.49992C3.57944 3.33325 2.83325 4.07944 2.83325 4.99992V16.6666C2.83325 17.5871 3.57944 18.3333 4.49992 18.3333H10.3333" stroke="#A4A7AE" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
32
|
+
<path d="M2.83325 8.33325H17.8333" stroke="#A4A7AE" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
33
|
+
</svg>
|
|
34
|
+
<input class="form-control" value="<%= Time.zone.today.strftime('%Y-%m-%d') %>" id="effective_from_date" name="effective_from_date" type="text" data-provide="datepicker" data-date-format="yyyy-mm-dd" data-date-today-highlight="true">
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="form-group d-flex justify-content-end pb-3">
|
|
39
|
+
<%= render "kaui/components/button/button", {
|
|
40
|
+
label: 'Close',
|
|
41
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
42
|
+
type: "button",
|
|
43
|
+
html_class: "kaui-button custom-hover mx-2",
|
|
44
|
+
html_options: {
|
|
45
|
+
id: "closeButton",
|
|
46
|
+
onclick: "window.history.back();"
|
|
47
|
+
}
|
|
48
|
+
} %>
|
|
49
|
+
<%= render "kaui/components/button/button", {
|
|
50
|
+
label: 'Save Subscription',
|
|
51
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
52
|
+
type: "submit",
|
|
53
|
+
html_class: "kaui-dropdown custom-hover"
|
|
54
|
+
} %>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<div class="register kaui-subscription-new">
|
|
2
|
+
<div class="">
|
|
3
|
+
<div class="mx-auto" style="max-width: 37.5rem;">
|
|
4
|
+
<h5 class="add-account-title border-bottom pb-3 mb-3">
|
|
5
|
+
<span class="icon-container">
|
|
6
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
7
|
+
<path d="M9.16675 11.6665H13.3334M6.66675 11.6665H6.67423M10.8334 14.9998H6.66675M13.3334 14.9998H13.3259" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8
|
+
<path d="M13.75 1.6665V4.99984M6.25 1.6665V4.99984" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
9
|
+
<path d="M15.8333 3.3335H4.16667C3.24619 3.3335 2.5 4.07969 2.5 5.00016V16.6668C2.5 17.5873 3.24619 18.3335 4.16667 18.3335H15.8333C16.7538 18.3335 17.5 17.5873 17.5 16.6668V5.00016C17.5 4.07969 16.7538 3.3335 15.8333 3.3335Z" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
10
|
+
<path d="M2.5 8.3335H17.5" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
11
|
+
</svg>
|
|
12
|
+
</span>
|
|
13
|
+
Record Usage
|
|
14
|
+
</h5>
|
|
15
|
+
|
|
16
|
+
<p class="text-muted small mb-3">
|
|
17
|
+
Record usage for subscription
|
|
18
|
+
<strong><%= @subscription.plan_name %></strong>
|
|
19
|
+
(<code><%= @subscription.subscription_id %></code>).
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<%= form_tag create_usage_path(@subscription.subscription_id), method: :post, id: 'record_usage_form', class: 'form-horizontal' do %>
|
|
23
|
+
|
|
24
|
+
<div class="form-group d-flex pb-3">
|
|
25
|
+
<%= label_tag :unit_type, 'Unit Type', class: 'col-sm-3 control-label' %>
|
|
26
|
+
<div class="col-sm-9">
|
|
27
|
+
<% if @unit_types.length == 1 %>
|
|
28
|
+
<%= text_field_tag :unit_type, @unit_types.first, readonly: true, required: true, class: 'form-control bg-light' %>
|
|
29
|
+
<% elsif @unit_types.length > 1 %>
|
|
30
|
+
<%= select_tag :unit_type, options_for_select(@unit_types.map { |u| [u, u] }, @unit_type.presence || @unit_types.first), required: true, class: 'form-select' %>
|
|
31
|
+
<% else %>
|
|
32
|
+
<%= text_field_tag :unit_type, @unit_type, required: true, maxlength: 255, class: 'form-control', placeholder: 'e.g. api-calls' %>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="form-group d-flex pb-3">
|
|
38
|
+
<%= label_tag :amount, 'Amount', class: 'col-sm-3 control-label' %>
|
|
39
|
+
<div class="col-sm-9">
|
|
40
|
+
<%= number_field_tag :amount, @amount, required: true, min: 1, step: 1, class: 'form-control', placeholder: 'Positive integer' %>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="form-group d-flex pb-3">
|
|
45
|
+
<%= label_tag :record_date, 'Date/Time of Usage', class: 'col-sm-3 control-label' %>
|
|
46
|
+
<div class="col-sm-9">
|
|
47
|
+
<%= datetime_field_tag :record_date,
|
|
48
|
+
@record_date.presence || Time.now.utc.strftime('%Y-%m-%dT%H:%M'),
|
|
49
|
+
required: true,
|
|
50
|
+
class: 'form-control' %>
|
|
51
|
+
<small class="form-text text-muted">Date and time treated as UTC</small>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="form-group d-flex pb-3">
|
|
56
|
+
<%= label_tag :tracking_id, 'Tracking ID', class: 'col-sm-3 control-label' %>
|
|
57
|
+
<div class="col-sm-9">
|
|
58
|
+
<%= text_field_tag :tracking_id, @tracking_id, maxlength: 255, class: 'form-control', placeholder: 'Optional, used for idempotency' %>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div class="form-group d-flex justify-content-end pb-3">
|
|
63
|
+
<%= render "kaui/components/button/button", {
|
|
64
|
+
label: 'Close',
|
|
65
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
66
|
+
type: "button",
|
|
67
|
+
html_class: "kaui-button custom-hover mx-2",
|
|
68
|
+
html_options: {
|
|
69
|
+
id: "closeButton",
|
|
70
|
+
onclick: "window.history.back();"
|
|
71
|
+
}
|
|
72
|
+
} %>
|
|
73
|
+
<%= render "kaui/components/button/button", {
|
|
74
|
+
label: 'Record Usage',
|
|
75
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
76
|
+
type: "submit",
|
|
77
|
+
html_class: "kaui-dropdown custom-hover"
|
|
78
|
+
} %>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<%= javascript_tag do %>
|
|
86
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
87
|
+
var form = document.getElementById('record_usage_form');
|
|
88
|
+
if (!form) { return; }
|
|
89
|
+
form.addEventListener('submit', function (e) {
|
|
90
|
+
var errors = [];
|
|
91
|
+
|
|
92
|
+
var unitTypeEl = document.getElementById('unit_type');
|
|
93
|
+
var unitType = unitTypeEl ? (unitTypeEl.value || '').trim() : '';
|
|
94
|
+
if (unitType.length === 0) {
|
|
95
|
+
errors.push('Unit type is required.');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
var amountStr = (document.getElementById('amount').value || '').trim();
|
|
99
|
+
var amount = parseInt(amountStr, 10);
|
|
100
|
+
if (amountStr.length === 0 || isNaN(amount) || amount <= 0 || String(amount) !== amountStr) {
|
|
101
|
+
errors.push('Amount must be a positive integer.');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (errors.length > 0) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
alert(errors.join('\n'));
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -141,7 +141,12 @@ Kaui::Engine.routes.draw do
|
|
|
141
141
|
post '/:id/tags' => 'subscriptions#update_tags', :as => 'update_subscriptions_tags'
|
|
142
142
|
get '/:id/edit_bcd' => 'subscriptions#edit_bcd', :as => 'edit_bcd'
|
|
143
143
|
put '/:id/update_bcd' => 'subscriptions#update_bcd', :as => 'update_bcd'
|
|
144
|
+
get '/:id/edit_quantity' => 'subscriptions#edit_quantity', :as => 'edit_quantity'
|
|
145
|
+
put '/:id/update_quantity' => 'subscriptions#update_quantity', :as => 'update_quantity'
|
|
146
|
+
get '/:id/record_usage' => 'subscriptions#record_usage', :as => 'record_usage'
|
|
147
|
+
post '/:id/record_usage' => 'subscriptions#create_usage', :as => 'create_usage'
|
|
144
148
|
put '/:id/reinstate' => 'subscriptions#reinstate', :as => 'reinstate'
|
|
149
|
+
get '/:id/show_json' => 'subscriptions#show_json', :as => 'show_subscription_json'
|
|
145
150
|
get '/validate_external_key' => 'subscriptions#validate_external_key', :as => 'subscriptions_validate_external_key'
|
|
146
151
|
get '/validate_bundle_external_key' => 'subscriptions#validate_bundle_external_key', :as => 'subscriptions_validate_bundle_external_key'
|
|
147
152
|
end
|
data/lib/kaui/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kaui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0.
|
|
4
|
+
version: 4.0.19
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kill Bill core team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actionpack
|
|
@@ -507,6 +507,7 @@ files:
|
|
|
507
507
|
- app/models/kaui/tag_definition.rb
|
|
508
508
|
- app/models/kaui/tenant.rb
|
|
509
509
|
- app/models/kaui/transaction.rb
|
|
510
|
+
- app/models/kaui/usage.rb
|
|
510
511
|
- app/models/kaui/user.rb
|
|
511
512
|
- app/models/kaui/user_role.rb
|
|
512
513
|
- app/services/dependencies/kenui.rb
|
|
@@ -649,9 +650,12 @@ files:
|
|
|
649
650
|
- app/views/kaui/subscriptions/_edit_form.html.erb
|
|
650
651
|
- app/views/kaui/subscriptions/_form.html.erb
|
|
651
652
|
- app/views/kaui/subscriptions/_subscriptions_table.html.erb
|
|
653
|
+
- app/views/kaui/subscriptions/_view_json_modal.html.erb
|
|
652
654
|
- app/views/kaui/subscriptions/edit.html.erb
|
|
653
655
|
- app/views/kaui/subscriptions/edit_bcd.erb
|
|
656
|
+
- app/views/kaui/subscriptions/edit_quantity.erb
|
|
654
657
|
- app/views/kaui/subscriptions/new.html.erb
|
|
658
|
+
- app/views/kaui/subscriptions/record_usage.erb
|
|
655
659
|
- app/views/kaui/tag_definitions/_form.html.erb
|
|
656
660
|
- app/views/kaui/tag_definitions/index.html.erb
|
|
657
661
|
- app/views/kaui/tag_definitions/new.html.erb
|