kaui 4.0.17 → 4.0.18
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 +103 -0
- data/app/helpers/kaui/subscription_helper.rb +28 -0
- data/app/models/kaui/usage.rb +6 -0
- data/app/views/kaui/components/menu_dropdown/_menu_dropdown.html.erb +1 -1
- 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 +126 -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: b226cddd4e3508c415761943d804cab4ff71e506eca8d8ffbe592fde0cbed193
|
|
4
|
+
data.tar.gz: 6b29cc16b8e32acc0fceca6f1444e3073e8bbe8cd29fbd59158c7edb05cf4796
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b562deca64d70f1fe3323cedd752ed20fe00202ea56e248873aeb11672a3128c45b809ff7138395dc04d301b2b35fc07b8191e0d6e653423eb18176131e3b7d2
|
|
7
|
+
data.tar.gz: 276b11b5364329227e3be9d0b121500350ced30662505dccea557c7084eaccb91508401b73c7bf1ae80e92cec06d630ee0fab7f9bbfc1c17cbead743d2b2efa1
|
|
@@ -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
|
|
@@ -158,11 +158,114 @@ module Kaui
|
|
|
158
158
|
redirect_to kaui_engine.account_bundles_path(input_subscription['account_id']), notice: 'Subscription BCD was successfully changed'
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
+
def edit_quantity
|
|
162
|
+
@subscription = Kaui::Subscription.find_by_id(params.require(:id), 'NONE', options_for_klient)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def update_quantity
|
|
166
|
+
id = params.require(:id)
|
|
167
|
+
input_subscription = params.require(:subscription)
|
|
168
|
+
|
|
169
|
+
quantity_raw = input_subscription['quantity'].to_s.strip
|
|
170
|
+
quantity = Integer(quantity_raw, exception: false)
|
|
171
|
+
if quantity.nil? || quantity <= 0
|
|
172
|
+
flash.now[:error] = 'Quantity must be a positive integer'
|
|
173
|
+
@subscription = Kaui::Subscription.find_by_id(id, 'NONE', options_for_klient)
|
|
174
|
+
@subscription.quantity = quantity_raw
|
|
175
|
+
render :edit_quantity and return
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
subscription = Kaui::Subscription.new
|
|
179
|
+
subscription.subscription_id = id
|
|
180
|
+
subscription.quantity = quantity
|
|
181
|
+
|
|
182
|
+
effective_from_date = params['effective_from_date']
|
|
183
|
+
|
|
184
|
+
subscription.update_quantity(current_user.kb_username, params[:reason], params[:comment], effective_from_date, nil, options_for_klient)
|
|
185
|
+
redirect_to kaui_engine.account_bundles_path(input_subscription['account_id']), notice: 'Subscription quantity was successfully changed'
|
|
186
|
+
rescue ActionController::ParameterMissing
|
|
187
|
+
redirect_to kaui_engine.edit_quantity_path(params[:id]), flash: { error: 'Required parameter missing: subscription' }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def record_usage
|
|
191
|
+
@subscription = Kaui::Subscription.find_by_id(params.require(:id), 'NONE', options_for_klient)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def create_usage
|
|
195
|
+
subscription_id = params.require(:id)
|
|
196
|
+
unit_type = params[:unit_type].to_s.strip
|
|
197
|
+
amount_raw = params[:amount].to_s.strip
|
|
198
|
+
record_date = params[:record_date].to_s.strip
|
|
199
|
+
|
|
200
|
+
# Input validation
|
|
201
|
+
errors = []
|
|
202
|
+
errors << 'Unit type is required' if unit_type.blank?
|
|
203
|
+
errors << 'Amount is required' if amount_raw.blank?
|
|
204
|
+
amount = Integer(amount_raw, exception: false)
|
|
205
|
+
errors << 'Amount must be a positive integer' if amount.nil? || amount <= 0
|
|
206
|
+
errors << 'Date/time of usage is required' if record_date.blank?
|
|
207
|
+
parsed_date = begin
|
|
208
|
+
record_date.blank? ? nil : Time.iso8601(record_date)
|
|
209
|
+
rescue ArgumentError
|
|
210
|
+
nil
|
|
211
|
+
end
|
|
212
|
+
errors << 'Date/time of usage must be a valid ISO 8601 timestamp' if record_date.present? && parsed_date.nil?
|
|
213
|
+
|
|
214
|
+
if errors.any?
|
|
215
|
+
flash.now[:error] = errors.join('. ')
|
|
216
|
+
@subscription = Kaui::Subscription.find_by_id(subscription_id, 'NONE', options_for_klient)
|
|
217
|
+
@unit_type = unit_type
|
|
218
|
+
@amount = amount_raw
|
|
219
|
+
@record_date = record_date
|
|
220
|
+
@tracking_id = params[:tracking_id]
|
|
221
|
+
render :record_usage and return
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
begin
|
|
225
|
+
usage_record = KillBillClient::Model::UsageRecordAttributes.new
|
|
226
|
+
usage_record.record_date = parsed_date.utc.iso8601
|
|
227
|
+
usage_record.amount = amount
|
|
228
|
+
|
|
229
|
+
unit_usage_record = KillBillClient::Model::UnitUsageRecordAttributes.new
|
|
230
|
+
unit_usage_record.unit_type = unit_type
|
|
231
|
+
unit_usage_record.usage_records = [usage_record]
|
|
232
|
+
|
|
233
|
+
usage = Kaui::Usage.new
|
|
234
|
+
usage.subscription_id = subscription_id
|
|
235
|
+
usage.tracking_id = params[:tracking_id].presence
|
|
236
|
+
usage.unit_usage_records = [unit_usage_record]
|
|
237
|
+
|
|
238
|
+
usage.create(current_user.kb_username, params[:reason], params[:comment], options_for_klient)
|
|
239
|
+
|
|
240
|
+
subscription = Kaui::Subscription.find_by_id(subscription_id, 'NONE', options_for_klient)
|
|
241
|
+
redirect_to kaui_engine.account_bundles_path(subscription.account_id), notice: 'Usage was successfully recorded'
|
|
242
|
+
rescue StandardError => e
|
|
243
|
+
Rails.logger.error("Failed to record usage for subscription #{subscription_id}: #{e.class}: #{e.message}")
|
|
244
|
+
Rails.logger.error(e.backtrace.join("\n")) if e.backtrace
|
|
245
|
+
flash.now[:error] = "Error while recording usage: #{as_string(e)}"
|
|
246
|
+
@subscription = Kaui::Subscription.find_by_id(subscription_id, 'NONE', options_for_klient)
|
|
247
|
+
@unit_type = unit_type
|
|
248
|
+
@amount = amount_raw
|
|
249
|
+
@record_date = record_date
|
|
250
|
+
@tracking_id = params[:tracking_id]
|
|
251
|
+
render :record_usage
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
161
255
|
def restful_show
|
|
162
256
|
subscription = Kaui::Subscription.find_by_id(params.require(:id), 'NONE', options_for_klient)
|
|
163
257
|
redirect_to kaui_engine.account_bundles_path(subscription.account_id)
|
|
164
258
|
end
|
|
165
259
|
|
|
260
|
+
def show_json
|
|
261
|
+
raw_body = Kaui::Subscription.find_raw_by_id(params.require(:id), 'NONE', options_for_klient)
|
|
262
|
+
render body: raw_body, content_type: 'application/json'
|
|
263
|
+
rescue KillBillClient::API::ResponseError => e
|
|
264
|
+
render body: e.response.body, content_type: 'application/json', status: e.code
|
|
265
|
+
rescue StandardError => e
|
|
266
|
+
render json: { error: e.message }, status: :internal_server_error
|
|
267
|
+
end
|
|
268
|
+
|
|
166
269
|
def validate_bundle_external_key
|
|
167
270
|
json_response do
|
|
168
271
|
external_key = params.require(:external_key)
|
|
@@ -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
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
style="min-width: 6.25rem; display: none;">
|
|
21
21
|
<% menu_items&.each do |item| %>
|
|
22
22
|
<li>
|
|
23
|
-
<%= link_to item[:path], class: "dropdown-item d-flex align-items-center gap-2" do %>
|
|
23
|
+
<%= link_to item[:path], class: "dropdown-item d-flex align-items-center gap-2", data: (item[:data] || {}), method: item[:method] do %>
|
|
24
24
|
<%= image_tag(item[:icon], alt: "#{item[:label]} Icon", size: "16x16") if item[:icon].present? %>
|
|
25
25
|
<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
26
|
<% 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,126 @@
|
|
|
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
|
+
<%= text_field_tag :unit_type, @unit_type, required: true, maxlength: 255, class: 'form-control', placeholder: 'e.g. api-calls' %>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="form-group d-flex pb-3">
|
|
32
|
+
<%= label_tag :amount, 'Amount', class: 'col-sm-3 control-label' %>
|
|
33
|
+
<div class="col-sm-9">
|
|
34
|
+
<%= number_field_tag :amount, @amount, required: true, min: 1, step: 1, class: 'form-control', placeholder: 'Positive integer' %>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="form-group d-flex pb-3">
|
|
39
|
+
<%= label_tag :record_date, 'Date/Time of Usage', class: 'col-sm-3 control-label' %>
|
|
40
|
+
<div class="col-sm-9">
|
|
41
|
+
<%= text_field_tag :record_date,
|
|
42
|
+
@record_date.presence || Time.now.utc.iso8601,
|
|
43
|
+
required: true,
|
|
44
|
+
class: 'form-control',
|
|
45
|
+
placeholder: 'YYYY-MM-DDTHH:MM:SSZ (ISO 8601)' %>
|
|
46
|
+
<small class="form-text text-muted">ISO 8601 format, e.g. <code>2026-06-13T15:30:00Z</code></small>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="form-group d-flex pb-3">
|
|
51
|
+
<%= label_tag :tracking_id, 'Tracking ID', class: 'col-sm-3 control-label' %>
|
|
52
|
+
<div class="col-sm-9">
|
|
53
|
+
<%= text_field_tag :tracking_id, @tracking_id, maxlength: 255, class: 'form-control', placeholder: 'Optional, used for idempotency' %>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="form-group d-flex pb-3 border-bottom mb-3">
|
|
58
|
+
<%= label_tag :reason, 'Reason', class: 'col-sm-3 control-label' %>
|
|
59
|
+
<div class="col-sm-9">
|
|
60
|
+
<%= text_field_tag :reason, params[:reason], class: 'form-control' %>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="form-group d-flex pb-3 border-bottom mb-3">
|
|
65
|
+
<%= label_tag :comment, 'Comment', class: 'col-sm-3 control-label' %>
|
|
66
|
+
<div class="col-sm-9">
|
|
67
|
+
<%= text_field_tag :comment, params[:comment], class: 'form-control' %>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="form-group d-flex justify-content-end pb-3">
|
|
72
|
+
<%= render "kaui/components/button/button", {
|
|
73
|
+
label: 'Close',
|
|
74
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
75
|
+
type: "button",
|
|
76
|
+
html_class: "kaui-button custom-hover mx-2",
|
|
77
|
+
html_options: {
|
|
78
|
+
id: "closeButton",
|
|
79
|
+
onclick: "window.history.back();"
|
|
80
|
+
}
|
|
81
|
+
} %>
|
|
82
|
+
<%= render "kaui/components/button/button", {
|
|
83
|
+
label: 'Record Usage',
|
|
84
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
85
|
+
type: "submit",
|
|
86
|
+
html_class: "kaui-dropdown custom-hover"
|
|
87
|
+
} %>
|
|
88
|
+
</div>
|
|
89
|
+
<% end %>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<%= javascript_tag do %>
|
|
95
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
96
|
+
var form = document.getElementById('record_usage_form');
|
|
97
|
+
if (!form) { return; }
|
|
98
|
+
form.addEventListener('submit', function (e) {
|
|
99
|
+
var errors = [];
|
|
100
|
+
|
|
101
|
+
var unitType = (document.getElementById('unit_type').value || '').trim();
|
|
102
|
+
if (unitType.length === 0) {
|
|
103
|
+
errors.push('Unit type is required.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
var amountStr = (document.getElementById('amount').value || '').trim();
|
|
107
|
+
var amount = parseInt(amountStr, 10);
|
|
108
|
+
if (amountStr.length === 0 || isNaN(amount) || amount <= 0 || String(amount) !== amountStr) {
|
|
109
|
+
errors.push('Amount must be a positive integer.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
var dateStr = (document.getElementById('record_date').value || '').trim();
|
|
113
|
+
// Permissive ISO 8601 check
|
|
114
|
+
var isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?$/;
|
|
115
|
+
var parsed = Date.parse(dateStr);
|
|
116
|
+
if (dateStr.length === 0 || isNaN(parsed) || !isoRegex.test(dateStr)) {
|
|
117
|
+
errors.push('Date/time of usage must be a valid ISO 8601 timestamp (e.g. 2026-06-13T15:30:00Z).');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (errors.length > 0) {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
alert(errors.join('\n'));
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
<% 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.18
|
|
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-06-
|
|
11
|
+
date: 2026-06-18 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
|