kaui 4.0.20 → 4.0.24
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/multi_functions_bar_utils.js +18 -9
- data/app/assets/stylesheets/kaui/tenants.css +53 -0
- data/app/controllers/kaui/accounts_controller.rb +2 -2
- data/app/controllers/kaui/admin_tenants_controller.rb +12 -0
- data/app/controllers/kaui/engine_controller_util.rb +2 -2
- data/app/controllers/kaui/invoices_controller.rb +2 -0
- data/app/controllers/kaui/payments_controller.rb +2 -0
- data/app/controllers/kaui/subscriptions_controller.rb +58 -23
- data/app/models/kaui/account.rb +3 -1
- data/app/models/kaui/admin_tenant.rb +10 -0
- data/app/models/kaui/invoice.rb +3 -0
- data/app/models/kaui/payment.rb +3 -0
- data/app/services/dependencies/aviate.rb +89 -0
- data/app/views/kaui/account_tags/index.html.erb +1 -1
- data/app/views/kaui/accounts/_functions_bar.html.erb +2 -2
- data/app/views/kaui/accounts/_multi_functions_bar.html.erb +2 -2
- data/app/views/kaui/accounts/index.html.erb +5 -5
- data/app/views/kaui/admin_tenants/_form_invoice_template.erb +0 -1
- data/app/views/kaui/admin_tenants/_form_invoice_translation.erb +47 -1
- data/app/views/kaui/audit_logs/index.html.erb +1 -1
- data/app/views/kaui/components/button/_button.html.erb +1 -1
- data/app/views/kaui/custom_fields/index.html.erb +1 -1
- data/app/views/kaui/invoices/_multi_functions_bar.html.erb +2 -2
- data/app/views/kaui/invoices/index.html.erb +6 -5
- data/app/views/kaui/payments/_multi_functions_bar.html.erb +2 -2
- data/app/views/kaui/payments/index.html.erb +9 -8
- data/app/views/kaui/shared/_advanced_search_filterbar.html.erb +6 -6
- data/app/views/kaui/subscriptions/record_usage.erb +32 -8
- data/app/views/kaui/tags/index.html.erb +1 -1
- data/lib/kaui/version.rb +1 -1
- data/lib/kaui.rb +13 -9
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 19115a4ca850dfd755598a560dc66e96b00694f00b305e057260fd6b13c57e3d
|
|
4
|
+
data.tar.gz: 9780699cd5e97d8c6887b3e0c200d968a11e644afd5496c58829277d558a28bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8bcf2e6e322833277c4d305aca8621bf4495cc6443961af0a565bf7da0d6efdb5892d8b66ac31e820f08953d221ad54b309018fbd3d088032e211ea73d51abd9
|
|
7
|
+
data.tar.gz: e1a44d558e7b996185be6a79cca68fb2eecb4e200f2affff7e5de3ccf0ca6a38f78d0ffdc814c67f3ab794585e6d7b4c4604dfe06ea77e7b7bc86360e229273e
|
|
@@ -29,8 +29,8 @@ function searchParseOperatorText(text) {
|
|
|
29
29
|
// Function to parse URL parameters
|
|
30
30
|
function getUrlParams() {
|
|
31
31
|
var params = {};
|
|
32
|
-
var
|
|
33
|
-
queryString =
|
|
32
|
+
var advanceSearchQuery = new URLSearchParams(window.location.search).get('advance_search_query');
|
|
33
|
+
var queryString = advanceSearchQuery !== null ? advanceSearchQuery : window.location.search.substring(1).replace(/ac_id/g, 'account_id');
|
|
34
34
|
var regex = /([^&=]+)=([^&]*)/g;
|
|
35
35
|
var m;
|
|
36
36
|
while (m = regex.exec(queryString)) {
|
|
@@ -55,15 +55,17 @@ function populateSearchLabelsFromUrl() {
|
|
|
55
55
|
if (match) {
|
|
56
56
|
var columnName = match[1].replace(/_/g, ' ').replace(/^\w/, function(l) { return l.toUpperCase(); });
|
|
57
57
|
var filter = searchFormatOperator(match[2].trim());
|
|
58
|
+
var isHiddenAccountFilter = match[1].trim().toLowerCase() === 'account_id' && window.location.pathname.includes('/accounts/');
|
|
58
59
|
var label = $('<span>', {
|
|
59
|
-
class: 'label label-info d-inline-flex align-items-center gap-2',
|
|
60
|
+
class: 'label label-info d-inline-flex align-items-center gap-2' + (isHiddenAccountFilter ? ' d-none' : ''),
|
|
60
61
|
'data-field': columnName.trim(),
|
|
61
62
|
'data-filter': filter.trim(),
|
|
62
|
-
'data-value': value.trim()
|
|
63
|
+
'data-value': value.trim(),
|
|
64
|
+
'data-hidden-filter': isHiddenAccountFilter
|
|
63
65
|
});
|
|
64
66
|
|
|
65
67
|
if (hasBalanceFilter && columnName.toLowerCase().trim() !== 'balance') {
|
|
66
|
-
label.attr('class', 'label label-default d-inline-flex align-items-center gap-2');
|
|
68
|
+
label.attr('class', 'label label-default d-inline-flex align-items-center gap-2' + (isHiddenAccountFilter ? ' d-none' : ''));
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
var labelText = $('<span>', {
|
|
@@ -168,6 +170,10 @@ function showAdvanceSearchModal() {
|
|
|
168
170
|
|
|
169
171
|
// Populate the search fields with the current filters
|
|
170
172
|
searchLabelsContainer.find('.label').each(function() {
|
|
173
|
+
if ($(this).data('hidden-filter')) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
171
177
|
var labelText = $(this).text().trim();
|
|
172
178
|
var parts = labelText.split(' [');
|
|
173
179
|
var columnName = parts[0].trim();
|
|
@@ -226,6 +232,10 @@ $(document).on('click', '.filter-close-icon', function() {
|
|
|
226
232
|
}
|
|
227
233
|
});
|
|
228
234
|
var searchParams = searchQuery();
|
|
235
|
+
// searchQuery() returns an already percent-encoded field[op]=value fragment; decode it
|
|
236
|
+
// back to raw text so it can be sent as the (single-encoded) value of the dedicated
|
|
237
|
+
// advance_search_query param instead of as top-level bracketed keys.
|
|
238
|
+
var rawSearchParams = searchParams ? decodeURIComponent(searchParams) : searchParams;
|
|
229
239
|
// Reapply search without the removed filter
|
|
230
240
|
var tableSelectors = ['#invoices-table', '#accounts-table', '#payments-table', '#subscriptions-table'];
|
|
231
241
|
var table = null;
|
|
@@ -240,15 +250,14 @@ $(document).on('click', '.filter-close-icon', function() {
|
|
|
240
250
|
|
|
241
251
|
if (table && table.ajax) {
|
|
242
252
|
table.on('preXhr.dt', function(e, settings, data) {
|
|
243
|
-
data.
|
|
253
|
+
data.advance_search_query = rawSearchParams;
|
|
244
254
|
});
|
|
245
255
|
table.ajax.reload(null, false);
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
// Update URL
|
|
249
|
-
if (
|
|
250
|
-
var
|
|
251
|
-
var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + pushParams;
|
|
259
|
+
if (rawSearchParams) {
|
|
260
|
+
var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?advance_search_query=' + encodeURIComponent(rawSearchParams);
|
|
252
261
|
window.history.pushState({ path: newUrl }, '', newUrl);
|
|
253
262
|
} else {
|
|
254
263
|
var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname;
|
|
@@ -641,6 +641,59 @@
|
|
|
641
641
|
color: #414651;
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
+
/* app/views/kaui/admin_tenants/_form_invoice_translation.erb */
|
|
645
|
+
|
|
646
|
+
.existing-invoice-translations-container {
|
|
647
|
+
max-width: 80rem;
|
|
648
|
+
width: 100%;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.existing-invoice-translations-container .existing-invoice-translations-header {
|
|
652
|
+
display: flex;
|
|
653
|
+
align-items: start;
|
|
654
|
+
justify-content: space-between;
|
|
655
|
+
padding: 0;
|
|
656
|
+
border-bottom: 0.0625rem solid #E9EAEB;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.existing-invoice-translations-container .existing-invoice-translations-header h2 {
|
|
660
|
+
font-weight: 600;
|
|
661
|
+
font-size: 1.125rem;
|
|
662
|
+
line-height: 1.75rem;
|
|
663
|
+
color: #414651;
|
|
664
|
+
margin-bottom: 1.25rem;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.existing-invoice-translations-container table.existing-invoice-translations-table {
|
|
668
|
+
overflow-x: auto;
|
|
669
|
+
min-width: 100%;
|
|
670
|
+
border: none !important;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.existing-invoice-translations-container table.existing-invoice-translations-table tbody tr {
|
|
674
|
+
border-bottom: 0.0625rem solid #E9EAEB !important;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.existing-invoice-translations-container table.existing-invoice-translations-table th {
|
|
678
|
+
padding: 0.375rem 0.75rem !important;
|
|
679
|
+
font-weight: 500 !important;
|
|
680
|
+
font-size: 0.75rem !important;
|
|
681
|
+
line-height: 1.125rem;
|
|
682
|
+
letter-spacing: 0;
|
|
683
|
+
color: #717680;
|
|
684
|
+
text-transform: capitalize;
|
|
685
|
+
background-color: #FAFAFA !important;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.existing-invoice-translations-container table.existing-invoice-translations-table td {
|
|
689
|
+
font-weight: 500 !important;
|
|
690
|
+
font-size: 0.875rem !important;
|
|
691
|
+
line-height: 1.125rem !important;
|
|
692
|
+
text-align: start;
|
|
693
|
+
padding: 0.3125rem 0.75rem !important;
|
|
694
|
+
color: #535862 !important;
|
|
695
|
+
}
|
|
696
|
+
|
|
644
697
|
/* app/views/kaui/admin_tenants/_show_catalog_simple.erb */
|
|
645
698
|
|
|
646
699
|
#catalog_simple, #catalog_xml {
|
|
@@ -17,7 +17,7 @@ module Kaui
|
|
|
17
17
|
return
|
|
18
18
|
end
|
|
19
19
|
@search_fields = Kaui::Account::ADVANCED_SEARCH_COLUMNS.map { |attr| [attr, attr.split('_').join(' ').capitalize] }
|
|
20
|
-
@dropdown_default = default_columns(Kaui.account_search_columns.call[2], Kaui::Account::
|
|
20
|
+
@dropdown_default = default_columns(Kaui.account_search_columns.call[2], Kaui::Account::DEFAULT_VISIBLE_COLUMNS)
|
|
21
21
|
@visible_columns = @dropdown_default
|
|
22
22
|
|
|
23
23
|
@ordering = params[:ordering] || (@search_query.blank? ? 'desc' : 'asc')
|
|
@@ -47,7 +47,7 @@ module Kaui
|
|
|
47
47
|
Kaui.account_search_columns.call(account, view_context)[1]
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
paginate searcher, data_extractor, formatter, default_columns(Kaui.account_search_columns.call[2], Kaui::Account::
|
|
50
|
+
paginate searcher, data_extractor, formatter, default_columns(Kaui.account_search_columns.call[2], Kaui::Account::DEFAULT_VISIBLE_COLUMNS)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def download
|
|
@@ -53,6 +53,12 @@ module Kaui
|
|
|
53
53
|
@invoice_template = nil
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
fetch_invoice_translations = promise do
|
|
57
|
+
Kaui::AdminTenant.get_invoice_translations(options)
|
|
58
|
+
rescue StandardError
|
|
59
|
+
@invoice_translations = {}
|
|
60
|
+
end
|
|
61
|
+
|
|
56
62
|
fetch_tenant_plugin_config = promise { Kaui::AdminTenant.get_tenant_plugin_config(options) }
|
|
57
63
|
|
|
58
64
|
@catalog_versions = []
|
|
@@ -76,6 +82,12 @@ module Kaui
|
|
|
76
82
|
nil
|
|
77
83
|
end
|
|
78
84
|
|
|
85
|
+
@invoice_translations = begin
|
|
86
|
+
wait(fetch_invoice_translations)
|
|
87
|
+
rescue StandardError
|
|
88
|
+
{}
|
|
89
|
+
end
|
|
90
|
+
|
|
79
91
|
@tenant_plugin_config = begin
|
|
80
92
|
wait(fetch_tenant_plugin_config)
|
|
81
93
|
rescue StandardError
|
|
@@ -203,8 +203,8 @@ module Kaui
|
|
|
203
203
|
render json: response, status: response_status
|
|
204
204
|
end
|
|
205
205
|
|
|
206
|
-
def default_columns(fields,
|
|
207
|
-
fields.map { |field| { data:
|
|
206
|
+
def default_columns(fields, visible_fields)
|
|
207
|
+
fields.each_with_index.map { |field, index| { data: index, visible: visible_fields.include?(field) } }
|
|
208
208
|
end
|
|
209
209
|
end
|
|
210
210
|
end
|
|
@@ -10,6 +10,8 @@ module Kaui
|
|
|
10
10
|
@offset = params[:offset] || 0
|
|
11
11
|
@limit = params[:limit] || 50
|
|
12
12
|
@search_fields = Kaui::Invoice::ADVANCED_SEARCH_COLUMNS.map { |attr| [attr, attr.split('_').join(' ').capitalize] }
|
|
13
|
+
@dropdown_default = default_columns(Kaui.account_invoices_columns.call[3], Kaui::Invoice::DEFAULT_VISIBLE_COLUMNS)
|
|
14
|
+
@visible_columns = @dropdown_default
|
|
13
15
|
|
|
14
16
|
@max_nb_records = @search_query.blank? ? Kaui::Invoice.list_or_search(nil, 0, 0, options_for_klient).pagination_max_nb_records : 0
|
|
15
17
|
end
|
|
@@ -11,6 +11,8 @@ module Kaui
|
|
|
11
11
|
@offset = params[:offset] || 0
|
|
12
12
|
@limit = params[:limit] || 50
|
|
13
13
|
@search_fields = Kaui::Payment::ADVANCED_SEARCH_COLUMNS.map { |attr| [attr, attr.split('_').join(' ').capitalize] }
|
|
14
|
+
@dropdown_default = default_columns(Kaui.account_payments_columns.call[3], Kaui::Payment::DEFAULT_VISIBLE_COLUMNS)
|
|
15
|
+
@visible_columns = @dropdown_default
|
|
14
16
|
|
|
15
17
|
@max_nb_records = @search_query.blank? ? Kaui::Payment.list_or_search(nil, 0, 0, options_for_klient).pagination_max_nb_records : 0
|
|
16
18
|
end
|
|
@@ -181,7 +181,12 @@ module Kaui
|
|
|
181
181
|
|
|
182
182
|
def record_usage
|
|
183
183
|
@subscription = Kaui::Subscription.find_by_id(params.require(:id), 'NONE', options_for_klient)
|
|
184
|
-
@
|
|
184
|
+
@is_aviate_catalog = Dependencies::Aviate::Metering.aviate_catalog?(@subscription.account_id, options_for_aviate_klient)
|
|
185
|
+
@unit_types = if @is_aviate_catalog
|
|
186
|
+
Dependencies::Aviate::Metering.billing_meter_codes_for_plan(@subscription.plan_name, @subscription.account_id, options_for_aviate_klient)
|
|
187
|
+
else
|
|
188
|
+
fetch_unit_types_from_subscription(@subscription)
|
|
189
|
+
end
|
|
185
190
|
end
|
|
186
191
|
|
|
187
192
|
def create_usage
|
|
@@ -189,56 +194,65 @@ module Kaui
|
|
|
189
194
|
unit_type = params[:unit_type].to_s.strip
|
|
190
195
|
amount_raw = params[:amount].to_s.strip
|
|
191
196
|
record_date = params[:record_date].to_s.strip
|
|
197
|
+
tracking_id = params[:tracking_id].to_s.strip
|
|
198
|
+
|
|
199
|
+
@subscription = Kaui::Subscription.find_by_id(subscription_id, 'NONE', options_for_klient)
|
|
200
|
+
@is_aviate_catalog = Dependencies::Aviate::Metering.aviate_catalog?(@subscription.account_id, options_for_aviate_klient)
|
|
192
201
|
|
|
193
202
|
# Input validation
|
|
194
203
|
errors = []
|
|
195
|
-
errors << 'Unit type is required' if unit_type.blank?
|
|
204
|
+
errors << (@is_aviate_catalog ? 'Billing meter code is required' : 'Unit type is required') if unit_type.blank?
|
|
196
205
|
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
|
|
206
|
+
amount = @is_aviate_catalog ? parse_usage_amount(amount_raw) : Integer(amount_raw, exception: false)
|
|
207
|
+
errors << (@is_aviate_catalog ? 'Amount must be a positive number' : 'Amount must be a positive integer') if amount.nil? || amount <= 0
|
|
199
208
|
errors << 'Date/time of usage is required' if record_date.blank?
|
|
200
209
|
parsed_date = parse_usage_date(record_date) if record_date.present?
|
|
201
210
|
errors << 'Date/time of usage must be a valid date or datetime' if record_date.present? && parsed_date.nil?
|
|
211
|
+
errors << 'Tracking ID must be a valid UUID' if @is_aviate_catalog && tracking_id.present? && !tracking_id.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
|
|
202
212
|
|
|
203
213
|
if errors.any?
|
|
204
214
|
flash.now[:error] = errors.join('. ')
|
|
205
|
-
@
|
|
206
|
-
@unit_types = fetch_unit_types_from_subscription(@subscription)
|
|
215
|
+
@unit_types = fetch_unit_types_for_record_usage_form(@subscription, @is_aviate_catalog)
|
|
207
216
|
@unit_type = unit_type
|
|
208
217
|
@amount = amount_raw
|
|
209
218
|
@record_date = record_date
|
|
210
|
-
@tracking_id =
|
|
219
|
+
@tracking_id = tracking_id
|
|
211
220
|
render :record_usage and return
|
|
212
221
|
end
|
|
213
222
|
|
|
214
223
|
begin
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
if @is_aviate_catalog
|
|
225
|
+
# trackingId is required by the Aviate metering API; auto-generate one if the user left it blank
|
|
226
|
+
tracking_id = SecureRandom.uuid if tracking_id.blank?
|
|
227
|
+
Dependencies::Aviate::Metering.submit_usage_event(@subscription.account_id, unit_type, subscription_id, tracking_id,
|
|
228
|
+
parsed_date.utc.strftime('%Y-%m-%dT%H:%M:%S'), amount.to_f, options_for_aviate_klient)
|
|
229
|
+
else
|
|
230
|
+
usage_record = KillBillClient::Model::UsageRecordAttributes.new
|
|
231
|
+
usage_record.record_date = parsed_date.utc.iso8601
|
|
232
|
+
usage_record.amount = amount.to_i
|
|
218
233
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
234
|
+
unit_usage_record = KillBillClient::Model::UnitUsageRecordAttributes.new
|
|
235
|
+
unit_usage_record.unit_type = unit_type
|
|
236
|
+
unit_usage_record.usage_records = [usage_record]
|
|
222
237
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
usage = Kaui::Usage.new
|
|
239
|
+
usage.subscription_id = subscription_id
|
|
240
|
+
usage.tracking_id = tracking_id.presence
|
|
241
|
+
usage.unit_usage_records = [unit_usage_record]
|
|
227
242
|
|
|
228
|
-
|
|
243
|
+
usage.create(current_user.kb_username, nil, nil, options_for_klient)
|
|
244
|
+
end
|
|
229
245
|
|
|
230
|
-
|
|
231
|
-
redirect_to kaui_engine.account_bundles_path(subscription.account_id), notice: 'Usage was successfully recorded'
|
|
246
|
+
redirect_to kaui_engine.account_bundles_path(@subscription.account_id), notice: 'Usage was successfully recorded'
|
|
232
247
|
rescue StandardError => e
|
|
233
248
|
Rails.logger.error("Failed to record usage for subscription #{subscription_id}: #{e.class}: #{e.message}")
|
|
234
249
|
Rails.logger.error(e.backtrace.join("\n")) if e.backtrace
|
|
235
250
|
flash.now[:error] = "Error while recording usage: #{as_string(e)}"
|
|
236
|
-
@
|
|
237
|
-
@unit_types = fetch_unit_types_from_subscription(@subscription)
|
|
251
|
+
@unit_types = fetch_unit_types_for_record_usage_form(@subscription, @is_aviate_catalog)
|
|
238
252
|
@unit_type = unit_type
|
|
239
253
|
@amount = amount_raw
|
|
240
254
|
@record_date = record_date
|
|
241
|
-
@tracking_id =
|
|
255
|
+
@tracking_id = tracking_id
|
|
242
256
|
render :record_usage
|
|
243
257
|
end
|
|
244
258
|
end
|
|
@@ -411,6 +425,27 @@ module Kaui
|
|
|
411
425
|
(phase.prices || []).empty?
|
|
412
426
|
end
|
|
413
427
|
|
|
428
|
+
def fetch_unit_types_for_record_usage_form(subscription, is_aviate_catalog)
|
|
429
|
+
if is_aviate_catalog
|
|
430
|
+
Dependencies::Aviate::Metering.billing_meter_codes_for_plan(subscription.plan_name, subscription.account_id, options_for_aviate_klient)
|
|
431
|
+
else
|
|
432
|
+
fetch_unit_types_from_subscription(subscription)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# The Aviate plugin's catalog/metering endpoints require a JWT (obtained by logging in via the
|
|
437
|
+
# Aviate Configuration page in killbill-aviate-ui), stored in a jwt_token cookie - same convention
|
|
438
|
+
# as Aviate::EngineController#options_for_klient in that engine.
|
|
439
|
+
def options_for_aviate_klient
|
|
440
|
+
options_for_klient(jwt_token: cookies[:jwt_token])
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def parse_usage_amount(amount_raw)
|
|
444
|
+
BigDecimal(amount_raw, exception: false)
|
|
445
|
+
rescue ArgumentError
|
|
446
|
+
nil
|
|
447
|
+
end
|
|
448
|
+
|
|
414
449
|
def fetch_unit_types_from_subscription(subscription)
|
|
415
450
|
unit_types = []
|
|
416
451
|
(subscription.prices || []).each do |phase_price|
|
data/app/models/kaui/account.rb
CHANGED
|
@@ -4,7 +4,9 @@ module Kaui
|
|
|
4
4
|
class Account < KillBillClient::Model::Account
|
|
5
5
|
attr_accessor :phone, :bill_cycle_day_local
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# Columns shown by default on the Accounts list screen (demo-friendly defaults); the rest remain
|
|
8
|
+
# available but hidden until the user opts in via "Edit Columns".
|
|
9
|
+
DEFAULT_VISIBLE_COLUMNS = %w[name account_id external_key currency time_zone locale account_balance account_cba].freeze
|
|
8
10
|
REMAPPING_FIELDS = {
|
|
9
11
|
'is_payment_delegated_to_parent' => 'pay_via_parent',
|
|
10
12
|
'account_balance' => 'balance',
|
|
@@ -23,6 +23,16 @@ module Kaui
|
|
|
23
23
|
KillBillClient::Model::Invoice.upload_invoice_translation(invoice_translation, locale, delete_if_exists, user, reason, comment, options)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# Return a map of locale => invoice translation content, for every invoice translation uploaded for the tenant
|
|
27
|
+
def get_invoice_translations(options = {})
|
|
28
|
+
raw_translations = KillBillClient::Model::Tenant.search_tenant_config('INVOICE_TRANSLATION_', options)
|
|
29
|
+
|
|
30
|
+
raw_translations.each_with_object({}) do |e, hsh|
|
|
31
|
+
locale = e.key.gsub('INVOICE_TRANSLATION_', '')
|
|
32
|
+
hsh[locale] = e.values[0]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
26
36
|
def upload_catalog_translation(catalog_translation, locale, delete_if_exists, user = nil, reason = nil, comment = nil, options = {})
|
|
27
37
|
KillBillClient::Model::Invoice.upload_catalog_translation(catalog_translation, locale, delete_if_exists, user, reason, comment, options)
|
|
28
38
|
end
|
data/app/models/kaui/invoice.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
module Kaui
|
|
4
4
|
class Invoice < KillBillClient::Model::Invoice
|
|
5
5
|
TABLE_IGNORE_COLUMNS = %w[amount balance credit_adj refund_adj items is_parent_invoice parent_invoice_id parent_account_id audit_logs bundle_keys].freeze
|
|
6
|
+
# Columns shown by default on the Invoices list screen (demo-friendly defaults); the rest remain
|
|
7
|
+
# available but hidden until the user opts in via "Edit Columns".
|
|
8
|
+
DEFAULT_VISIBLE_COLUMNS = %w[invoice_number invoice_id status invoice_date target_date currency account_id].freeze
|
|
6
9
|
ADVANCED_SEARCH_COLUMNS = %w[id account_id invoice_date target_date currency status balance].freeze
|
|
7
10
|
ADVANCED_SEARCH_NAME_CHANGES = [%w[ac_id account_id]].freeze
|
|
8
11
|
|
data/app/models/kaui/payment.rb
CHANGED
|
@@ -7,6 +7,9 @@ module Kaui
|
|
|
7
7
|
attr_accessor :payment_date, :target_invoice_id
|
|
8
8
|
|
|
9
9
|
TRANSACTION_STATUSES = %w[SUCCESS PENDING PAYMENT_FAILURE PLUGIN_FAILURE UNKNOWN].freeze
|
|
10
|
+
# Columns shown by default on the Payments list screen (demo-friendly defaults); the rest remain
|
|
11
|
+
# available but hidden until the user opts in via "Edit Columns".
|
|
12
|
+
DEFAULT_VISIBLE_COLUMNS = %w[payment_date payment_number status account_id currency purchased_amount refunded_amount credited_amount].freeze
|
|
10
13
|
REMAPPING_FIELDS = {
|
|
11
14
|
'auth_amount' => 'auth',
|
|
12
15
|
'captured_amount' => 'capture',
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dependencies
|
|
4
|
+
module Aviate
|
|
5
|
+
# Thin wrapper around the Aviate plugin's Catalog and Metering REST APIs
|
|
6
|
+
# (https://apidocs.killbill.io/aviate-catalog.html, https://apidocs.killbill.io/aviate-metering.html).
|
|
7
|
+
# These are not (yet) exposed by the killbill-client gem, so we call them directly.
|
|
8
|
+
#
|
|
9
|
+
# Unlike most Kill Bill APIs, these endpoints do NOT accept tenant RBAC (username/password) or
|
|
10
|
+
# api_key/api_secret alone - they require a JWT obtained via the Aviate Auth API
|
|
11
|
+
# (https://apidocs.killbill.io/aviate-auth.html), passed as a `jwt_token` entry in options_for_klient
|
|
12
|
+
# (see Aviate::EngineController#options_for_klient in killbill-aviate-ui, which stores it in a
|
|
13
|
+
# `jwt_token` cookie after logging in via the Aviate Configuration page). If no jwt_token is present,
|
|
14
|
+
# calls will fail authentication and callers should treat that as "unknown/not Aviate".
|
|
15
|
+
class Metering
|
|
16
|
+
KILLBILL_AVIATE_PREFIX = '/plugins/aviate-plugin/v1'
|
|
17
|
+
CATALOG_STATE_AVIATE = 'CATALOG_STATE_AVIATE'
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# Returns true if the tenant/account is using an Aviate catalog (as opposed to an XML one).
|
|
21
|
+
# Any error (plugin not installed, missing/invalid JWT, network issue, etc.) is treated as
|
|
22
|
+
# "not Aviate" so that callers can safely fall back to the existing XML-catalog behavior.
|
|
23
|
+
def aviate_catalog?(account_id, options_for_klient)
|
|
24
|
+
response = KillBillClient::API.get("#{KILLBILL_AVIATE_PREFIX}/catalog/info", catalog_params(account_id), request_options(options_for_klient))
|
|
25
|
+
JSON.parse(response.body)['state'] == CATALOG_STATE_AVIATE
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
Rails.logger.warn("Failed to retrieve Aviate catalog info: #{e.class}: #{e.message}")
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns the list of billing meter codes configured on the given plan's usage phases.
|
|
32
|
+
def billing_meter_codes_for_plan(plan_name, account_id, options_for_klient)
|
|
33
|
+
return [] if plan_name.blank?
|
|
34
|
+
|
|
35
|
+
response = KillBillClient::API.get("#{KILLBILL_AVIATE_PREFIX}/catalog/#{plan_name}/plan", catalog_params(account_id), request_options(options_for_klient))
|
|
36
|
+
plan = JSON.parse(response.body)
|
|
37
|
+
|
|
38
|
+
codes = []
|
|
39
|
+
Array(plan['phases']).each do |phase|
|
|
40
|
+
Array(phase['usages']).each do |usage|
|
|
41
|
+
Array(usage['tiers']).each do |tier|
|
|
42
|
+
Array(tier['blocks']).each do |block|
|
|
43
|
+
codes << block['billingMeterCode'] if block['billingMeterCode'].present?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
codes.uniq
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
Rails.logger.warn("Failed to retrieve Aviate billing meter codes for plan #{plan_name}: #{e.class}: #{e.message}")
|
|
51
|
+
[]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Submits a single usage event via the Aviate metering plugin.
|
|
55
|
+
def submit_usage_event(account_id, billing_meter_code, subscription_id, tracking_id, timestamp, value, options_for_klient)
|
|
56
|
+
body = [{
|
|
57
|
+
billingMeterCode: billing_meter_code,
|
|
58
|
+
subscriptionId: subscription_id,
|
|
59
|
+
trackingId: tracking_id,
|
|
60
|
+
timestamp: timestamp,
|
|
61
|
+
value: value
|
|
62
|
+
}].to_json
|
|
63
|
+
|
|
64
|
+
KillBillClient::API.post("#{KILLBILL_AVIATE_PREFIX}/metering/billing/#{account_id}", body, {}, request_options(options_for_klient))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def catalog_params(account_id)
|
|
70
|
+
account_id.present? ? { accountId: account_id } : {}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Builds request options scoped to just api_key/api_secret + the Aviate JWT bearer header.
|
|
74
|
+
# Deliberately excludes username/password/session_id from options_for_klient: KillBillClient's
|
|
75
|
+
# net_http_adapter calls request.basic_auth(username, password) when they're present, which
|
|
76
|
+
# would overwrite (clobber) the Authorization header we set below for the Bearer token.
|
|
77
|
+
def request_options(options_for_klient)
|
|
78
|
+
request_options = {
|
|
79
|
+
api_key: options_for_klient[:api_key],
|
|
80
|
+
api_secret: options_for_klient[:api_secret]
|
|
81
|
+
}
|
|
82
|
+
jwt_token = options_for_klient[:jwt_token]
|
|
83
|
+
request_options[:head] = { 'Authorization' => "Bearer #{jwt_token}" } if jwt_token.present?
|
|
84
|
+
request_options
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
$(document).ready(function() {
|
|
64
64
|
var tags = JSON.parse($("#tags").val());
|
|
65
65
|
var table = $('#tags-table').DataTable({
|
|
66
|
-
"dom": "<'row'<'col-md-6'
|
|
66
|
+
"dom": "<'row'<'col-md-6'f>r>t<'row'<'col-md-6'i><'col-md-6'p>>",
|
|
67
67
|
data: tags
|
|
68
68
|
});
|
|
69
69
|
|
|
@@ -378,7 +378,7 @@ $(document).ready(function() {
|
|
|
378
378
|
updateDropdownOrder();
|
|
379
379
|
|
|
380
380
|
function updateDropdownOrder() {
|
|
381
|
-
var state = JSON.parse(localStorage.getItem('DataTables_accounts-
|
|
381
|
+
var state = JSON.parse(localStorage.getItem('DataTables_accounts-table-v2_' + window.location.pathname));
|
|
382
382
|
if (state === null) {
|
|
383
383
|
// On initial load, ensure the existing checkboxes work
|
|
384
384
|
setupColumnToggleEvents();
|
|
@@ -393,7 +393,7 @@ $(document).ready(function() {
|
|
|
393
393
|
|
|
394
394
|
if (columnOrder !== undefined) {
|
|
395
395
|
$list.empty();
|
|
396
|
-
var state = JSON.parse(localStorage.getItem('DataTables_accounts-
|
|
396
|
+
var state = JSON.parse(localStorage.getItem('DataTables_accounts-table-v2_' + window.location.pathname));
|
|
397
397
|
if (state !== null) {
|
|
398
398
|
var colsOrder = state.ColReorder;
|
|
399
399
|
}
|
|
@@ -319,7 +319,7 @@ $(document).ready(function() {
|
|
|
319
319
|
updateDropdownOrder();
|
|
320
320
|
|
|
321
321
|
function updateDropdownOrder() {
|
|
322
|
-
var state = JSON.parse(localStorage.getItem('DataTables_accounts-
|
|
322
|
+
var state = JSON.parse(localStorage.getItem('DataTables_accounts-table-v2_' + window.location.pathname));
|
|
323
323
|
if (state === null) {
|
|
324
324
|
return;
|
|
325
325
|
}
|
|
@@ -332,7 +332,7 @@ $(document).ready(function() {
|
|
|
332
332
|
|
|
333
333
|
if (columnOrder !== undefined) {
|
|
334
334
|
$list.empty();
|
|
335
|
-
var state = JSON.parse(localStorage.getItem('DataTables_accounts-
|
|
335
|
+
var state = JSON.parse(localStorage.getItem('DataTables_accounts-table-v2_' + window.location.pathname));
|
|
336
336
|
if (state !== null) {
|
|
337
337
|
var colsOrder = state.ColReorder;
|
|
338
338
|
}
|
|
@@ -79,11 +79,12 @@ $(document).ready(function() {
|
|
|
79
79
|
"enable": false
|
|
80
80
|
},
|
|
81
81
|
"stateSave": true,
|
|
82
|
+
"stateDuration": -1,
|
|
82
83
|
"stateSaveCallback": function(settings, data) {
|
|
83
|
-
localStorage.setItem('DataTables_accounts-
|
|
84
|
+
localStorage.setItem('DataTables_accounts-table-v2_' + window.location.pathname, JSON.stringify(data));
|
|
84
85
|
},
|
|
85
86
|
"stateLoadCallback": function(settings) {
|
|
86
|
-
return JSON.parse(localStorage.getItem('DataTables_accounts-
|
|
87
|
+
return JSON.parse(localStorage.getItem('DataTables_accounts-table-v2_' + window.location.pathname));
|
|
87
88
|
},
|
|
88
89
|
"scrollX": true,
|
|
89
90
|
"dom": "<'row'r>t<'row'<'col-md-6'i><'col-md-6'p>>",
|
|
@@ -131,11 +132,10 @@ $(document).ready(function() {
|
|
|
131
132
|
// If the page loaded with advanced search params in the URL (e.g. restored from
|
|
132
133
|
// localStorage) but @advance_search_query was not set server-side, re-fire the
|
|
133
134
|
// DataTable request with the correct filters.
|
|
134
|
-
if (window.location.search.includes('
|
|
135
|
-
var urlSearch = window.location.search.
|
|
135
|
+
if (window.location.search.includes('advance_search_query=') && '<%= j(@advance_search_query.to_s.strip) %>' === '') {
|
|
136
|
+
var urlSearch = new URLSearchParams(window.location.search).get('advance_search_query') || '';
|
|
136
137
|
var ajaxUrl = "<%= accounts_pagination_path(:ordering => @ordering, :format => :json) %>";
|
|
137
138
|
table.on('preXhr.dt.filter', function(e, settings, data) {
|
|
138
|
-
data.search.value = urlSearch;
|
|
139
139
|
data.advance_search_query = urlSearch;
|
|
140
140
|
});
|
|
141
141
|
table.ajax.url(ajaxUrl).load();
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
<div class="tab-pane fade invoice-template form-invoice-template" id="InvoiceTemplate">
|
|
3
3
|
<% if @invoice_template.present? %>
|
|
4
4
|
<div class="invoice-template-display mb-4">
|
|
5
|
-
<h5>Current Invoice Template</h5>
|
|
6
5
|
<div class="card">
|
|
7
6
|
<div class="card-body p-0">
|
|
8
7
|
<iframe src="<%= admin_tenant_invoice_template_path(@tenant.id) %>" style="width: 100%; height: 500px; border: none;" title="Invoice Template Preview"></iframe>
|
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
<% if can? :config_upload, Kaui::AdminTenant %>
|
|
2
2
|
<div class="tab-pane fade" id="InvoiceTranslation">
|
|
3
|
+
<div class="existing-invoice-translations-container mb-4">
|
|
4
|
+
<div class="d-flex flex-column">
|
|
5
|
+
<div class="existing-invoice-translations-header mb-3">
|
|
6
|
+
<div class="d-flex align-items-start flex-column">
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
<div>
|
|
10
|
+
<table id="existing-invoice-translations-for-tenants" class="existing-invoice-translations-table">
|
|
11
|
+
<% if @invoice_translations.blank? %>
|
|
12
|
+
<tbody><tr><td>No invoice translation defined for tenant</td></tr></tbody>
|
|
13
|
+
<% else %>
|
|
14
|
+
<thead>
|
|
15
|
+
<tr>
|
|
16
|
+
<th>Locale</th>
|
|
17
|
+
<th></th>
|
|
18
|
+
</tr>
|
|
19
|
+
</thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
<% @invoice_translations.each do |locale, content| %>
|
|
22
|
+
<tr>
|
|
23
|
+
<td><%= locale %></td>
|
|
24
|
+
<td class="text-end">
|
|
25
|
+
<%= render "kaui/components/button/button", {
|
|
26
|
+
label: 'View Source',
|
|
27
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
28
|
+
type: "button",
|
|
29
|
+
html_class: "kaui-button custom-hover",
|
|
30
|
+
html_options: {
|
|
31
|
+
onclick: '$(this).blur(); var $row = $(this).closest("tr").next(".invoice-translation-source-row"); $row.toggle(); $(this).find(".kaui-button-label").text($row.is(":visible") ? "Hide Source" : "View Source"); return false;'
|
|
32
|
+
}
|
|
33
|
+
} %>
|
|
34
|
+
</td>
|
|
35
|
+
</tr>
|
|
36
|
+
<tr class="invoice-translation-source-row" style="display: none;">
|
|
37
|
+
<td colspan="2">
|
|
38
|
+
<pre class="mb-0 p-2 bg-light" style="max-height: 400px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; font-family: monospace; font-size: 12px;"><%= html_escape(content) %></pre>
|
|
39
|
+
</td>
|
|
40
|
+
</tr>
|
|
41
|
+
<% end %>
|
|
42
|
+
</tbody>
|
|
43
|
+
<% end %>
|
|
44
|
+
</table>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
3
49
|
<%= form_tag({:action => :upload_invoice_translation}, :method => 'post', :multipart => true, :class => 'form-horizontal') do %>
|
|
4
50
|
<%= hidden_field_tag(:id, @tenant.id) %>
|
|
5
51
|
|
|
@@ -21,7 +67,7 @@
|
|
|
21
67
|
<div class="form-group d-flex pb-3 border-bottom mb-3">
|
|
22
68
|
<%= label_tag :translation_locale, 'Locale', :class => 'col-sm-2 control-label' %>
|
|
23
69
|
<div class="col-sm-10">
|
|
24
|
-
<%=
|
|
70
|
+
<%= select_tag :translation_locale, options_for_select([['-- Select a locale --', '']] + all_available_locales, { disabled: ['---------------'] }), class: 'form-control', required: true %>
|
|
25
71
|
</div>
|
|
26
72
|
</div>
|
|
27
73
|
<div class="d-flex justify-content-end">
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
$(document).ready(function() {
|
|
104
104
|
var auditLogs = JSON.parse($("#audit-logs").val());
|
|
105
105
|
var table = $('#audit-logs-table').DataTable({
|
|
106
|
-
dom: "<'row'<'col-md-6'
|
|
106
|
+
"dom": "<'row'<'col-md-6'f>r>t<'row'<'col-md-6'i><'col-md-6'p>>",
|
|
107
107
|
pagingType: "full_numbers",
|
|
108
108
|
data: auditLogs,
|
|
109
109
|
"scrollX": true,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<% icon_html = icon.present? ? image_tag(icon, class: "me-1", size: "16x16") : "" %>
|
|
10
10
|
<% trailing_icon_html = trailing_icon.present? ? image_tag(trailing_icon, class: "ms-1", size: "16x16") : "" %>
|
|
11
11
|
|
|
12
|
-
<%= tag.button raw("#{icon_html}
|
|
12
|
+
<%= tag.button raw("#{icon_html}<span class=\"kaui-button-label\">#{label}</span>#{trailing_icon_html}"),
|
|
13
13
|
type: type,
|
|
14
14
|
class: "btn #{variant} #{html_class}",
|
|
15
15
|
**html_options
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
<%= javascript_tag do %>
|
|
83
83
|
$(document).ready(function() {
|
|
84
84
|
var table = $('#custom_fields-table').DataTable({
|
|
85
|
-
"dom": "<'row'<'col-md-6'
|
|
85
|
+
"dom": "<'row'<'col-md-6'f>r>t<'row'<'col-md-6'i><'col-md-6'p>>",
|
|
86
86
|
"pagingType": <% if @max_nb_records.nil? -%>"simple"<% else -%>"full_numbers"<% end -%>,
|
|
87
87
|
"language": {
|
|
88
88
|
<!-- See DefaultPaginationSqlDaoHelper.java -->
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
<% Kaui.account_invoices_columns.call[0].each_with_index do |title, index| %>
|
|
53
53
|
<li class="list-group-item-manual" data-id="<%= index %>">
|
|
54
54
|
<label class="label-group-item-manual">
|
|
55
|
-
<input type="checkbox" class="column-toggle" draggable="true" data-column="<%= index %>" checked
|
|
55
|
+
<input type="checkbox" class="column-toggle" draggable="true" data-column="<%= index %>" <%= 'checked' if @dropdown_default[index][:visible] %>>
|
|
56
56
|
<%= title %>
|
|
57
57
|
<span class="icon-drag" aria-hidden="true"></span>
|
|
58
58
|
</label>
|
|
@@ -337,7 +337,7 @@ $(document).ready(function() {
|
|
|
337
337
|
updateDropdownOrder();
|
|
338
338
|
|
|
339
339
|
function loadState() {
|
|
340
|
-
var state = JSON.parse(localStorage.getItem('DataTables_invoices-table'));
|
|
340
|
+
var state = JSON.parse(localStorage.getItem('DataTables_invoices-table-v2'));
|
|
341
341
|
return state || { columns: [], columnOrder: [] };
|
|
342
342
|
}
|
|
343
343
|
|
|
@@ -50,7 +50,7 @@ $(document).ready(function() {
|
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
var stateKey = 'DataTables_invoices-table';
|
|
53
|
+
var stateKey = 'DataTables_invoices-table-v2';
|
|
54
54
|
var state = JSON.parse(localStorage.getItem(stateKey));
|
|
55
55
|
if (state) {
|
|
56
56
|
state.start = <%= @offset %>;
|
|
@@ -63,6 +63,7 @@ $(document).ready(function() {
|
|
|
63
63
|
"enable": false
|
|
64
64
|
},
|
|
65
65
|
"stateSave": true,
|
|
66
|
+
"stateDuration": -1,
|
|
66
67
|
"stateSaveCallback": function(settings, data) {
|
|
67
68
|
localStorage.setItem(stateKey, JSON.stringify(data));
|
|
68
69
|
},
|
|
@@ -105,11 +106,11 @@ $(document).ready(function() {
|
|
|
105
106
|
// If the page loaded with advanced search params in the URL (e.g. restored from
|
|
106
107
|
// localStorage) but @advance_search_query was not set server-side, re-fire the
|
|
107
108
|
// DataTable request with the correct filters.
|
|
108
|
-
if (window.location.search.includes('
|
|
109
|
-
var urlSearch = window.location.search.
|
|
110
|
-
var ajaxUrl = "<%= invoices_pagination_path(:ordering => @ordering, :format => :json) %>" + '?' + urlSearch;
|
|
109
|
+
if (window.location.search.includes('advance_search_query=') && '<%= j(@advance_search_query.to_s.strip) %>' === '' && !window.location.pathname.includes('/accounts/')) {
|
|
110
|
+
var urlSearch = new URLSearchParams(window.location.search).get('advance_search_query') || '';
|
|
111
|
+
var ajaxUrl = "<%= invoices_pagination_path(:ordering => @ordering, :format => :json) %>" + '?advance_search_query=' + encodeURIComponent(urlSearch);
|
|
111
112
|
table.on('preXhr.dt.filter', function(e, settings, data) {
|
|
112
|
-
data.
|
|
113
|
+
data.advance_search_query = urlSearch;
|
|
113
114
|
});
|
|
114
115
|
table.ajax.url(ajaxUrl).load();
|
|
115
116
|
}
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
<% Kaui.account_payments_columns.call()[0].each_with_index do |title, index| %>
|
|
53
53
|
<li class="list-group-item-manual" data-id="<%= index %>">
|
|
54
54
|
<label class="label-group-item-manual">
|
|
55
|
-
<input type="checkbox" class="column-toggle" draggable="true" data-column="<%= index %>" checked
|
|
55
|
+
<input type="checkbox" class="column-toggle" draggable="true" data-column="<%= index %>" <%= 'checked' if @dropdown_default[index][:visible] %>>
|
|
56
56
|
<%= title %>
|
|
57
57
|
<span class="icon-drag" aria-hidden="true"></span>
|
|
58
58
|
</label>
|
|
@@ -338,7 +338,7 @@ $(document).ready(function() {
|
|
|
338
338
|
updateDropdownOrder();
|
|
339
339
|
|
|
340
340
|
function loadState() {
|
|
341
|
-
var state = JSON.parse(localStorage.getItem('DataTables_payments-table'));
|
|
341
|
+
var state = JSON.parse(localStorage.getItem('DataTables_payments-table-v2'));
|
|
342
342
|
return state || { columns: [], columnOrder: [] };
|
|
343
343
|
}
|
|
344
344
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<%= javascript_tag do %>
|
|
43
43
|
$(document).ready(function() {
|
|
44
|
-
var stateKey = 'DataTables_payments-table';
|
|
44
|
+
var stateKey = 'DataTables_payments-table-v2';
|
|
45
45
|
var state = JSON.parse(localStorage.getItem(stateKey));
|
|
46
46
|
if (state) {
|
|
47
47
|
state.start = <%= @offset %>;
|
|
@@ -54,11 +54,12 @@ $(document).ready(function() {
|
|
|
54
54
|
"enable": false
|
|
55
55
|
},
|
|
56
56
|
"stateSave": true,
|
|
57
|
+
"stateDuration": -1,
|
|
57
58
|
"stateSaveCallback": function(settings, data) {
|
|
58
|
-
localStorage.setItem(
|
|
59
|
+
localStorage.setItem(stateKey, JSON.stringify(data));
|
|
59
60
|
},
|
|
60
61
|
"stateLoadCallback": function(settings) {
|
|
61
|
-
return JSON.parse(localStorage.getItem(
|
|
62
|
+
return JSON.parse(localStorage.getItem(stateKey));
|
|
62
63
|
},
|
|
63
64
|
"scrollX": true,
|
|
64
65
|
<% if @account.account_id.blank? %>
|
|
@@ -114,17 +115,17 @@ $(document).ready(function() {
|
|
|
114
115
|
<% end %>
|
|
115
116
|
"processing": true,
|
|
116
117
|
"serverSide": true,
|
|
117
|
-
"search": {"search": "<%= @search_query %>"}
|
|
118
|
+
"search": {"search": "<%= @search_query %>"}
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
// If the page loaded with advanced search params in the URL (e.g. restored from
|
|
121
122
|
// localStorage) but @advance_search_query was not set server-side, re-fire the
|
|
122
123
|
// DataTable request with the correct filters.
|
|
123
|
-
if (window.location.search.includes('
|
|
124
|
-
var urlSearch = window.location.search.
|
|
125
|
-
var ajaxUrl = "<%= payments_pagination_path(:ordering => @ordering, :format => :json) %>" + '?' + urlSearch;
|
|
124
|
+
if (window.location.search.includes('advance_search_query=') && '<%= j(@advance_search_query.to_s.strip) %>' === '' && !window.location.pathname.includes('/accounts/')) {
|
|
125
|
+
var urlSearch = new URLSearchParams(window.location.search).get('advance_search_query') || '';
|
|
126
|
+
var ajaxUrl = "<%= payments_pagination_path(:ordering => @ordering, :format => :json) %>" + '?advance_search_query=' + encodeURIComponent(urlSearch);
|
|
126
127
|
table.on('preXhr.dt.filter', function(e, settings, data) {
|
|
127
|
-
data.
|
|
128
|
+
data.advance_search_query = urlSearch;
|
|
128
129
|
});
|
|
129
130
|
table.ajax.url(ajaxUrl).load();
|
|
130
131
|
}
|
|
@@ -191,19 +191,19 @@ $(document).ready(function() {
|
|
|
191
191
|
var table = $('<%= table_selector %>').DataTable();
|
|
192
192
|
table.off('preXhr.dt.filter');
|
|
193
193
|
table.on('preXhr.dt.filter', function(e, settings, data) {
|
|
194
|
-
data.
|
|
194
|
+
data.advance_search_query = decodeURIComponent(searchQuery('<%= j search_query.to_s %>'));
|
|
195
195
|
});
|
|
196
196
|
|
|
197
197
|
var searchParams = searchQuery('<%= j search_query.to_s %>');
|
|
198
|
+
var rawSearchParams = searchParams ? decodeURIComponent(searchParams) : searchParams;
|
|
198
199
|
var ajaxUrl = "<%= pagination_path %>";
|
|
199
|
-
if (
|
|
200
|
-
ajaxUrl += (ajaxUrl.includes('?') ? '&' : '?') +
|
|
200
|
+
if (rawSearchParams) {
|
|
201
|
+
ajaxUrl += (ajaxUrl.includes('?') ? '&' : '?') + 'advance_search_query=' + encodeURIComponent(rawSearchParams);
|
|
201
202
|
}
|
|
202
203
|
table.ajax.url(ajaxUrl).load();
|
|
203
204
|
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + searchParams;
|
|
205
|
+
if (rawSearchParams) {
|
|
206
|
+
var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?advance_search_query=' + encodeURIComponent(rawSearchParams);
|
|
207
207
|
window.history.pushState({ path: newUrl }, '', newUrl);
|
|
208
208
|
}
|
|
209
209
|
|
|
@@ -22,22 +22,34 @@
|
|
|
22
22
|
<%= form_tag create_usage_path(@subscription.subscription_id), method: :post, id: 'record_usage_form', class: 'form-horizontal' do %>
|
|
23
23
|
|
|
24
24
|
<div class="form-group d-flex pb-3">
|
|
25
|
-
<%= label_tag :unit_type, 'Unit Type', class: 'col-sm-3 control-label' %>
|
|
25
|
+
<%= label_tag :unit_type, (@is_aviate_catalog ? 'Billing Meter Code' : 'Unit Type'), class: 'col-sm-3 control-label' %>
|
|
26
26
|
<div class="col-sm-9">
|
|
27
27
|
<% if @unit_types.length == 1 %>
|
|
28
28
|
<%= text_field_tag :unit_type, @unit_types.first, readonly: true, required: true, class: 'form-control bg-light' %>
|
|
29
29
|
<% elsif @unit_types.length > 1 %>
|
|
30
30
|
<%= select_tag :unit_type, options_for_select(@unit_types.map { |u| [u, u] }, @unit_type.presence || @unit_types.first), required: true, class: 'form-control', title: (@unit_type.presence || @unit_types.first) %>
|
|
31
31
|
<% else %>
|
|
32
|
-
<%= text_field_tag :unit_type, @unit_type, required: true, maxlength: 255, class: 'form-control', placeholder: 'e.g. api-calls' %>
|
|
32
|
+
<%= text_field_tag :unit_type, @unit_type, required: true, maxlength: 255, class: 'form-control', placeholder: (@is_aviate_catalog ? 'e.g. api-calls-meter' : 'e.g. api-calls') %>
|
|
33
33
|
<% end %>
|
|
34
|
+
<small class="form-text text-muted">
|
|
35
|
+
<% if @is_aviate_catalog %>
|
|
36
|
+
This subscription's plan is billed via an Aviate catalog: usage is recorded against a <strong>billing meter code</strong> instead of a unit type.
|
|
37
|
+
<% else %>
|
|
38
|
+
This subscription's plan is billed via a standard (XML) catalog: usage is recorded against a <strong>unit type</strong>.
|
|
39
|
+
<% end %>
|
|
40
|
+
<a href="#" data-toggle="tooltip" class="kb-tooltip" title="Kaui automatically detects the catalog type for this subscription's account and submits usage through the matching Kill Bill API: the Record Usage API (unit type) for standard catalogs, or the Aviate Submit Usage Events API (billing meter code) for Aviate catalogs."></a>
|
|
41
|
+
</small>
|
|
34
42
|
</div>
|
|
35
43
|
</div>
|
|
36
44
|
|
|
37
45
|
<div class="form-group d-flex pb-3">
|
|
38
46
|
<%= label_tag :amount, 'Amount', class: 'col-sm-3 control-label' %>
|
|
39
47
|
<div class="col-sm-9">
|
|
40
|
-
|
|
48
|
+
<% if @is_aviate_catalog %>
|
|
49
|
+
<%= number_field_tag :amount, @amount, required: true, min: 0, step: 'any', class: 'form-control', placeholder: 'Positive number' %>
|
|
50
|
+
<% else %>
|
|
51
|
+
<%= number_field_tag :amount, @amount, required: true, min: 1, step: 1, class: 'form-control', placeholder: 'Positive integer' %>
|
|
52
|
+
<% end %>
|
|
41
53
|
</div>
|
|
42
54
|
</div>
|
|
43
55
|
|
|
@@ -84,10 +96,12 @@
|
|
|
84
96
|
<div class="form-group d-flex pb-3">
|
|
85
97
|
<%= label_tag :tracking_id, 'Tracking ID', class: 'col-sm-3 control-label' %>
|
|
86
98
|
<div class="col-sm-9">
|
|
87
|
-
<%= text_field_tag :tracking_id, @tracking_id, maxlength: 255, class: 'form-control', placeholder: 'Optional, used for idempotency' %>
|
|
99
|
+
<%= text_field_tag :tracking_id, @tracking_id, maxlength: 255, class: 'form-control', placeholder: (@is_aviate_catalog ? 'Optional UUID, auto-generated if left blank' : 'Optional, used for idempotency') %>
|
|
88
100
|
</div>
|
|
89
101
|
</div>
|
|
90
102
|
|
|
103
|
+
<%= hidden_field_tag :is_aviate_catalog, @is_aviate_catalog.to_s %>
|
|
104
|
+
|
|
91
105
|
<div class="form-group d-flex justify-content-end pb-3">
|
|
92
106
|
<%= render "kaui/components/button/button", {
|
|
93
107
|
label: 'Close',
|
|
@@ -113,8 +127,11 @@
|
|
|
113
127
|
|
|
114
128
|
<%= javascript_tag do %>
|
|
115
129
|
document.addEventListener('DOMContentLoaded', function () {
|
|
130
|
+
$('[data-toggle="tooltip"]').tooltip();
|
|
131
|
+
|
|
116
132
|
var form = document.getElementById('record_usage_form');
|
|
117
133
|
if (!form) { return; }
|
|
134
|
+
var isAviateCatalog = document.getElementById('is_aviate_catalog') && document.getElementById('is_aviate_catalog').value === 'true';
|
|
118
135
|
var unitTypeSelect = document.getElementById('unit_type');
|
|
119
136
|
if (unitTypeSelect && unitTypeSelect.tagName === 'SELECT') {
|
|
120
137
|
unitTypeSelect.title = unitTypeSelect.value || '';
|
|
@@ -129,13 +146,20 @@
|
|
|
129
146
|
var unitTypeEl = document.getElementById('unit_type');
|
|
130
147
|
var unitType = unitTypeEl ? (unitTypeEl.value || '').trim() : '';
|
|
131
148
|
if (unitType.length === 0) {
|
|
132
|
-
errors.push('Unit type is required.');
|
|
149
|
+
errors.push(isAviateCatalog ? 'Billing meter code is required.' : 'Unit type is required.');
|
|
133
150
|
}
|
|
134
151
|
|
|
135
152
|
var amountStr = (document.getElementById('amount').value || '').trim();
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
if (isAviateCatalog) {
|
|
154
|
+
var floatAmount = parseFloat(amountStr);
|
|
155
|
+
if (amountStr.length === 0 || isNaN(floatAmount) || floatAmount <= 0) {
|
|
156
|
+
errors.push('Amount must be a positive number.');
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
var amount = parseInt(amountStr, 10);
|
|
160
|
+
if (amountStr.length === 0 || isNaN(amount) || amount <= 0 || String(amount) !== amountStr) {
|
|
161
|
+
errors.push('Amount must be a positive integer.');
|
|
162
|
+
}
|
|
139
163
|
}
|
|
140
164
|
|
|
141
165
|
var dateEl = document.getElementById('record_date_date');
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
<%= javascript_tag do %>
|
|
72
72
|
$(document).ready(function() {
|
|
73
73
|
var table = $('#tags-table').DataTable({
|
|
74
|
-
"dom": "<'row'<'col-md-6'
|
|
74
|
+
"dom": "<'row'<'col-md-6'f>r>t<'row'<'col-md-6'i><'col-md-6'p>>",
|
|
75
75
|
"pagingType": <% if @max_nb_records.nil? -%>"simple"<% else -%>"full_numbers"<% end -%>,
|
|
76
76
|
"language": {
|
|
77
77
|
"info": <% if @max_nb_records.nil? -%>"Showing _START_ to _END_ of <%= number_with_delimiter(Kaui::EngineControllerUtil::SIMPLE_PAGINATION_THRESHOLD) -%>+ entries"<% else -%>"Showing _START_ to _END_ of _TOTAL_ entries"<% end -%>
|
data/lib/kaui/version.rb
CHANGED
data/lib/kaui.rb
CHANGED
|
@@ -66,6 +66,8 @@ module Kaui
|
|
|
66
66
|
# Add additional fields if needed
|
|
67
67
|
fields = original_fields.dup
|
|
68
68
|
fields -= %w[audit_logs first_name_length]
|
|
69
|
+
# Demo-friendly default column order (see Kaui::Account::DEFAULT_VISIBLE_COLUMNS for the ones shown by default)
|
|
70
|
+
fields = Kaui::Account::DEFAULT_VISIBLE_COLUMNS + (fields - Kaui::Account::DEFAULT_VISIBLE_COLUMNS)
|
|
69
71
|
headers = fields.dup
|
|
70
72
|
Kaui::Account::REMAPPING_FIELDS.each do |k, v|
|
|
71
73
|
headers[fields.index(k)] = v
|
|
@@ -115,8 +117,8 @@ module Kaui
|
|
|
115
117
|
fields = KillBillClient::Model::InvoiceAttributes.instance_variable_get(:@json_attributes)
|
|
116
118
|
# Change the order if needed
|
|
117
119
|
fields -= Kaui::Invoice::TABLE_IGNORE_COLUMNS
|
|
118
|
-
|
|
119
|
-
fields
|
|
120
|
+
# Demo-friendly default column order (see Kaui::Invoice::DEFAULT_VISIBLE_COLUMNS for the ones shown by default)
|
|
121
|
+
fields = Kaui::Invoice::DEFAULT_VISIBLE_COLUMNS + (fields - Kaui::Invoice::DEFAULT_VISIBLE_COLUMNS)
|
|
120
122
|
|
|
121
123
|
headers = fields.map { |attr| attr.split('_').join(' ').capitalize }
|
|
122
124
|
# Add additional headers if needed
|
|
@@ -131,6 +133,8 @@ module Kaui
|
|
|
131
133
|
view_context.humanized_money_with_symbol(invoice.amount_to_money)
|
|
132
134
|
when 'balance'
|
|
133
135
|
view_context.humanized_money_with_symbol(invoice.balance_to_money)
|
|
136
|
+
when 'invoice_number'
|
|
137
|
+
view_context.link_to(invoice.invoice_number, view_context.url_for(controller: :invoices, action: :show, account_id: invoice.account_id, id: invoice.invoice_id))
|
|
134
138
|
when 'invoice_id'
|
|
135
139
|
view_context.link_to(invoice.invoice_id, view_context.url_for(controller: :invoices, action: :show, account_id: invoice.account_id, id: invoice.invoice_id))
|
|
136
140
|
when 'status'
|
|
@@ -146,16 +150,16 @@ module Kaui
|
|
|
146
150
|
|
|
147
151
|
raw_data = fields.map { |attr| invoice&.send(attr.downcase) }
|
|
148
152
|
|
|
149
|
-
[headers, values, raw_data]
|
|
153
|
+
[headers, values, raw_data, fields]
|
|
150
154
|
end
|
|
151
155
|
|
|
152
156
|
self.account_payments_columns = lambda do |account = nil, payment = nil, view_context = nil|
|
|
153
157
|
fields = KillBillClient::Model::PaymentAttributes.instance_variable_get(:@json_attributes)
|
|
154
158
|
# Change the order if needed
|
|
155
|
-
fields = %w[payment_date] + fields
|
|
156
|
-
fields -= %w[
|
|
157
|
-
|
|
158
|
-
fields
|
|
159
|
+
fields = %w[payment_date status] + fields
|
|
160
|
+
fields -= %w[transactions audit_logs]
|
|
161
|
+
# Demo-friendly default column order (see Kaui::Payment::DEFAULT_VISIBLE_COLUMNS for the ones shown by default)
|
|
162
|
+
fields = Kaui::Payment::DEFAULT_VISIBLE_COLUMNS + (fields - Kaui::Payment::DEFAULT_VISIBLE_COLUMNS)
|
|
159
163
|
|
|
160
164
|
headers = fields.dup
|
|
161
165
|
Kaui::Payment::REMAPPING_FIELDS.each do |k, v|
|
|
@@ -163,7 +167,7 @@ module Kaui
|
|
|
163
167
|
end
|
|
164
168
|
headers.map! { |attr| attr.split('_').join(' ').capitalize }
|
|
165
169
|
|
|
166
|
-
return [headers, []] if payment.nil?
|
|
170
|
+
return [headers, [], [], fields] if payment.nil?
|
|
167
171
|
|
|
168
172
|
values = fields.map do |attr|
|
|
169
173
|
case attr
|
|
@@ -190,7 +194,7 @@ module Kaui
|
|
|
190
194
|
end
|
|
191
195
|
|
|
192
196
|
# Add additional values if needed
|
|
193
|
-
[headers, values, raw_data]
|
|
197
|
+
[headers, values, raw_data, fields]
|
|
194
198
|
end
|
|
195
199
|
|
|
196
200
|
self.account_audit_logs_columns = lambda do
|
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.24
|
|
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-08-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actionpack
|
|
@@ -510,6 +510,7 @@ files:
|
|
|
510
510
|
- app/models/kaui/usage.rb
|
|
511
511
|
- app/models/kaui/user.rb
|
|
512
512
|
- app/models/kaui/user_role.rb
|
|
513
|
+
- app/services/dependencies/aviate.rb
|
|
513
514
|
- app/services/dependencies/kenui.rb
|
|
514
515
|
- app/views/kaui/account_children/index.html.erb
|
|
515
516
|
- app/views/kaui/account_custom_fields/index.html.erb
|