kaui 4.0.11 → 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/account_timelines_controller.rb +3 -3
- data/app/controllers/kaui/accounts_controller.rb +52 -52
- data/app/controllers/kaui/admin_allowed_users_controller.rb +29 -29
- data/app/controllers/kaui/admin_controller.rb +2 -2
- data/app/controllers/kaui/admin_tenants_controller.rb +89 -81
- data/app/controllers/kaui/audit_logs_controller.rb +1 -1
- data/app/controllers/kaui/bundles_controller.rb +53 -5
- data/app/controllers/kaui/chargebacks_controller.rb +1 -1
- data/app/controllers/kaui/charges_controller.rb +5 -2
- data/app/controllers/kaui/credits_controller.rb +1 -1
- data/app/controllers/kaui/custom_fields_controller.rb +11 -11
- data/app/controllers/kaui/engine_controller_util.rb +5 -5
- data/app/controllers/kaui/home_controller.rb +6 -2
- data/app/controllers/kaui/invoices_controller.rb +6 -4
- data/app/controllers/kaui/payment_methods_controller.rb +5 -5
- data/app/controllers/kaui/payments_controller.rb +19 -19
- data/app/controllers/kaui/queues_controller.rb +6 -6
- data/app/controllers/kaui/registrations_controller.rb +1 -1
- data/app/controllers/kaui/role_definitions_controller.rb +2 -2
- data/app/controllers/kaui/sessions_controller.rb +1 -1
- data/app/controllers/kaui/subscriptions_controller.rb +15 -14
- data/app/controllers/kaui/tag_definitions_controller.rb +1 -1
- data/app/controllers/kaui/tenants_controller.rb +2 -2
- data/app/controllers/kaui/transactions_controller.rb +6 -6
- data/app/helpers/kaui/account_helper.rb +9 -7
- data/app/helpers/kaui/exception_helper.rb +7 -5
- data/app/helpers/kaui/payment_helper.rb +2 -9
- data/app/helpers/kaui/plugin_helper.rb +3 -5
- data/app/helpers/kaui/subscription_helper.rb +46 -30
- data/app/helpers/kaui/uuid_helper.rb +1 -1
- data/app/models/kaui/account.rb +4 -3
- data/app/models/kaui/admin_tenant.rb +2 -2
- data/app/models/kaui/allowed_user.rb +3 -1
- data/app/models/kaui/allowed_user_tenant.rb +2 -2
- data/app/models/kaui/audit_log.rb +1 -1
- data/app/models/kaui/bundle.rb +11 -5
- data/app/models/kaui/invoice.rb +1 -1
- data/app/models/kaui/invoice_payment.rb +2 -2
- data/app/models/kaui/killbill_authenticatable.rb +2 -39
- data/app/models/kaui/killbill_registerable.rb +3 -11
- data/app/models/kaui/overdue.rb +5 -2
- data/app/models/kaui/payment.rb +1 -1
- data/app/models/kaui/payment_state.rb +3 -1
- data/app/models/kaui/rails_methods.rb +2 -2
- data/app/models/kaui/tag_definition.rb +2 -2
- data/app/models/kaui/tenant.rb +2 -1
- data/app/models/kaui/transaction.rb +1 -1
- data/app/services/dependencies/kenui.rb +1 -1
- 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/components/search_input/_search_input.html.erb +39 -1
- 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/_form.html.erb +7 -1
- data/app/views/kaui/subscriptions/_subscriptions_table.html.erb +12 -1
- data/config/routes.rb +79 -79
- data/lib/devise/models/killbill_authenticatable.rb +38 -0
- data/lib/devise/models/killbill_registerable.rb +9 -0
- data/lib/generators/kaui/install/install_generator.rb +1 -4
- data/lib/kaui/version.rb +1 -1
- data/lib/kaui.rb +7 -7
- metadata +5 -2
data/app/models/kaui/bundle.rb
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
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
|
-
if
|
|
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
|
|
8
14
|
find_by_id(bundle_id_or_key, options)
|
|
9
15
|
rescue StandardError
|
|
@@ -47,15 +53,15 @@ module Kaui
|
|
|
47
53
|
end
|
|
48
54
|
|
|
49
55
|
def self.list_transfer_policy_params
|
|
50
|
-
|
|
56
|
+
[
|
|
51
57
|
# [I18n.translate('start_of_term'), 'START_OF_TERM'], Temporarily removed as it is not supported by Kill Bill
|
|
52
|
-
[I18n.
|
|
53
|
-
[I18n.
|
|
58
|
+
[I18n.t('end_of_term'), 'END_OF_TERM'],
|
|
59
|
+
[I18n.t('immediate'), 'IMMEDIATE']
|
|
54
60
|
]
|
|
55
61
|
end
|
|
56
62
|
|
|
57
63
|
def self.list_transfer_policy_params_keys
|
|
58
|
-
|
|
64
|
+
%w[END_OF_TERM IMMEDIATE]
|
|
59
65
|
end
|
|
60
66
|
end
|
|
61
67
|
end
|
data/app/models/kaui/invoice.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Kaui
|
|
|
8
8
|
|
|
9
9
|
def self.build_from_raw_invoice(raw_invoice)
|
|
10
10
|
result = Kaui::Invoice.new
|
|
11
|
-
KillBillClient::Model::InvoiceAttributes.instance_variable_get(
|
|
11
|
+
KillBillClient::Model::InvoiceAttributes.instance_variable_get(:@json_attributes).each do |attr|
|
|
12
12
|
result.send("#{attr}=", raw_invoice.send(attr))
|
|
13
13
|
end
|
|
14
14
|
result
|
|
@@ -17,7 +17,7 @@ module Kaui
|
|
|
17
17
|
return nil if raw_payment.nil?
|
|
18
18
|
|
|
19
19
|
result = Kaui::InvoicePayment.new
|
|
20
|
-
KillBillClient::Model::InvoicePaymentAttributes.instance_variable_get(
|
|
20
|
+
KillBillClient::Model::InvoicePaymentAttributes.instance_variable_get(:@json_attributes).each do |attr|
|
|
21
21
|
result.send("#{attr}=", raw_payment.send(attr))
|
|
22
22
|
end
|
|
23
23
|
# Use Kaui::Transaction to benefit from additional fields (e.g next_retry_date)
|
|
@@ -25,7 +25,7 @@ module Kaui
|
|
|
25
25
|
result.transactions = []
|
|
26
26
|
original_transactions.each do |transaction|
|
|
27
27
|
new_transaction = Kaui::Transaction.new
|
|
28
|
-
KillBillClient::Model::PaymentTransactionAttributes.instance_variable_get(
|
|
28
|
+
KillBillClient::Model::PaymentTransactionAttributes.instance_variable_get(:@json_attributes).each do |attr|
|
|
29
29
|
new_transaction.send("#{attr}=", transaction.send(attr))
|
|
30
30
|
end
|
|
31
31
|
result.transactions << new_transaction
|
|
@@ -1,45 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'devise/models/killbill_authenticatable'
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
# rubocop:disable Style/OneClassPerFile
|
|
5
|
+
# Placeholder for Zeitwerk - must define Kaui::KillbillAuthenticatable before Devise::Models::KillbillAuthenticatable
|
|
7
6
|
module Kaui
|
|
8
7
|
module KillbillAuthenticatable; end
|
|
9
8
|
end
|
|
10
|
-
|
|
11
|
-
module Devise
|
|
12
|
-
module Models
|
|
13
|
-
module KillbillAuthenticatable
|
|
14
|
-
extend ActiveSupport::Concern
|
|
15
|
-
|
|
16
|
-
def valid_killbill_password?(creds)
|
|
17
|
-
# Simply try to look-up the permissions for that user - this will
|
|
18
|
-
# Take care of the auth part
|
|
19
|
-
response = Kaui::User.find_permissions(creds)
|
|
20
|
-
# Auth was successful, update the session id
|
|
21
|
-
self.kb_session_id = response.session_id
|
|
22
|
-
true
|
|
23
|
-
rescue KillBillClient::API::Unauthorized => _e
|
|
24
|
-
false
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def after_killbill_authentication
|
|
28
|
-
save(validate: false)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
module ClassMethods
|
|
32
|
-
# Invoked by the KillbillAuthenticatable strategy to lookup the user
|
|
33
|
-
# before attempting authentication
|
|
34
|
-
def find_for_killbill_authentication(kb_username)
|
|
35
|
-
find_for_authentication(kb_username:) ||
|
|
36
|
-
new(kb_username:)
|
|
37
|
-
rescue KillBillClient::API::Unauthorized => _e
|
|
38
|
-
# Multi-Tenancy was enabled, but the tenant_id couldn't be retrieved because of bad credentials
|
|
39
|
-
nil
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
# rubocop:enable Style/OneClassPerFile
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require 'devise/models/killbill_registerable'
|
|
4
|
+
|
|
5
|
+
# Placeholder for Zeitwerk - must define Kaui::KillbillRegisterable before Devise::Models::KillbillRegisterable
|
|
5
6
|
module Kaui
|
|
6
7
|
module KillbillRegisterable; end
|
|
7
8
|
end
|
|
8
|
-
|
|
9
|
-
module Devise
|
|
10
|
-
module Models
|
|
11
|
-
module KillbillRegisterable
|
|
12
|
-
include Registerable
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
# rubocop:enable Style/OneClassPerFile
|
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
|
data/app/models/kaui/payment.rb
CHANGED
|
@@ -19,7 +19,7 @@ module Kaui
|
|
|
19
19
|
|
|
20
20
|
def self.build_from_raw_payment(raw_payment)
|
|
21
21
|
result = Kaui::Payment.new
|
|
22
|
-
KillBillClient::Model::PaymentAttributes.instance_variable_get(
|
|
22
|
+
KillBillClient::Model::PaymentAttributes.instance_variable_get(:@json_attributes).each do |attr|
|
|
23
23
|
result.send("#{attr}=", raw_payment.send(attr))
|
|
24
24
|
end
|
|
25
25
|
result
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Kaui
|
|
4
4
|
module PaymentState
|
|
5
|
+
REFUNDABLE_TRANSACTION_TYPES = %w[CAPTURE PURCHASE].freeze
|
|
6
|
+
|
|
5
7
|
def refundable?
|
|
6
8
|
transactions.each do |transaction|
|
|
7
|
-
return true if transaction.status == 'SUCCESS' &&
|
|
9
|
+
return true if transaction.status == 'SUCCESS' && REFUNDABLE_TRANSACTION_TYPES.include?(transaction.transaction_type)
|
|
8
10
|
end
|
|
9
11
|
false
|
|
10
12
|
end
|
|
@@ -32,7 +32,7 @@ module Kaui
|
|
|
32
32
|
send(attr)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# rubocop:disable Naming/PredicateMethod
|
|
35
|
+
# rubocop:disable Naming/PredicateMethod, ThreadSafety/ClassInstanceVariable
|
|
36
36
|
def save
|
|
37
37
|
@errors.add(:save, 'Saving this object is not yet supported')
|
|
38
38
|
false
|
|
@@ -47,7 +47,7 @@ module Kaui
|
|
|
47
47
|
@errors.add(:destroy, 'Destroying this object is not yet supported')
|
|
48
48
|
false
|
|
49
49
|
end
|
|
50
|
-
# rubocop:enable Naming/PredicateMethod
|
|
50
|
+
# rubocop:enable Naming/PredicateMethod, ThreadSafety/ClassInstanceVariable
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
base_class.instance_eval do
|
|
@@ -25,12 +25,12 @@ module Kaui
|
|
|
25
25
|
|
|
26
26
|
ALL_OBJECT_TYPES.each do |object_type|
|
|
27
27
|
define_singleton_method "all_for_#{object_type.downcase}" do |options_for_klient|
|
|
28
|
-
all('NONE', options_for_klient).delete_if { |tag_definition|
|
|
28
|
+
all('NONE', options_for_klient).delete_if { |tag_definition| tag_definition.applicable_object_types.exclude?(object_type) }.sort
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def system_tag?
|
|
33
|
-
return false
|
|
33
|
+
return false if id.blank?
|
|
34
34
|
|
|
35
35
|
last_group = id.split('-')[4]
|
|
36
36
|
|
data/app/models/kaui/tenant.rb
CHANGED
|
@@ -7,7 +7,8 @@ module Kaui
|
|
|
7
7
|
attribute :encrypted_api_secret, :encrypted, random_iv: false
|
|
8
8
|
alias_attribute :api_secret, :encrypted_api_secret
|
|
9
9
|
|
|
10
|
-
has_many :kaui_allowed_user_tenants, class_name: 'Kaui::AllowedUserTenant', foreign_key: 'kaui_tenant_id'
|
|
10
|
+
has_many :kaui_allowed_user_tenants, class_name: 'Kaui::AllowedUserTenant', foreign_key: 'kaui_tenant_id',
|
|
11
|
+
dependent: :destroy, inverse_of: :kaui_tenant
|
|
11
12
|
has_many :kaui_allowed_users, through: :kaui_allowed_user_tenants, source: :kaui_allowed_user
|
|
12
13
|
end
|
|
13
14
|
end
|
|
@@ -6,7 +6,7 @@ module Kaui
|
|
|
6
6
|
|
|
7
7
|
def self.build_from_raw_transaction(raw_transaction)
|
|
8
8
|
result = Kaui::Transaction.new
|
|
9
|
-
KillBillClient::Model::PaymentTransactionAttributes.instance_variable_get(
|
|
9
|
+
KillBillClient::Model::PaymentTransactionAttributes.instance_variable_get(:@json_attributes).each do |attr|
|
|
10
10
|
result.send("#{attr}=", raw_transaction.send(attr))
|
|
11
11
|
end
|
|
12
12
|
result
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Dependencies
|
|
4
4
|
module Kenui
|
|
5
5
|
class EmailNotification
|
|
6
|
-
ERROR_MESSAGE = I18n.
|
|
6
|
+
ERROR_MESSAGE = I18n.t('errors.messages.email_notification_plugin_not_available')
|
|
7
7
|
class << self
|
|
8
8
|
def email_notification_plugin_available?(options_for_klient)
|
|
9
9
|
is_available = ::Kenui::EmailNotificationService.email_notification_plugin_available?(options_for_klient)
|
|
@@ -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>
|
|
@@ -59,13 +59,50 @@
|
|
|
59
59
|
document.addEventListener("DOMContentLoaded", () => {
|
|
60
60
|
const input = document.getElementById("search-box");
|
|
61
61
|
const dropdown = document.getElementById("search-suggestions");
|
|
62
|
+
const validPrefixes = ['account:', 'invoice:', 'payment:', 'bundle:', 'custom field:', 'subscription:', 'tag:', 'transaction:', 'credit:', 'invoice payment:', 'tag definition:'];
|
|
63
|
+
|
|
64
|
+
// Get current prefix and search term from input value
|
|
65
|
+
function parseInput(value) {
|
|
66
|
+
const colonIndex = value.indexOf(':');
|
|
67
|
+
if (colonIndex === -1) {
|
|
68
|
+
return { prefix: '', searchTerm: value };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
prefix: value.substring(0, colonIndex + 1).toLowerCase(),
|
|
72
|
+
searchTerm: value.substring(colonIndex + 1)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Ensure input always has a valid prefix
|
|
77
|
+
function ensurePrefix() {
|
|
78
|
+
const { prefix, searchTerm } = parseInput(input.value);
|
|
79
|
+
const hasValidPrefix = validPrefixes.some(p => prefix === p.toLowerCase());
|
|
80
|
+
if (!hasValidPrefix) {
|
|
81
|
+
input.value = 'account: ' + input.value.replace(/^[^:]*:?\s*/, '');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
62
84
|
|
|
63
85
|
input.addEventListener("input", () => {
|
|
64
86
|
dropdown.classList.remove("d-none");
|
|
87
|
+
ensurePrefix();
|
|
65
88
|
});
|
|
66
89
|
|
|
67
90
|
input.addEventListener("focus", () => {
|
|
68
91
|
dropdown.classList.remove("d-none");
|
|
92
|
+
// Add default prefix when focusing on empty input
|
|
93
|
+
if (input.value.trim() === '') {
|
|
94
|
+
input.value = 'account: ';
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
input.addEventListener("blur", () => {
|
|
99
|
+
// Clear back to empty if only prefix remains
|
|
100
|
+
const trimmed = input.value.trim();
|
|
101
|
+
if (validPrefixes.some(p => trimmed.toLowerCase() === p.trim().toLowerCase())) {
|
|
102
|
+
input.value = '';
|
|
103
|
+
} else {
|
|
104
|
+
ensurePrefix();
|
|
105
|
+
}
|
|
69
106
|
});
|
|
70
107
|
|
|
71
108
|
document.addEventListener("click", (e) => {
|
|
@@ -79,7 +116,8 @@
|
|
|
79
116
|
element.addEventListener("click", (e) => {
|
|
80
117
|
e.preventDefault();
|
|
81
118
|
const term = element.getAttribute("data-term");
|
|
82
|
-
input.value
|
|
119
|
+
const { searchTerm } = parseInput(input.value);
|
|
120
|
+
input.value = term + searchTerm;
|
|
83
121
|
input.focus();
|
|
84
122
|
// Keep dropdown open so user can continue typing
|
|
85
123
|
});
|
|
@@ -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
|
};
|
|
@@ -34,12 +34,18 @@
|
|
|
34
34
|
<%= select_tag :plan_name, options_for_select(@plans), :class => 'form-control' %>
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
37
|
-
<div class="form-group d-flex pb-3
|
|
37
|
+
<div class="form-group d-flex pb-3">
|
|
38
38
|
<%= label_tag :price_override, 'Price Override', :class => 'col-sm-3 control-label' %>
|
|
39
39
|
<div class="col-sm-9">
|
|
40
40
|
<%= number_field_tag :price_override, nil, :step => :any, :min => 0, :class => 'form-control' %>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
43
|
+
<div class="form-group d-flex pb-3 border-bottom mb-3">
|
|
44
|
+
<%= label_tag :quantity, 'Quantity', :class => 'col-sm-3 control-label' %>
|
|
45
|
+
<div class="col-sm-9">
|
|
46
|
+
<%= number_field_tag :quantity, 1, :min => 1, :step => 1, :class => 'form-control' %>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
43
49
|
<div class="form-group d-flex pb-3">
|
|
44
50
|
<%= label_tag :type_change, 'Type', :class => 'col-sm-3 control-label' %>
|
|
45
51
|
<div class="col-sm-offset-2 col-sm-9">
|