kaui 4.0.12 → 4.0.13
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 +21 -0
- data/app/assets/stylesheets/kaui/subscription.css +75 -0
- data/app/assets/stylesheets/kaui/tags.css +6 -3
- data/app/controllers/kaui/accounts_controller.rb +1 -1
- data/app/controllers/kaui/admin_tenants_controller.rb +9 -1
- data/app/controllers/kaui/bundles_controller.rb +53 -5
- data/app/controllers/kaui/engine_controller_util.rb +2 -2
- data/app/controllers/kaui/invoices_controller.rb +1 -1
- data/app/helpers/kaui/plugin_helper.rb +3 -5
- data/app/helpers/kaui/subscription_helper.rb +18 -0
- data/app/models/kaui/bundle.rb +6 -0
- data/app/models/kaui/overdue.rb +5 -2
- data/app/views/kaui/accounts/_account_details.html.erb +1 -1
- data/app/views/kaui/admin_tenants/new_overdue_config.html.erb +25 -1
- data/app/views/kaui/bundles/_bundle_filterbar.html.erb +119 -0
- data/app/views/kaui/bundles/index.html.erb +28 -3
- data/app/views/kaui/invoices/index.html.erb +15 -11
- data/app/views/kaui/refunds/_form.html.erb +3 -11
- data/app/views/kaui/subscriptions/_subscriptions_table.html.erb +1 -1
- data/lib/kaui/version.rb +1 -1
- 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: 62d467e5b936f1738863675c8f5747e9ea4187ac28670556e09d857b1921d588
|
|
4
|
+
data.tar.gz: c79e0a54c96cb563b1eaf7129f147240fd8da04e784a284f7390c39a7b5ac587
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6ff3a1e16012a78cfed10bfaf51ce9733f22d9ebf624d539cdf03cf3a8d7f8dcbda547022dc9258334ae9411c32a0261a7a81b40b19000218a0142950297198
|
|
7
|
+
data.tar.gz: fa4d96b4ccb3afea63726fa9d6a2c2e0f5c853ce8ce5e5acf518c839ab0487fd9c5952ccf33d0b21307b3703ac48b917c15e486061628fb8462091280fb8d21c
|
|
@@ -302,6 +302,27 @@ jQuery(document).ready(function ($) {
|
|
|
302
302
|
|
|
303
303
|
setObjectIdPopover();
|
|
304
304
|
setObjectIdTooltip();
|
|
305
|
+
|
|
306
|
+
/*
|
|
307
|
+
* Tag dropdown overflow prevention
|
|
308
|
+
* Flips each .tag-select-box to open leftward when it would overflow the right edge of the viewport.
|
|
309
|
+
*/
|
|
310
|
+
function repositionTagDropdowns() {
|
|
311
|
+
$('.tag-select').each(function() {
|
|
312
|
+
var $box = $(this).find('.tag-select-box');
|
|
313
|
+
var triggerRight = $(this).offset().left + $(this).outerWidth();
|
|
314
|
+
var boxWidth = Math.max($box[0].scrollWidth, 240);
|
|
315
|
+
var viewportWidth = $(window).width();
|
|
316
|
+
if (triggerRight + boxWidth > viewportWidth) {
|
|
317
|
+
$box.css({ left: 'auto', right: '0' });
|
|
318
|
+
} else {
|
|
319
|
+
$box.css({ left: '0', right: 'auto' });
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
repositionTagDropdowns();
|
|
325
|
+
$(window).on('resize', repositionTagDropdowns);
|
|
305
326
|
});
|
|
306
327
|
|
|
307
328
|
|
|
@@ -689,4 +689,79 @@ table tr.expired td {
|
|
|
689
689
|
line-height: 1.25rem;
|
|
690
690
|
color: #414651;
|
|
691
691
|
margin-bottom: 0.25rem;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/* Advance Search modal – Subscription Bundles page */
|
|
695
|
+
.subscription-bundl-index .close-button {
|
|
696
|
+
background: transparent;
|
|
697
|
+
padding: 0;
|
|
698
|
+
margin-top: -1.25rem;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.subscription-bundl-index .close-button:hover {
|
|
702
|
+
background: transparent;
|
|
703
|
+
padding: 0;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.subscription-bundl-index .border-button {
|
|
707
|
+
background: transparent;
|
|
708
|
+
display: inline-flex;
|
|
709
|
+
justify-content: center;
|
|
710
|
+
align-items: center;
|
|
711
|
+
border: 0.0625rem solid #D5D7DA;
|
|
712
|
+
border-radius: 0.375rem;
|
|
713
|
+
width: 2.5rem;
|
|
714
|
+
height: 2.5rem;
|
|
715
|
+
padding: 0.625rem;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.subscription-bundl-index .border-button:hover {
|
|
719
|
+
background: transparent;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.subscription-bundl-index .button {
|
|
723
|
+
background: transparent;
|
|
724
|
+
display: inline-flex;
|
|
725
|
+
justify-content: center;
|
|
726
|
+
align-items: center;
|
|
727
|
+
width: 2.5rem;
|
|
728
|
+
height: 2.5rem;
|
|
729
|
+
padding: 0.625rem;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.subscription-bundl-index .button:hover {
|
|
733
|
+
background: transparent;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.subscription-bundl-index .field-label {
|
|
737
|
+
font-weight: 500;
|
|
738
|
+
font-size: 0.875rem;
|
|
739
|
+
line-height: 1.25rem;
|
|
740
|
+
color: #414651;
|
|
741
|
+
margin-top: -1.25rem;
|
|
742
|
+
min-width: 9.59375rem;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.subscription-bundl-index .search-field-label {
|
|
746
|
+
margin-top: -1.25rem;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.subscription-bundl-index .form-group.row.align-items-center {
|
|
750
|
+
display: flex;
|
|
751
|
+
align-items: center;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.subscription-bundl-index .form-group.row.align-items-center label {
|
|
755
|
+
margin-bottom: 0;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.subscription-bundl-index .form-group.row.align-items-center .form-control {
|
|
759
|
+
margin-right: 0.25rem;
|
|
760
|
+
height: 2.5rem;
|
|
761
|
+
border-radius: 0.375rem;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.subscription-bundl-index .form-group.row.align-items-center.search-field div {
|
|
765
|
+
padding: 0;
|
|
766
|
+
margin-right: 0.25rem;
|
|
692
767
|
}
|
|
@@ -58,15 +58,18 @@
|
|
|
58
58
|
.tag-bar .tag-select .tag-select-box {
|
|
59
59
|
position: absolute;
|
|
60
60
|
top: 100%;
|
|
61
|
-
left:
|
|
61
|
+
left: 0;
|
|
62
|
+
right: auto;
|
|
62
63
|
max-height: 0;
|
|
63
64
|
background-color: #fff;
|
|
64
65
|
z-index: 100;
|
|
65
66
|
padding: 0 0.9375rem;
|
|
66
67
|
font-size: 0.75rem;
|
|
67
68
|
color: #999999;
|
|
68
|
-
overflow:
|
|
69
|
-
width:
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
min-width: 15rem;
|
|
71
|
+
width: max-content;
|
|
72
|
+
max-width: min(25rem, 95vw);
|
|
70
73
|
border-bottom: 0;
|
|
71
74
|
display: inline-block;
|
|
72
75
|
}
|
|
@@ -5,7 +5,7 @@ module Kaui
|
|
|
5
5
|
class AccountsController < Kaui::EngineController
|
|
6
6
|
def index
|
|
7
7
|
@search_query = params[:q]
|
|
8
|
-
@advance_search_query = @search_query ||
|
|
8
|
+
@advance_search_query = @search_query.presence || params[:advance_search_query].presence
|
|
9
9
|
if @search_query.present?
|
|
10
10
|
account = Kaui::Account.list_or_search(@search_query, -1, 1, options_for_klient).first
|
|
11
11
|
if account.nil?
|
|
@@ -288,7 +288,15 @@ module Kaui
|
|
|
288
288
|
options = tenant_options_for_client
|
|
289
289
|
options[:api_key] = @tenant.api_key
|
|
290
290
|
options[:api_secret] = @tenant.api_secret
|
|
291
|
-
|
|
291
|
+
begin
|
|
292
|
+
@overdue = Kaui::Overdue.get_overdue_json(options)
|
|
293
|
+
@overdue_corrupted = false
|
|
294
|
+
rescue StandardError => e
|
|
295
|
+
Rails.logger.warn("Failed to load overdue configuration for tenant #{@tenant.id}: #{e.class}: #{e.message}")
|
|
296
|
+
@overdue = KillBillClient::Model::Overdue.new.tap { |o| o.overdue_states = [] }
|
|
297
|
+
@overdue_corrupted = true
|
|
298
|
+
flash.now[:warning] = 'The existing overdue configuration is corrupted and cannot be loaded. Use the XML upload below to replace it with a valid configuration.'
|
|
299
|
+
end
|
|
292
300
|
end
|
|
293
301
|
|
|
294
302
|
def modify_overdue_config
|
|
@@ -5,10 +5,11 @@ module Kaui
|
|
|
5
5
|
# rubocop:disable Lint/HashCompareByIdentity
|
|
6
6
|
def index
|
|
7
7
|
cached_options_for_klient = options_for_klient
|
|
8
|
+
@search_query = params[:q].presence
|
|
9
|
+
@search_by = params[:search_by] || 'bundle_id'
|
|
8
10
|
@per_page = (params[:per_page] || 10).to_i
|
|
9
11
|
@page = (params[:page] || 1).to_i
|
|
10
12
|
|
|
11
|
-
fetch_bundles = promise { Kaui::Account.paginated_bundles(@account.account_id, (@page - 1) * @per_page, @per_page, 'NONE', cached_options_for_klient) }
|
|
12
13
|
fetch_bundle_tags = promise do
|
|
13
14
|
all_bundle_tags = @account.all_tags(:BUNDLE, false, 'NONE', cached_options_for_klient)
|
|
14
15
|
all_bundle_tags.each_with_object({}) do |entry, hsh|
|
|
@@ -36,8 +37,15 @@ module Kaui
|
|
|
36
37
|
fetch_available_tags = promise { Kaui::TagDefinition.all_for_bundle(cached_options_for_klient) }
|
|
37
38
|
fetch_available_subscription_tags = promise { Kaui::TagDefinition.all_for_subscription(cached_options_for_klient) }
|
|
38
39
|
|
|
39
|
-
@
|
|
40
|
-
|
|
40
|
+
if @search_query.present?
|
|
41
|
+
@bundles = search_bundles(@search_query, @search_by, cached_options_for_klient)
|
|
42
|
+
@total_pages = 1
|
|
43
|
+
@page = 1
|
|
44
|
+
else
|
|
45
|
+
fetched = Kaui::Account.paginated_bundles(@account.account_id, (@page - 1) * @per_page, @per_page, 'NONE', cached_options_for_klient)
|
|
46
|
+
@bundles = fetched
|
|
47
|
+
@total_pages = (fetched.pagination_max_nb_records.to_f / @per_page).ceil
|
|
48
|
+
end
|
|
41
49
|
|
|
42
50
|
@tags_per_bundle = wait(fetch_bundle_tags)
|
|
43
51
|
@tags_per_subscription = wait(fetch_subscription_tags)
|
|
@@ -46,8 +54,15 @@ module Kaui
|
|
|
46
54
|
@available_tags = wait(fetch_available_tags)
|
|
47
55
|
@available_subscription_tags = wait(fetch_available_subscription_tags)
|
|
48
56
|
|
|
49
|
-
#
|
|
50
|
-
|
|
57
|
+
# Collect the distinct start dates from subscriptions on this page, then fetch
|
|
58
|
+
# only the catalog versions needed — one per unique date — to avoid loading all
|
|
59
|
+
# historical versions into memory.
|
|
60
|
+
start_dates = @bundles.flat_map(&:subscriptions).filter_map(&:start_date).uniq
|
|
61
|
+
@catalogs = start_dates.filter_map do |date|
|
|
62
|
+
Kaui::Catalog.get_account_catalog_json(@account.account_id, date, cached_options_for_klient)&.last
|
|
63
|
+
rescue StandardError
|
|
64
|
+
nil
|
|
65
|
+
end.uniq(&:effective_date)
|
|
51
66
|
|
|
52
67
|
@subscription = {}
|
|
53
68
|
@bundles.each do |bundle|
|
|
@@ -112,5 +127,38 @@ module Kaui
|
|
|
112
127
|
end
|
|
113
128
|
redirect_to kaui_engine.account_bundles_path(@account.account_id), notice: msg
|
|
114
129
|
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
def search_bundles(query, search_by, options)
|
|
134
|
+
case search_by
|
|
135
|
+
when 'bundle_id'
|
|
136
|
+
bundle = Kaui::Bundle.find_by_id(query, options)
|
|
137
|
+
bundle ? [bundle] : []
|
|
138
|
+
when 'bundle_external_key'
|
|
139
|
+
bundle = Kaui::Bundle.find_by_external_key(query, false, options)
|
|
140
|
+
bundle ? [bundle] : []
|
|
141
|
+
when 'subscription_id'
|
|
142
|
+
subscription = KillBillClient::Model::Subscription.find_by_id(query, 'NONE', options)
|
|
143
|
+
if subscription
|
|
144
|
+
bundle = Kaui::Bundle.find_by_id(subscription.bundle_id, options)
|
|
145
|
+
bundle ? [bundle] : []
|
|
146
|
+
else
|
|
147
|
+
[]
|
|
148
|
+
end
|
|
149
|
+
when 'subscription_external_key'
|
|
150
|
+
subscription = KillBillClient::Model::Subscription.find_by_external_key(query, 'NONE', options)
|
|
151
|
+
if subscription
|
|
152
|
+
bundle = Kaui::Bundle.find_by_id(subscription.bundle_id, options)
|
|
153
|
+
bundle ? [bundle] : []
|
|
154
|
+
else
|
|
155
|
+
[]
|
|
156
|
+
end
|
|
157
|
+
else
|
|
158
|
+
[]
|
|
159
|
+
end
|
|
160
|
+
rescue KillBillClient::API::NotFound
|
|
161
|
+
[]
|
|
162
|
+
end
|
|
115
163
|
end
|
|
116
164
|
end
|
|
@@ -127,7 +127,7 @@ module Kaui
|
|
|
127
127
|
as_string(exception.cause)
|
|
128
128
|
else
|
|
129
129
|
log_rescue_error(exception)
|
|
130
|
-
exception.message
|
|
130
|
+
exception.message.to_s[0..200]
|
|
131
131
|
end
|
|
132
132
|
end
|
|
133
133
|
|
|
@@ -150,7 +150,7 @@ module Kaui
|
|
|
150
150
|
error_message += " (code=#{error_message['code']})" if error_message['code'].present?
|
|
151
151
|
end
|
|
152
152
|
# Limit the error size to avoid ActionDispatch::Cookies::CookieOverflow
|
|
153
|
-
error_message[0..
|
|
153
|
+
error_message.to_s[0..200]
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
def nested_hash_value(obj, key)
|
|
@@ -6,7 +6,7 @@ module Kaui
|
|
|
6
6
|
def index
|
|
7
7
|
@search_query = params[:account_id]
|
|
8
8
|
@advance_search_query = params[:q] || request.query_string
|
|
9
|
-
@ordering = params[:ordering] ||
|
|
9
|
+
@ordering = params[:ordering] || 'desc'
|
|
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] }
|
|
@@ -11,10 +11,8 @@ module Kaui
|
|
|
11
11
|
def installed_plugin_names
|
|
12
12
|
plugins = []
|
|
13
13
|
nodes_info = KillBillClient::Model::NodesInfo.nodes_info(Kaui.current_tenant_user_options(current_user, session)) || []
|
|
14
|
-
plugins_info = nodes_info.
|
|
15
|
-
plugins_info.each do |plugin|
|
|
16
|
-
next unless plugin.state == 'RUNNING'
|
|
17
|
-
|
|
14
|
+
plugins_info = nodes_info.flat_map { |node| node.plugins_info || [] }
|
|
15
|
+
plugins_info.select { |p| p.state == 'RUNNING' }.uniq(&:plugin_name).each do |plugin|
|
|
18
16
|
plugin_name = plugin.plugin_name
|
|
19
17
|
plugin_key = plugin_name.gsub('-plugin', '')
|
|
20
18
|
|
|
@@ -40,7 +38,7 @@ module Kaui
|
|
|
40
38
|
def installed_plugins
|
|
41
39
|
installed_plugins = []
|
|
42
40
|
nodes_info = KillBillClient::Model::NodesInfo.nodes_info(Kaui.current_tenant_user_options(current_user, session)) || []
|
|
43
|
-
plugins_info = nodes_info.
|
|
41
|
+
plugins_info = nodes_info.flat_map { |node| node.plugins_info || [] }
|
|
44
42
|
|
|
45
43
|
plugins_info.each do |plugin|
|
|
46
44
|
next if plugin.version.nil?
|
|
@@ -107,6 +107,24 @@ module Kaui
|
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
def catalog_for_subscription(sub, catalogs)
|
|
111
|
+
return nil if catalogs.blank? || sub&.start_date.blank?
|
|
112
|
+
|
|
113
|
+
start_date = Date.parse(sub.start_date)
|
|
114
|
+
|
|
115
|
+
# Find the latest catalog version whose effective_date <= subscription start_date
|
|
116
|
+
best = catalogs.select do |c|
|
|
117
|
+
Date.parse(c.effective_date) <= start_date
|
|
118
|
+
rescue StandardError
|
|
119
|
+
false
|
|
120
|
+
end.max_by(&:effective_date)
|
|
121
|
+
|
|
122
|
+
# Fall back to the oldest version if none precedes the start_date
|
|
123
|
+
best || catalogs.first
|
|
124
|
+
rescue StandardError
|
|
125
|
+
catalogs.last
|
|
126
|
+
end
|
|
127
|
+
|
|
110
128
|
def humanized_subscription_phase_type(sub)
|
|
111
129
|
sub.phase_type
|
|
112
130
|
end
|
data/app/models/kaui/bundle.rb
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Kaui
|
|
4
4
|
class Bundle < KillBillClient::Model::Bundle
|
|
5
|
+
SEARCH_FIELDS = [
|
|
6
|
+
['bundle_id', 'Bundle ID'],
|
|
7
|
+
['bundle_external_key', 'Bundle External Key'],
|
|
8
|
+
['subscription_id', 'Subscription ID'],
|
|
9
|
+
['subscription_external_key', 'Subscription External Key']
|
|
10
|
+
].freeze
|
|
5
11
|
def self.find_by_id_or_key(bundle_id_or_key, options = {})
|
|
6
12
|
if /[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}/.match?(bundle_id_or_key)
|
|
7
13
|
bundle = begin
|
data/app/models/kaui/overdue.rb
CHANGED
|
@@ -37,8 +37,11 @@ module Kaui
|
|
|
37
37
|
|
|
38
38
|
result.overdue_states << state
|
|
39
39
|
end
|
|
40
|
-
#
|
|
41
|
-
|
|
40
|
+
# Sort by days descending (most severe first) so Kill Bill receives them in the correct order
|
|
41
|
+
# regardless of the order the user added rows in the form.
|
|
42
|
+
result.overdue_states.sort_by! do |s|
|
|
43
|
+
-s.condition&.time_since_earliest_unpaid_invoice_equals_or_exceeds&.number.to_i
|
|
44
|
+
end
|
|
42
45
|
|
|
43
46
|
result
|
|
44
47
|
end
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<% end %>
|
|
46
46
|
<span class="tag-bar tag-bar-breathe">
|
|
47
47
|
<% unless @available_tags.blank? %>
|
|
48
|
-
<div class="tag-select" onclick="void(0)">
|
|
48
|
+
<div class="tag-select position-relative" onclick="void(0)">
|
|
49
49
|
<%= render "kaui/components/button/button", {
|
|
50
50
|
label: "Tag As",
|
|
51
51
|
trailing_icon: "kaui/account/down-arrow.svg",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
<td><%= state_form.select :is_block_changes, options_for_select([true, false ], state.is_block_changes), :class => 'form-control' %></td>
|
|
62
62
|
<td><%= state_form.select :subscription_cancellation_policy, options_for_select([:NONE, :POLICY_NONE, :POLICY_IMMEDIATE, :POLICY_END_OF_TERM], state.subscription_cancellation), :class => 'form-control' %></td>
|
|
63
63
|
<%= state_form.fields_for 'condition' do |condition| %>
|
|
64
|
-
<td><%= condition.number_field :time_since_earliest_unpaid_invoice_equals_or_exceeds, :value => state.condition.time_since_earliest_unpaid_invoice_equals_or_exceeds&.number %></td>
|
|
64
|
+
<td><%= condition.number_field :time_since_earliest_unpaid_invoice_equals_or_exceeds, :value => state.condition.time_since_earliest_unpaid_invoice_equals_or_exceeds&.number, :min => 1, :class => 'days-since-field' %></td>
|
|
65
65
|
<td><%= condition.select :control_tag_inclusion, options_for_select([:NONE, :AUTO_PAY_OFF, :AUTO_INVOICING_OFF, :OVERDUE_ENFORCEMENT_OFF, :MANUAL_PAY, :TEST, :PARTNER], state.condition&.control_tag_inclusion), :class => 'form-control' %></td>
|
|
66
66
|
<td><%= condition.select :control_tag_exclusion, options_for_select([:NONE, :AUTO_PAY_OFF, :AUTO_INVOICING_OFF, :OVERDUE_ENFORCEMENT_OFF, :MANUAL_PAY, :TEST, :PARTNER], state.condition&.control_tag_exclusion), :class => 'form-control'%></td>
|
|
67
67
|
<td><%= condition.number_field :number_of_unpaid_invoices_equals_or_exceeds, :value => state.condition&.number_of_unpaid_invoices_equals_or_exceeds %></td>
|
|
@@ -214,8 +214,32 @@ function overdue_delete_state(obj) {
|
|
|
214
214
|
$("#tr_state_" + idx).hide();
|
|
215
215
|
};
|
|
216
216
|
|
|
217
|
+
$('form.form-horizontal').on('submit', function(e) {
|
|
218
|
+
var invalid = false;
|
|
219
|
+
$('#existing-overdue-config-for-tenants tbody tr:visible').each(function() {
|
|
220
|
+
var $days = $(this).find('.days-since-field');
|
|
221
|
+
var val = parseInt($days.val(), 10);
|
|
222
|
+
if (isNaN(val) || val <= 0) {
|
|
223
|
+
$days.addClass('is-invalid');
|
|
224
|
+
invalid = true;
|
|
225
|
+
} else {
|
|
226
|
+
$days.removeClass('is-invalid');
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
if (invalid) {
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
alert('"Days since earliest unpaid invoice" must be greater than 0 for all states.');
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
217
235
|
$(document).ready(function() {
|
|
236
|
+
<% if @overdue_corrupted %>
|
|
237
|
+
switch_overdue_xml_config();
|
|
238
|
+
document.querySelectorAll('.toggle-option').forEach(opt => opt.classList.remove('active-btn'));
|
|
239
|
+
document.querySelectorAll('.toggle-option')[1]?.classList.add('active-btn');
|
|
240
|
+
<% else %>
|
|
218
241
|
switch_overdue_basic_config();
|
|
242
|
+
<% end %>
|
|
219
243
|
});
|
|
220
244
|
|
|
221
245
|
document.querySelectorAll('.toggle-option').forEach(el => {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<div class="modal fade" id="advanceSearchModal" tabindex="-1" role="dialog" aria-labelledby="advanceSearchModalLabel" aria-hidden="true">
|
|
2
|
+
<div class="modal-dialog" 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="advanceSearchModalLabel">
|
|
6
|
+
<span class="icon-container">
|
|
7
|
+
<%= image_tag("kaui/modal/search.svg", width: 20, height: 20) %>
|
|
8
|
+
</span>
|
|
9
|
+
Advance Search
|
|
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
|
+
<form id="advanceSearchForm">
|
|
19
|
+
<div class="form-group d-flex align-items-center">
|
|
20
|
+
<label for="searchFieldSelect" class="mr-2 field-label" style="width: 30%;">Search Field</label>
|
|
21
|
+
<select id="searchFieldSelect" class="form-control mr-2">
|
|
22
|
+
<% Kaui::Bundle::SEARCH_FIELDS.each do |value, title| %>
|
|
23
|
+
<option value="<%= value %>" <%= 'selected' if @search_by == value %>><%= title %></option>
|
|
24
|
+
<% end %>
|
|
25
|
+
</select>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="form-group d-flex align-items-center mt-3">
|
|
28
|
+
<label for="bundleSearchValue" class="mr-2 field-label" style="width: 30%;">Value</label>
|
|
29
|
+
<input type="text" id="bundleSearchValue" class="form-control" placeholder="Enter search value..." value="<%= @search_query %>">
|
|
30
|
+
</div>
|
|
31
|
+
</form>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="modal-footer">
|
|
34
|
+
<%= render "kaui/components/button/button", {
|
|
35
|
+
label: 'Clear Search',
|
|
36
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
37
|
+
type: "button",
|
|
38
|
+
html_class: "kaui-button custom-hover",
|
|
39
|
+
html_options: { id: "clearAdvanceSearch" }
|
|
40
|
+
} %>
|
|
41
|
+
<%= render "kaui/components/button/button", {
|
|
42
|
+
label: 'Apply Search',
|
|
43
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
44
|
+
type: "button",
|
|
45
|
+
html_class: "kaui-dropdown custom-hover",
|
|
46
|
+
html_options: { id: "applyAdvanceSearch" }
|
|
47
|
+
} %>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<%= javascript_tag do %>
|
|
54
|
+
$(document).ready(function() {
|
|
55
|
+
// Pre-populate modal from current URL params on open
|
|
56
|
+
$('#advanceSearchModal').on('show.bs.modal', function() {
|
|
57
|
+
var params = new URLSearchParams(window.location.search);
|
|
58
|
+
var searchBy = params.get('search_by') || 'bundle_id';
|
|
59
|
+
var q = params.get('q') || '';
|
|
60
|
+
$('#searchFieldSelect').val(searchBy);
|
|
61
|
+
$('#bundleSearchValue').val(q);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Apply search: navigate to same page with query params
|
|
65
|
+
$('#applyAdvanceSearch').on('click', function() {
|
|
66
|
+
var searchBy = $('#searchFieldSelect').val();
|
|
67
|
+
var q = $('#bundleSearchValue').val().trim();
|
|
68
|
+
if (!q) return;
|
|
69
|
+
|
|
70
|
+
var url = new URL(window.location.href);
|
|
71
|
+
url.searchParams.set('search_by', searchBy);
|
|
72
|
+
url.searchParams.set('q', q);
|
|
73
|
+
url.searchParams.delete('page');
|
|
74
|
+
window.location.href = url.toString();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Clear search: navigate to same page without query params
|
|
78
|
+
$('#clearAdvanceSearch').on('click', function() {
|
|
79
|
+
var url = new URL(window.location.href);
|
|
80
|
+
url.searchParams.delete('q');
|
|
81
|
+
url.searchParams.delete('search_by');
|
|
82
|
+
url.searchParams.delete('page');
|
|
83
|
+
window.location.href = url.toString();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Handle click on active search label close icon
|
|
87
|
+
$(document).on('click', '.bundle-filter-close-icon', function() {
|
|
88
|
+
var url = new URL(window.location.href);
|
|
89
|
+
url.searchParams.delete('q');
|
|
90
|
+
url.searchParams.delete('search_by');
|
|
91
|
+
url.searchParams.delete('page');
|
|
92
|
+
window.location.href = url.toString();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Show active search label on page load
|
|
96
|
+
updateBundleSearchLabels();
|
|
97
|
+
|
|
98
|
+
function updateBundleSearchLabels() {
|
|
99
|
+
var params = new URLSearchParams(window.location.search);
|
|
100
|
+
var q = params.get('q');
|
|
101
|
+
var searchBy = params.get('search_by');
|
|
102
|
+
var container = $('#search-labels-container');
|
|
103
|
+
container.empty();
|
|
104
|
+
|
|
105
|
+
if (q && searchBy) {
|
|
106
|
+
var fieldLabel = $('#searchFieldSelect option[value="' + searchBy + '"]').text() || searchBy;
|
|
107
|
+
var label = $('<span>', {
|
|
108
|
+
class: 'label label-info d-inline-flex align-items-center gap-2'
|
|
109
|
+
});
|
|
110
|
+
label.append($('<span>', { text: fieldLabel + ': ' + q }));
|
|
111
|
+
label.append($('<span>', {
|
|
112
|
+
class: 'bundle-filter-close-icon',
|
|
113
|
+
style: 'cursor: pointer; margin-left: 5px; display: inline-flex; align-items: center;'
|
|
114
|
+
}).html('<svg width="12" height="12" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15.8337 4.1665L4.16699 15.8332M4.16699 4.1665L15.8337 15.8332" stroke="#A4A7AE" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>'));
|
|
115
|
+
container.append(label);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
<% end %>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<div class="kaui-container subscription-bundl-index pb-5">
|
|
2
2
|
<%= render "kaui/components/breadcrumb/breadcrumb" %>
|
|
3
|
+
<%= render :partial => 'bundle_filterbar' %>
|
|
3
4
|
<div class="d-flex mb-5" style="gap: 4rem;">
|
|
4
5
|
<%= render :template => 'kaui/layouts/kaui_account_sidebar' %>
|
|
5
6
|
<div class="subscription-bundle">
|
|
@@ -8,7 +9,20 @@
|
|
|
8
9
|
<div class="d-flex align-items-center">
|
|
9
10
|
<h2>Subscription Bundles</h2>
|
|
10
11
|
</div>
|
|
11
|
-
<span>
|
|
12
|
+
<span class="d-flex align-items-center gap-2">
|
|
13
|
+
<%= render "kaui/components/button/button", {
|
|
14
|
+
label: "Advance Search",
|
|
15
|
+
icon: "kaui/search.svg",
|
|
16
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
17
|
+
type: "button",
|
|
18
|
+
html_class: "kaui-button custom-hover",
|
|
19
|
+
html_options: {
|
|
20
|
+
data: {
|
|
21
|
+
bs_toggle: "modal",
|
|
22
|
+
bs_target: "#advanceSearchModal"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} %>
|
|
12
26
|
<% if can? :create, Kaui::Subscription %>
|
|
13
27
|
<%= link_to kaui_engine.new_subscription_path(:params => { :account_id => @account.account_id, :product_category => 'BASE' }) do %>
|
|
14
28
|
<%= render "kaui/components/button/button", {
|
|
@@ -22,21 +36,31 @@
|
|
|
22
36
|
<% end %>
|
|
23
37
|
</span>
|
|
24
38
|
</div>
|
|
39
|
+
<div id="search-labels-container" class="ml-2 mb-2">
|
|
40
|
+
<!-- Dynamic search labels will be added here -->
|
|
41
|
+
</div>
|
|
25
42
|
<div class="subscriptions-scroll">
|
|
26
43
|
<% @bundles.each_with_index do |bundle, idx| %>
|
|
27
44
|
<div class="row">
|
|
28
45
|
<div class="d-flex">
|
|
29
|
-
<%= render :partial => Kaui.bundle_details_partial, :locals => { :bundle => bundle, :account => @account, :catalog => @
|
|
46
|
+
<%= render :partial => Kaui.bundle_details_partial, :locals => { :bundle => bundle, :account => @account, :catalog => @catalogs&.last } %>
|
|
30
47
|
</div>
|
|
31
48
|
<% if bundle.subscriptions.present? %>
|
|
32
49
|
<div class="search">
|
|
33
|
-
<%= render :partial => 'kaui/subscriptions/subscriptions_table', :locals => {:bundle => bundle, :account => @account, :
|
|
50
|
+
<%= render :partial => 'kaui/subscriptions/subscriptions_table', :locals => {:bundle => bundle, :account => @account, :catalogs => @catalogs} %>
|
|
34
51
|
</div>
|
|
35
52
|
<% end %>
|
|
36
53
|
</div>
|
|
37
54
|
<% end %>
|
|
38
55
|
|
|
39
56
|
|
|
57
|
+
<% if @bundles.empty? %>
|
|
58
|
+
<div class="custom-alert custom-alert-info mt-3">
|
|
59
|
+
<span>No bundles found<%= @search_query.present? ? " for the given search query." : "." %></span>
|
|
60
|
+
</div>
|
|
61
|
+
<% end %>
|
|
62
|
+
|
|
63
|
+
<% unless @search_query.present? %>
|
|
40
64
|
<div class="text-right d-flex justify-content-end pagination" style="">
|
|
41
65
|
<%= link_to account_bundles_path(page: @page - 1), class: "btn btn-custom #{'disabled' if @page == 1}" do %>
|
|
42
66
|
<%= render "kaui/components/button/button", {
|
|
@@ -81,6 +105,7 @@
|
|
|
81
105
|
} %>
|
|
82
106
|
<% end %>
|
|
83
107
|
</div>
|
|
108
|
+
<% end %>
|
|
84
109
|
</div>
|
|
85
110
|
</div>
|
|
86
111
|
</div>
|
|
@@ -41,7 +41,16 @@
|
|
|
41
41
|
|
|
42
42
|
<%= javascript_tag do %>
|
|
43
43
|
$(document).ready(function() {
|
|
44
|
-
|
|
44
|
+
// Dynamically find the Invoice date column index based on current header order
|
|
45
|
+
var invoiceDateColIndex = 3; // fallback default
|
|
46
|
+
$('#invoices-table thead th').each(function(i) {
|
|
47
|
+
if ($(this).find('.header-text').text().trim().toLowerCase() === 'invoice date') {
|
|
48
|
+
invoiceDateColIndex = i;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
var stateKey = 'DataTables_invoices-table_' + window.location.pathname.replace(/\//g, '_');
|
|
45
54
|
var state = JSON.parse(localStorage.getItem(stateKey));
|
|
46
55
|
if (state) {
|
|
47
56
|
state.start = <%= @offset %>;
|
|
@@ -69,11 +78,8 @@ $(document).ready(function() {
|
|
|
69
78
|
},
|
|
70
79
|
"pageLength": <%= @limit %>,
|
|
71
80
|
"displayStart": <%= @offset %>,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<% elsif !@ordering.blank? %>
|
|
75
|
-
"order": [[ 0, "<%= @ordering %>" ]],
|
|
76
|
-
<% end %>
|
|
81
|
+
"ordering": true,
|
|
82
|
+
"order": [[ invoiceDateColIndex, "asc" ]],
|
|
77
83
|
"processing": true,
|
|
78
84
|
"serverSide": true,
|
|
79
85
|
"search": {"search": "<%= @search_query %>"},
|
|
@@ -97,8 +103,8 @@ $(document).ready(function() {
|
|
|
97
103
|
});
|
|
98
104
|
|
|
99
105
|
// Custom sorting functionality
|
|
100
|
-
var currentSortColumn =
|
|
101
|
-
var currentSortDirection = '
|
|
106
|
+
var currentSortColumn = invoiceDateColIndex;
|
|
107
|
+
var currentSortDirection = 'desc';
|
|
102
108
|
|
|
103
109
|
// Handle custom header clicks
|
|
104
110
|
$('.sortable-header').on('click', function() {
|
|
@@ -134,9 +140,7 @@ $(document).ready(function() {
|
|
|
134
140
|
|
|
135
141
|
// Initialize sort indicators based on current state
|
|
136
142
|
<% if !@ordering.blank? %>
|
|
137
|
-
|
|
138
|
-
var initialDirection = "<%= @ordering %>";
|
|
139
|
-
updateSortIndicators(initialColumn, initialDirection);
|
|
143
|
+
updateSortIndicators(invoiceDateColIndex, 'desc');
|
|
140
144
|
<% end %>
|
|
141
145
|
|
|
142
146
|
// Add an action whenever the page changes
|
|
@@ -126,8 +126,7 @@
|
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
var validateRefundAmount = function() {
|
|
129
|
-
if (Number($("#refund_amount").
|
|
130
|
-
Number($("#refund_amount").attr('value')) <= 0) {
|
|
129
|
+
if (Number($("#refund_amount").val()) <= 0) {
|
|
131
130
|
setClassForElement("#div_refund_amount", "form-group d-flex pb-3 error");
|
|
132
131
|
$('#new_kill_bill_client_model_invoice_item :submit').prop('disabled', true);
|
|
133
132
|
} else {
|
|
@@ -151,11 +150,6 @@
|
|
|
151
150
|
});
|
|
152
151
|
};
|
|
153
152
|
|
|
154
|
-
/*
|
|
155
|
-
* Recompute refund amount based on adjustment type:
|
|
156
|
-
* - For Invoice Item Adjustment, recompute price based on selection and invalidate text area to make it match exact selection
|
|
157
|
-
* _ For Invoice adjustment or no adjustment, default to payment amount
|
|
158
|
-
*/
|
|
159
153
|
var recomputeRefundAmountAndValidateAmount = function() {
|
|
160
154
|
var computedRefundAmount = <%= @payment.purchased_amount %>;
|
|
161
155
|
if ($("#adjustment_type_invoiceItemAdjustment").is(':checked')) {
|
|
@@ -166,11 +160,9 @@
|
|
|
166
160
|
}
|
|
167
161
|
});
|
|
168
162
|
computedRefundAmount = x.toFixed(2);
|
|
169
|
-
$("#refund_amount").
|
|
170
|
-
$("#refund_amount").prop('readonly', true);
|
|
163
|
+
$("#refund_amount").val(computedRefundAmount);
|
|
171
164
|
} else {
|
|
172
|
-
$("#refund_amount").
|
|
173
|
-
$("#refund_amount").prop('readonly', false);
|
|
165
|
+
$("#refund_amount").val(computedRefundAmount);
|
|
174
166
|
}
|
|
175
167
|
validateRefundAmount();
|
|
176
168
|
};
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
</div>
|
|
105
105
|
</td>
|
|
106
106
|
<% end %>
|
|
107
|
-
<td><%= humanized_subscription_plan_or_product_name(sub,
|
|
107
|
+
<td><%= humanized_subscription_plan_or_product_name(sub, catalog_for_subscription(sub, catalogs)) %></td>
|
|
108
108
|
<td>
|
|
109
109
|
<span id="<%= sub.subscription_id %>-popover" class="object-id-popover category-bedge" data-id="<%= sub.subscription_id %>">
|
|
110
110
|
<%= humanized_subscription_product_category(sub) %>
|
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.13
|
|
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-04-
|
|
11
|
+
date: 2026-04-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actionpack
|
|
@@ -576,6 +576,7 @@ files:
|
|
|
576
576
|
- app/views/kaui/bundle_tags/_form_bar.html.erb
|
|
577
577
|
- app/views/kaui/bundle_tags/edit.html.erb
|
|
578
578
|
- app/views/kaui/bundles/_bundle_details.html.erb
|
|
579
|
+
- app/views/kaui/bundles/_bundle_filterbar.html.erb
|
|
579
580
|
- app/views/kaui/bundles/index.html.erb
|
|
580
581
|
- app/views/kaui/bundles/pause_resume.html.erb
|
|
581
582
|
- app/views/kaui/bundles/transfer.html.erb
|