kaui 4.0.19 → 4.0.20
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/controllers/kaui/admin_tenants_controller.rb +41 -3
- data/app/controllers/kaui/subscriptions_controller.rb +20 -13
- data/app/models/kaui/admin_tenant.rb +4 -0
- data/app/views/kaui/admin_tenants/_form_catalog_translation.erb +1 -1
- data/app/views/kaui/admin_tenants/_form_invoice_template.erb +23 -2
- data/app/views/kaui/admin_tenants/_form_invoice_translation.erb +1 -1
- data/app/views/kaui/subscriptions/_edit_form.html.erb +15 -5
- data/app/views/kaui/subscriptions/_form.html.erb +18 -8
- data/app/views/kaui/subscriptions/record_usage.erb +70 -8
- data/config/locales/en.yml +1 -0
- data/config/routes.rb +1 -0
- data/lib/kaui/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef67e5aac2cf29cee71a7a52b5b87452afc410ed711d1d55c08225dc39b1d044
|
|
4
|
+
data.tar.gz: 6232e9397218afea690a6d45422623b16fb346489a380eaaa6fdba3ed6e98930
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2cd48b2725369b2a466e49cabbf656ce64b6bea4cdcb272f97fb7d34c909bdb56a6876c13af14328a1dcdd0d341e9d04757318d9504aeece87d55ad2ba8a6fa
|
|
7
|
+
data.tar.gz: 8abadbb98b2d800acb54b5dd7fd9bdcf20a8a49186c1b3b9b2357eb3a717ff6a79a45094aebcfaa252a11a8ca562ac5ab2812080c6abb3774ea711b57d98099b
|
|
@@ -47,6 +47,12 @@ module Kaui
|
|
|
47
47
|
@overdue_xml = nil
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
fetch_invoice_template = promise do
|
|
51
|
+
Kaui::AdminTenant.get_invoice_template(false, options)
|
|
52
|
+
rescue StandardError
|
|
53
|
+
@invoice_template = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
50
56
|
fetch_tenant_plugin_config = promise { Kaui::AdminTenant.get_tenant_plugin_config(options) }
|
|
51
57
|
|
|
52
58
|
@catalog_versions = []
|
|
@@ -64,6 +70,12 @@ module Kaui
|
|
|
64
70
|
@overdue = flash[:overdue_deleted] ? nil : wait(fetch_overdue)
|
|
65
71
|
@overdue_xml = flash[:overdue_deleted] ? nil : wait(fetch_overdue_xml)
|
|
66
72
|
@overdue_config_exists = @overdue_xml.present? || (@overdue&.overdue_states.present? && !@overdue.has_states)
|
|
73
|
+
@invoice_template = begin
|
|
74
|
+
wait(fetch_invoice_template)
|
|
75
|
+
rescue StandardError
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
67
79
|
@tenant_plugin_config = begin
|
|
68
80
|
wait(fetch_tenant_plugin_config)
|
|
69
81
|
rescue StandardError
|
|
@@ -367,6 +379,22 @@ module Kaui
|
|
|
367
379
|
redirect_to admin_tenant_path(current_tenant.id, active_tab: 'OverdueShow'), notice: I18n.t('flashes.notices.overdue_deleted_successfully')
|
|
368
380
|
end
|
|
369
381
|
|
|
382
|
+
def invoice_template
|
|
383
|
+
current_tenant = safely_find_tenant_by_id(params[:id])
|
|
384
|
+
|
|
385
|
+
options = tenant_options_for_client
|
|
386
|
+
options[:api_key] = current_tenant.api_key
|
|
387
|
+
options[:api_secret] = current_tenant.api_secret
|
|
388
|
+
|
|
389
|
+
template = Kaui::AdminTenant.get_invoice_template(false, options)
|
|
390
|
+
|
|
391
|
+
if template.present?
|
|
392
|
+
render body: template, content_type: 'text/html'
|
|
393
|
+
else
|
|
394
|
+
render plain: 'No invoice template found', status: :not_found
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
370
398
|
def upload_invoice_template
|
|
371
399
|
current_tenant = safely_find_tenant_by_id(params[:id])
|
|
372
400
|
|
|
@@ -380,7 +408,7 @@ module Kaui
|
|
|
380
408
|
|
|
381
409
|
Kaui::AdminTenant.upload_invoice_template(invoice_template, is_manual_pay, true, options[:username], nil, comment, options)
|
|
382
410
|
|
|
383
|
-
redirect_to admin_tenant_path(current_tenant.id), notice: I18n.t('flashes.notices.invoice_template_uploaded_successfully')
|
|
411
|
+
redirect_to admin_tenant_path(current_tenant.id, active_tab: 'InvoiceTemplate'), notice: I18n.t('flashes.notices.invoice_template_uploaded_successfully')
|
|
384
412
|
end
|
|
385
413
|
|
|
386
414
|
def upload_invoice_translation
|
|
@@ -394,9 +422,14 @@ module Kaui
|
|
|
394
422
|
uploaded_invoice_translation = params.require(:invoice_translation)
|
|
395
423
|
invoice_translation = uploaded_invoice_translation.read
|
|
396
424
|
|
|
425
|
+
if locale.blank?
|
|
426
|
+
flash[:error] = I18n.t('errors.messages.locale_required')
|
|
427
|
+
redirect_to admin_tenant_path(current_tenant.id, active_tab: 'InvoiceTranslation') and return
|
|
428
|
+
end
|
|
429
|
+
|
|
397
430
|
Kaui::AdminTenant.upload_invoice_translation(invoice_translation, locale, true, options[:username], nil, comment, options)
|
|
398
431
|
|
|
399
|
-
redirect_to admin_tenant_path(current_tenant.id), notice: I18n.t('flashes.notices.invoice_translation_uploaded_successfully')
|
|
432
|
+
redirect_to admin_tenant_path(current_tenant.id, active_tab: 'InvoiceTranslation'), notice: I18n.t('flashes.notices.invoice_translation_uploaded_successfully')
|
|
400
433
|
end
|
|
401
434
|
|
|
402
435
|
def upload_catalog_translation
|
|
@@ -410,9 +443,14 @@ module Kaui
|
|
|
410
443
|
uploaded_catalog_translation = params.require(:catalog_translation)
|
|
411
444
|
catalog_translation = uploaded_catalog_translation.read
|
|
412
445
|
|
|
446
|
+
if locale.blank?
|
|
447
|
+
flash[:error] = I18n.t('errors.messages.locale_required')
|
|
448
|
+
redirect_to admin_tenant_path(current_tenant.id, active_tab: 'CatalogTranslation') and return
|
|
449
|
+
end
|
|
450
|
+
|
|
413
451
|
Kaui::AdminTenant.upload_catalog_translation(catalog_translation, locale, true, options[:username], nil, comment, options)
|
|
414
452
|
|
|
415
|
-
redirect_to admin_tenant_path(current_tenant.id), notice: I18n.t('flashes.notices.catalog_translation_uploaded_successfully')
|
|
453
|
+
redirect_to admin_tenant_path(current_tenant.id, active_tab: 'CatalogTranslation'), notice: I18n.t('flashes.notices.catalog_translation_uploaded_successfully')
|
|
416
454
|
end
|
|
417
455
|
|
|
418
456
|
def upload_plugin_config
|
|
@@ -353,7 +353,7 @@ module Kaui
|
|
|
353
353
|
return nil if raw.blank?
|
|
354
354
|
|
|
355
355
|
entries = raw.respond_to?(:values) ? raw.values : Array(raw)
|
|
356
|
-
phase_meta = (plan_details.phases || []).index_by(&:type)
|
|
356
|
+
phase_meta = plan_details.respond_to?(:phases) ? (plan_details.phases || []).index_by(&:type) : {}
|
|
357
357
|
|
|
358
358
|
overrides = entries.filter_map do |entry|
|
|
359
359
|
entry = entry.to_unsafe_h if entry.respond_to?(:to_unsafe_h)
|
|
@@ -384,18 +384,25 @@ module Kaui
|
|
|
384
384
|
|
|
385
385
|
def build_plan_phases_map(plans_details)
|
|
386
386
|
(plans_details || []).to_h do |pd|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
387
|
+
# PlanDetail objects returned by available_base_plans/available_addons only expose the
|
|
388
|
+
# final phase (product/plan/priceList/finalPhaseBillingPeriod/finalPhaseRecurringPrice)
|
|
389
|
+
# and have no phases method, unlike full catalog Plan objects. Skip phase overrides for those.
|
|
390
|
+
phases = if pd.respond_to?(:phases)
|
|
391
|
+
(pd.phases || []).map do |ph|
|
|
392
|
+
fixed = phase_uses_fixed_price?(ph)
|
|
393
|
+
prices = ph.prices || []
|
|
394
|
+
price_label = if fixed
|
|
395
|
+
'$0.00'
|
|
396
|
+
elsif prices.any?
|
|
397
|
+
format('$%.2f', prices.first.value.to_f)
|
|
398
|
+
else
|
|
399
|
+
''
|
|
400
|
+
end
|
|
401
|
+
{ type: ph.type, fixed: fixed, priceLabel: price_label }
|
|
402
|
+
end
|
|
403
|
+
else
|
|
404
|
+
[]
|
|
405
|
+
end
|
|
399
406
|
[pd.plan, phases]
|
|
400
407
|
end
|
|
401
408
|
end
|
|
@@ -15,6 +15,10 @@ module Kaui
|
|
|
15
15
|
KillBillClient::Model::Invoice.upload_invoice_template(invoice_template, is_manual_pay, delete_if_exists, user, reason, comment, options)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def get_invoice_template(is_manual_pay, options = {})
|
|
19
|
+
KillBillClient::Model::Invoice.get_invoice_template(is_manual_pay, nil, options)
|
|
20
|
+
end
|
|
21
|
+
|
|
18
22
|
def upload_invoice_translation(invoice_translation, locale, delete_if_exists, user = nil, reason = nil, comment = nil, options = {})
|
|
19
23
|
KillBillClient::Model::Invoice.upload_invoice_translation(invoice_translation, locale, delete_if_exists, user, reason, comment, options)
|
|
20
24
|
end
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<div class="form-group d-flex pb-3 border-bottom mb-3">
|
|
22
22
|
<%= label_tag :translation_locale, 'Locale', :class => 'col-sm-2 control-label' %>
|
|
23
23
|
<div class="col-sm-10">
|
|
24
|
-
<%= text_field_tag :translation_locale, nil, :class => 'form-control' %>
|
|
24
|
+
<%= text_field_tag :translation_locale, nil, :class => 'form-control', :required => true, :placeholder => 'e.g. en_US' %>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
<div class="d-flex justify-content-end">
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
<% if can? :config_upload, Kaui::AdminTenant %>
|
|
2
2
|
<div class="tab-pane fade invoice-template form-invoice-template" id="InvoiceTemplate">
|
|
3
|
+
<% if @invoice_template.present? %>
|
|
4
|
+
<div class="invoice-template-display mb-4">
|
|
5
|
+
<h5>Current Invoice Template</h5>
|
|
6
|
+
<div class="card">
|
|
7
|
+
<div class="card-body p-0">
|
|
8
|
+
<iframe src="<%= admin_tenant_invoice_template_path(@tenant.id) %>" style="width: 100%; height: 500px; border: none;" title="Invoice Template Preview"></iframe>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="mt-2">
|
|
12
|
+
<%= render "kaui/components/button/button", {
|
|
13
|
+
label: 'View Source',
|
|
14
|
+
variant: "outline-secondary d-inline-flex align-items-center gap-1",
|
|
15
|
+
type: "button",
|
|
16
|
+
html_class: "kaui-dropdown custom-hover",
|
|
17
|
+
html_options: {
|
|
18
|
+
onclick: '$(this).toggleClass("active"); $(this).next("pre").toggle(); return false;'
|
|
19
|
+
}
|
|
20
|
+
} %>
|
|
21
|
+
<pre class="mb-0 mt-2 p-2 bg-light" style="display: none; max-height: 400px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; font-family: monospace; font-size: 12px;"><%= html_escape(@invoice_template) %></pre>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<% end %>
|
|
25
|
+
|
|
3
26
|
<%= form_tag({:action => :upload_invoice_template}, :method => 'post', :multipart => true, :class => 'form-horizontal') do %>
|
|
4
27
|
<%= hidden_field_tag(:id, @tenant.id) %>
|
|
5
28
|
|
|
@@ -19,8 +42,6 @@
|
|
|
19
42
|
%>
|
|
20
43
|
</div>
|
|
21
44
|
|
|
22
|
-
|
|
23
|
-
|
|
24
45
|
<div class="form-group pb-3 border-bottom mb-3">
|
|
25
46
|
<div class="col-sm-10">
|
|
26
47
|
<div class="checkbox">
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<div class="form-group d-flex pb-3 border-bottom mb-3">
|
|
22
22
|
<%= label_tag :translation_locale, 'Locale', :class => 'col-sm-2 control-label' %>
|
|
23
23
|
<div class="col-sm-10">
|
|
24
|
-
<%= text_field_tag :translation_locale, nil, :class => 'form-control' %>
|
|
24
|
+
<%= text_field_tag :translation_locale, nil, :class => 'form-control', :required => true, :placeholder => 'e.g. en_US' %>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
<div class="d-flex justify-content-end">
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<div style="width: 40px;"></div>
|
|
28
28
|
</div>
|
|
29
29
|
<div id="phase_overrides_rows"></div>
|
|
30
|
-
<button type="button" id="add_phase_override" class="btn w-100 mt-2
|
|
30
|
+
<button type="button" id="add_phase_override" class="btn w-100 mt-2 align-items-center justify-content-center gap-1"
|
|
31
31
|
style="border: 1px dashed #d0d5dd; background: white; color: #2563eb; font-size: 14px;">
|
|
32
32
|
<span style="font-size: 16px; line-height: 1;">+</span> Add another phase
|
|
33
33
|
</button>
|
|
@@ -161,7 +161,13 @@ function initPhaseOverrides() {
|
|
|
161
161
|
var $planSelect = $('select[name="plan_name"]');
|
|
162
162
|
|
|
163
163
|
function currentPhases() {
|
|
164
|
-
|
|
164
|
+
var phases = planPhases[$planSelect.val()] || [];
|
|
165
|
+
var seen = {};
|
|
166
|
+
return phases.filter(function(p) {
|
|
167
|
+
if (!p || !p.type || seen[p.type]) return false;
|
|
168
|
+
seen[p.type] = true;
|
|
169
|
+
return true;
|
|
170
|
+
});
|
|
165
171
|
}
|
|
166
172
|
|
|
167
173
|
function phaseLabel(p) {
|
|
@@ -177,9 +183,13 @@ function initPhaseOverrides() {
|
|
|
177
183
|
|
|
178
184
|
function refreshAddBtn() {
|
|
179
185
|
var phases = currentPhases();
|
|
180
|
-
var used = selectedTypes();
|
|
186
|
+
var used = selectedTypes().filter(function(type) { return type; });
|
|
181
187
|
var hasMore = phases.length > 0 && used.length < phases.length;
|
|
182
|
-
|
|
188
|
+
if (hasMore) {
|
|
189
|
+
$addBtn.css('display', 'flex');
|
|
190
|
+
} else {
|
|
191
|
+
$addBtn.hide();
|
|
192
|
+
}
|
|
183
193
|
$headers.css('display', $rows.find('.phase-override-row').length > 0 ? 'flex' : 'none');
|
|
184
194
|
}
|
|
185
195
|
|
|
@@ -205,7 +215,7 @@ function initPhaseOverrides() {
|
|
|
205
215
|
function addRow(preferredType) {
|
|
206
216
|
var phases = currentPhases();
|
|
207
217
|
if (phases.length === 0) return;
|
|
208
|
-
var used = selectedTypes();
|
|
218
|
+
var used = selectedTypes().filter(function(type) { return type; });
|
|
209
219
|
var next = preferredType && phases.some(function(p) { return p.type === preferredType; }) && used.indexOf(preferredType) === -1
|
|
210
220
|
? preferredType
|
|
211
221
|
: (phases.find(function(p) { return used.indexOf(p.type) === -1; }) || {}).type;
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
<div class="form-group d-flex pb-3">
|
|
38
38
|
<div class="col-sm-3 control-label" style="padding-top: 2px;">
|
|
39
39
|
<div id="toggle_phase_overrides" style="cursor: pointer; user-select: none;">
|
|
40
|
-
<span id="toggle_phase_overrides_caret">&#
|
|
41
|
-
<strong>
|
|
40
|
+
<span id="toggle_phase_overrides_caret">▸</span>
|
|
41
|
+
<strong>Price Override</strong>
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
44
|
-
<div class="col-sm-9" id="phase_overrides_section">
|
|
44
|
+
<div class="col-sm-9" id="phase_overrides_section" style="display: none;">
|
|
45
45
|
<p class="text-muted mb-2" style="font-size: 13px;">Override prices for specific phases. All other phases will use catalog pricing.</p>
|
|
46
46
|
<div id="phase_overrides_headers" class="d-flex mb-1 gap-2" style="display: none;">
|
|
47
47
|
<div style="flex: 1; padding-left: 2px;"><small><strong>Phase</strong></small></div>
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<div style="width: 40px;"></div>
|
|
56
56
|
</div>
|
|
57
57
|
<div id="phase_overrides_rows"></div>
|
|
58
|
-
<button type="button" id="add_phase_override" class="btn w-100 mt-2
|
|
58
|
+
<button type="button" id="add_phase_override" class="btn w-100 mt-2 align-items-center justify-content-center gap-1"
|
|
59
59
|
style="border: 1px dashed #d0d5dd; background: white; color: #2563eb; font-size: 14px;">
|
|
60
60
|
<span style="font-size: 16px; line-height: 1;">+</span> Add another phase
|
|
61
61
|
</button>
|
|
@@ -178,7 +178,13 @@ function initPhaseOverrides() {
|
|
|
178
178
|
var $planSelect = $('select[name="plan_name"]');
|
|
179
179
|
|
|
180
180
|
function currentPhases() {
|
|
181
|
-
|
|
181
|
+
var phases = planPhases[$planSelect.val()] || [];
|
|
182
|
+
var seen = {};
|
|
183
|
+
return phases.filter(function(p) {
|
|
184
|
+
if (!p || !p.type || seen[p.type]) return false;
|
|
185
|
+
seen[p.type] = true;
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
function phaseLabel(p) {
|
|
@@ -194,9 +200,13 @@ function initPhaseOverrides() {
|
|
|
194
200
|
|
|
195
201
|
function refreshAddBtn() {
|
|
196
202
|
var phases = currentPhases();
|
|
197
|
-
var used = selectedTypes();
|
|
203
|
+
var used = selectedTypes().filter(function(type) { return type; });
|
|
198
204
|
var hasMore = phases.length > 0 && used.length < phases.length;
|
|
199
|
-
|
|
205
|
+
if (hasMore) {
|
|
206
|
+
$addBtn.css('display', 'flex');
|
|
207
|
+
} else {
|
|
208
|
+
$addBtn.hide();
|
|
209
|
+
}
|
|
200
210
|
$headers.css('display', $rows.find('.phase-override-row').length > 0 ? 'flex' : 'none');
|
|
201
211
|
}
|
|
202
212
|
|
|
@@ -222,7 +232,7 @@ function initPhaseOverrides() {
|
|
|
222
232
|
function addRow(preferredType) {
|
|
223
233
|
var phases = currentPhases();
|
|
224
234
|
if (phases.length === 0) return;
|
|
225
|
-
var used = selectedTypes();
|
|
235
|
+
var used = selectedTypes().filter(function(type) { return type; });
|
|
226
236
|
var next = preferredType && phases.some(function(p) { return p.type === preferredType; }) && used.indexOf(preferredType) === -1
|
|
227
237
|
? preferredType
|
|
228
238
|
: (phases.find(function(p) { return used.indexOf(p.type) === -1; }) || {}).type;
|
|
@@ -27,7 +27,7 @@
|
|
|
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
|
-
<%= select_tag :unit_type, options_for_select(@unit_types.map { |u| [u, u] }, @unit_type.presence || @unit_types.first), required: true, class: 'form-
|
|
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
32
|
<%= text_field_tag :unit_type, @unit_type, required: true, maxlength: 255, class: 'form-control', placeholder: 'e.g. api-calls' %>
|
|
33
33
|
<% end %>
|
|
@@ -44,11 +44,40 @@
|
|
|
44
44
|
<div class="form-group d-flex pb-3">
|
|
45
45
|
<%= label_tag :record_date, 'Date/Time of Usage', class: 'col-sm-3 control-label' %>
|
|
46
46
|
<div class="col-sm-9">
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
<%
|
|
48
|
+
record_date_value = @record_date.to_s.strip
|
|
49
|
+
record_date_date_value = Time.now.utc.strftime('%Y-%m-%d')
|
|
50
|
+
record_date_time_value = '00:00'
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
if record_date_value.match?(/^\d{4}-\d{2}-\d{2}$/)
|
|
54
|
+
record_date_date_value = record_date_value
|
|
55
|
+
elsif record_date_value.match?(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/)
|
|
56
|
+
record_date_date_value = record_date_value[0, 10]
|
|
57
|
+
record_date_time_value = record_date_value[11, 5]
|
|
58
|
+
elsif !record_date_value.empty?
|
|
59
|
+
parsed_record_date = Time.iso8601(record_date_value).utc
|
|
60
|
+
record_date_date_value = parsed_record_date.strftime('%Y-%m-%d')
|
|
61
|
+
record_date_time_value = parsed_record_date.strftime('%H:%M')
|
|
62
|
+
end
|
|
63
|
+
rescue ArgumentError
|
|
64
|
+
record_date_date_value = Time.now.utc.strftime('%Y-%m-%d')
|
|
65
|
+
record_date_time_value = '00:00'
|
|
66
|
+
end
|
|
67
|
+
%>
|
|
68
|
+
|
|
69
|
+
<div class="d-flex gap-2 align-items-center">
|
|
70
|
+
<%= date_field_tag :record_date_date,
|
|
71
|
+
record_date_date_value,
|
|
72
|
+
required: true,
|
|
73
|
+
class: 'form-control' %>
|
|
74
|
+
<%= time_field_tag :record_date_time,
|
|
75
|
+
record_date_time_value,
|
|
76
|
+
step: 60,
|
|
77
|
+
class: 'form-control' %>
|
|
78
|
+
</div>
|
|
79
|
+
<%= hidden_field_tag :record_date, "#{record_date_date_value}T#{record_date_time_value}" %>
|
|
80
|
+
<small class="form-text text-muted">Pick a date. Time is optional and defaults to 00:00 (UTC) if unchanged.</small>
|
|
52
81
|
</div>
|
|
53
82
|
</div>
|
|
54
83
|
|
|
@@ -86,11 +115,19 @@
|
|
|
86
115
|
document.addEventListener('DOMContentLoaded', function () {
|
|
87
116
|
var form = document.getElementById('record_usage_form');
|
|
88
117
|
if (!form) { return; }
|
|
118
|
+
var unitTypeSelect = document.getElementById('unit_type');
|
|
119
|
+
if (unitTypeSelect && unitTypeSelect.tagName === 'SELECT') {
|
|
120
|
+
unitTypeSelect.title = unitTypeSelect.value || '';
|
|
121
|
+
unitTypeSelect.addEventListener('change', function () {
|
|
122
|
+
unitTypeSelect.title = unitTypeSelect.value || '';
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
89
126
|
form.addEventListener('submit', function (e) {
|
|
90
127
|
var errors = [];
|
|
91
128
|
|
|
92
|
-
|
|
93
|
-
|
|
129
|
+
var unitTypeEl = document.getElementById('unit_type');
|
|
130
|
+
var unitType = unitTypeEl ? (unitTypeEl.value || '').trim() : '';
|
|
94
131
|
if (unitType.length === 0) {
|
|
95
132
|
errors.push('Unit type is required.');
|
|
96
133
|
}
|
|
@@ -101,6 +138,31 @@
|
|
|
101
138
|
errors.push('Amount must be a positive integer.');
|
|
102
139
|
}
|
|
103
140
|
|
|
141
|
+
var dateEl = document.getElementById('record_date_date');
|
|
142
|
+
var timeEl = document.getElementById('record_date_time');
|
|
143
|
+
var hiddenDateEl = document.getElementById('record_date');
|
|
144
|
+
|
|
145
|
+
var datePart = dateEl ? (dateEl.value || '').trim() : '';
|
|
146
|
+
var timePart = timeEl ? (timeEl.value || '').trim() : '';
|
|
147
|
+
if (timePart.length === 0) {
|
|
148
|
+
timePart = '00:00';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
var dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
152
|
+
var timeRegex = /^\d{2}:\d{2}$/;
|
|
153
|
+
if (datePart.length === 0) {
|
|
154
|
+
errors.push('Date/time of usage is required.');
|
|
155
|
+
} else if (!dateRegex.test(datePart)) {
|
|
156
|
+
errors.push('Date of usage must be in YYYY-MM-DD format.');
|
|
157
|
+
} else if (!timeRegex.test(timePart)) {
|
|
158
|
+
errors.push('Time must be in HH:MM format.');
|
|
159
|
+
} else {
|
|
160
|
+
var composedDateTime = datePart + 'T' + timePart;
|
|
161
|
+
if (hiddenDateEl) {
|
|
162
|
+
hiddenDateEl.value = composedDateTime;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
104
166
|
if (errors.length > 0) {
|
|
105
167
|
e.preventDefault();
|
|
106
168
|
alert(errors.join('\n'));
|
data/config/locales/en.yml
CHANGED
|
@@ -98,6 +98,7 @@ en:
|
|
|
98
98
|
unable_to_connect_database: "Unable to connect to the database."
|
|
99
99
|
unable_to_connect_killbill: "Unable to connect to the Kill Bill server."
|
|
100
100
|
error_communicating_killbill: "Error while communicating with the Kill Bill server."
|
|
101
|
+
locale_required: "Please specify a Locale (e.g. en_US) for the translation file."
|
|
101
102
|
views:
|
|
102
103
|
subscriptions:
|
|
103
104
|
requested_date_for_billing_notice: <ul>
|
data/config/routes.rb
CHANGED
|
@@ -202,6 +202,7 @@ Kaui::Engine.routes.draw do
|
|
|
202
202
|
post '/modify_overdue_config' => 'admin_tenants#modify_overdue_config', :as => 'admin_tenant_modify_overdue_config'
|
|
203
203
|
post '/upload_overdue_config' => 'admin_tenants#upload_overdue_config', :as => 'admin_tenant_upload_overdue_config'
|
|
204
204
|
post '/upload_invoice_template' => 'admin_tenants#upload_invoice_template', :as => 'admin_tenant_upload_invoice_template'
|
|
205
|
+
get '/:id/invoice_template' => 'admin_tenants#invoice_template', :as => 'admin_tenant_invoice_template'
|
|
205
206
|
post '/upload_invoice_translation' => 'admin_tenants#upload_invoice_translation', :as => 'admin_tenant_upload_invoice_translation'
|
|
206
207
|
post '/upload_catalog_translation' => 'admin_tenants#upload_catalog_translation', :as => 'admin_tenant_upload_catalog_translation'
|
|
207
208
|
post '/upload_plugin_config' => 'admin_tenants#upload_plugin_config', :as => 'admin_tenant_upload_plugin_config'
|
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.20
|
|
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-07-
|
|
11
|
+
date: 2026-07-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actionpack
|