TT#32910 TT#33037 initial cash_balance, free_time_balance

+ create topup_log records for edit balance operations

Change-Id: I6cecbf6025111198b8c43a33b0ffd0f0e30955ad
(cherry picked from commit c81ca69b70)
changes/50/21150/2
Rene Krenn 7 years ago
parent a07c718042
commit 90dfbb534d

@ -21,7 +21,7 @@ sub allowed_methods{
}
sub api_description {
return 'Defines customer balances to access cash and free time balance.';
return 'Defines customer balances to access cash and free-time balance.';
};
sub query_params {

@ -129,6 +129,7 @@ sub PATCH :Allow {
my $item = $self->item_by_id($c, $id, $now);
last unless $self->resource_exists($c, customerbalance => $item);
my $old_resource = { $item->get_inflated_columns };
$old_resource->{cash_balance} /= 100.0 if defined $old_resource->{cash_balance};
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
@ -137,7 +138,8 @@ sub PATCH :Allow {
last unless $item;
my $hal = $self->hal_from_item($c, $item, $form);
last unless $self->add_update_journal_item_hal($c,$hal);
last unless $self->add_update_journal_item_hal($c, $hal);
$guard->commit;
@ -176,6 +178,7 @@ sub PUT :Allow {
);
last unless $resource;
my $old_resource = { $item->get_inflated_columns };
$old_resource->{cash_balance} /= 100.0 if defined $old_resource->{cash_balance};
my $form = $self->get_form($c);
$item = $self->update_item($c, $item, $old_resource, $resource, $form, $now);

@ -1020,6 +1020,8 @@ sub edit_balance :Chained('base_restricted') :PathPart('balance/edit') :Args(0)
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
my $entities = { contract => $contract, };
my $log_vals = {};
try {
my $schema = $c->model('DB');
$schema->set_transaction_isolation('READ COMMITTED');
@ -1027,11 +1029,21 @@ sub edit_balance :Chained('base_restricted') :PathPart('balance/edit') :Args(0)
$balance = NGCP::Panel::Utils::ProfilePackages::get_contract_balance(c => $c,
contract => $contract,
now => $now);
$balance = NGCP::Panel::Utils::ProfilePackages::underrun_update_balance(c => $c,
balance =>$balance,
$balance = NGCP::Panel::Utils::ProfilePackages::set_contract_balance(
c => $c,
balance => $balance,
cash_balance => $form->values->{cash_balance},
free_time_balance => $form->values->{free_time_balance},
now => $now,
new_cash_balance => $form->values->{cash_balance} );
$balance->update($form->values);
log_vals => $log_vals);
my $topup_log = NGCP::Panel::Utils::ProfilePackages::create_topup_log_record(
c => $c,
now => $now,
entities => $entities,
log_vals => $log_vals,
request_token => NGCP::Panel::Utils::ProfilePackages::PANEL_TOPUP_REQUEST_TOKEN,
);
});
NGCP::Panel::Utils::Message::info(
c => $c,

@ -91,6 +91,18 @@ has_field 'cash_balance' => (
},
);
has_field 'initial_cash_balance' => (
type => 'Money',
#label => 'Cash Balance',
#required => 1,
#inflate_method => sub { return $_[1] * 100 },
#deflate_method => sub { return $_[1] / 100 },
element_attr => {
rel => ['tooltip'],
title => ['The interval\'s initial cash balance of the contract in EUR/USD/etc.']
},
);
has_field 'cash_debit' => (
type => 'Money',
#label => 'Cash Balance',
@ -113,6 +125,16 @@ has_field 'free_time_balance' => (
},
);
has_field 'initial_free_time_balance' => (
type => 'Integer',
#label => 'Free-Time Balance',
#required => 1,
element_attr => {
rel => ['tooltip'],
title => ['The interval\'s initial free-time balance of the contract in seconds.']
},
);
has_field 'free_time_spent' => (
type => 'Integer',
#label => 'Free-Time Balance',

@ -4,6 +4,16 @@ use HTML::FormHandler::Moose;
extends 'NGCP::Panel::Form::Balance::CustomerBalance';
use Moose::Util::TypeConstraints;
has_field 'cash_balance' => (
type => 'Money',
label => 'Cash Balance',
required => 1,
element_attr => {
rel => ['tooltip'],
title => ['The current cash balance of the customer in EUR/USD/etc.']
},
);
has_field 'cash_debit' => (
type => 'Money',
#label => 'Cash Balance (Interval)',

@ -25,6 +25,7 @@ has_field 'type' => (
options => [
{ value => 'cash', label => 'Cash top-up' },
{ value => 'voucher', label => 'Voucher top-up' },
{ value => 'set_balance', label => 'Balance edited' },
],
required => 1,
);

@ -80,6 +80,7 @@ sub hal_from_balance {
my %resource = $item->get_inflated_columns;
$resource{cash_balance} /= 100.0;
$resource{initial_cash_balance} /= 100.0;
$resource{cash_debit} = (delete $resource{cash_balance_interval}) / 100.0;
$resource{free_time_spent} = delete $resource{free_time_balance_interval};
my $datetime_fmt = DateTime::Format::Strptime->new(

@ -102,6 +102,14 @@ sub item_by_id {
sub update_item {
my ($self, $c, $item, $old_resource, $resource, $form, $now) = @_;
# remove any readonly field before validation:
my %ro_fields = map { $_ => 1; } keys %$resource;
$ro_fields{cash_balance} = 0;
$ro_fields{free_time_balance} = 0;
foreach my $field (keys %$resource) {
delete $resource->{$field} if $ro_fields{$field};
}
$form //= $self->get_form($c);
return unless $self->validate_form(
c => $c,
@ -109,17 +117,24 @@ sub update_item {
resource => $resource,
);
$item = NGCP::Panel::Utils::ProfilePackages::underrun_update_balance(c => $c,
my $entities = { contract => $item->contract, };
my $log_vals = {};
$item = NGCP::Panel::Utils::ProfilePackages::set_contract_balance(
c => $c,
balance => $item,
cash_balance => $resource->{cash_balance} * 100.0,
free_time_balance => $resource->{free_time_balance},
now => $now,
new_cash_balance => $resource->{cash_balance} * 100.0);
log_vals => $log_vals);
my $topup_log = NGCP::Panel::Utils::ProfilePackages::create_topup_log_record(
c => $c,
now => $now,
entities => $entities,
log_vals => $log_vals,
request_token => NGCP::Panel::Utils::ProfilePackages::API_DEFAULT_TOPUP_REQUEST_TOKEN,
);
$resource->{cash_balance} *= 100.0;
# ignoring cash_debit and free_time_spent:
$item->update({
cash_balance => $resource->{cash_balance},
free_time_balance => $resource->{free_time_balance},
});
$item->discard_changes;
return $item;

@ -44,6 +44,7 @@ use constant _ENABLE_UNDERRUN_PROFILES => 1;
use constant _ENABLE_UNDERRUN_LOCK => 1;
use constant PANEL_TOPUP_REQUEST_TOKEN => 'panel';
use constant API_DEFAULT_TOPUP_REQUEST_TOKEN => 'api';
sub get_contract_balance {
my %params = @_;
@ -382,11 +383,56 @@ PREPARE_BALANCE_CATCHUP:
}
sub set_contract_balance {
my %params = @_;
my($c,$balance,$cash_balance,$free_time_balance,$now,$schema,$log_vals) = @params{qw/c balance cash_balance free_time_balance now schema log_vals/};
$schema //= $c->model('DB');
my $contract;
$contract = $balance->contract if $log_vals;
$now //= NGCP::Panel::Utils::DateTime::current_local;
$cash_balance //= $balance->cash_balance;
$free_time_balance //= $balance->free_time_balance;
$c->log->debug('set contract ' . $contract->id . ' cash_balance from ' . $balance->cash_balance . ' to ' . $cash_balance . ', free_time_balance from ' . $balance->free_time_balance . ' to ' . $free_time_balance);
if ($log_vals) {
my $package = $contract->profile_package;
$log_vals->{old_package} = ( $package ? { $package->get_inflated_columns } : undef);
$log_vals->{new_package} = $log_vals->{old_package};
$log_vals->{old_balance} = { $balance->get_inflated_columns };
my $bm_actual = get_actual_billing_mapping(schema => $schema, contract => $contract, now => $now);
my $profile = $bm_actual->billing_mappings->first->billing_profile;
$log_vals->{old_profile} = { $profile->get_inflated_columns };
$log_vals->{amount} = $cash_balance - $balance->cash_balance;
}
$balance = _underrun_update_balance(c => $c,
balance =>$balance,
now => $now,
new_cash_balance => $cash_balance );
$balance->update({
cash_balance => $cash_balance,
free_time_balance => $free_time_balance,
});
$contract->discard_changes();
if ($log_vals) {
$log_vals->{new_balance} = { $balance->get_inflated_columns };
my $bm_actual = get_actual_billing_mapping(schema => $schema, contract => $contract, now => $now);
my $profile = $bm_actual->billing_mappings->first->billing_profile;
$log_vals->{new_profile} = { $profile->get_inflated_columns };
}
return $balance;
}
sub topup_contract_balance {
my %params = @_;
my($c,$contract,$package,$voucher,$amount,$now,$request_token,$schema,$log_vals,$subscriber) = @params{qw/c contract package voucher amount now request_token schema log_vals subscriber/};
$schema = $c->model('DB');
$schema //= $c->model('DB');
$contract = lock_contracts(schema => $schema, contract_id => $contract->id);
$now //= NGCP::Panel::Utils::DateTime::current_local;
@ -508,8 +554,8 @@ sub create_topup_log_record {
return $c->model('DB')->resultset('topup_logs')->create({
username => $username,
timestamp => $now->hires_epoch,
type => ($is_cash ? 'cash' : 'voucher'),
outcome => ($is_success ? 'ok' : 'failed'),
type => (defined $is_cash ? ($is_cash ? 'cash' : 'voucher') : 'set_balance'),
outcome => ((not defined $is_cash or $is_success) ? 'ok' : 'failed'),
message => (defined $message ? substr($message,0,255) : undef),
subscriber_id => ($entities->{subscriber} ? $entities->{subscriber}->id : $resource->{subscriber_id}),
contract_id => ($entities->{contract} ? $entities->{contract}->id : $resource->{contract_id}),
@ -696,8 +742,10 @@ sub _get_balance_values {
$free_time_balance_interval = 0;
return [cash_balance => sprintf("%.4f",$cash_balance),
initial_cash_balance => sprintf("%.4f",$cash_balance),
cash_balance_interval => sprintf("%.4f",$cash_balance_interval),
free_time_balance => sprintf("%.0f",$free_time_balance),
initial_free_time_balance => sprintf("%.0f",$free_time_balance),
free_time_balance_interval => sprintf("%.0f",$free_time_balance_interval)];
}
@ -964,7 +1012,7 @@ sub underrun_lock_subscriber {
}
}
sub underrun_update_balance {
sub _underrun_update_balance {
my %params = @_;
my ($c,$balance,$new_cash_balance,$now,$schema) = @params{qw/c balance new_cash_balance now schema/};
$schema = $c->model('DB');
@ -1394,12 +1442,19 @@ sub get_balanceinterval_datatable_cols {
#convert_code => sub { my $s = shift; return $s if ($parser_date->parse_datetime($s) or $parser_datetime->parse_datetime($s)); } },
{ name => "end", search => 0, search_upper_column => 'interval', title => $c->loc('To'), },
#convert_code => sub { my $s = shift; return $s if ($parser_date->parse_datetime($s) or $parser_datetime->parse_datetime($s)); } },
{ name => "balance", search => 0, title => $c->loc('Cash'), literal_sql => "FORMAT(cash_balance / 100,2)" },
{ name => "initial_balance", search => 0, title => $c->loc('Initial Cash'), literal_sql => "FORMAT(initial_cash_balance / 100,2)" },
{ name => "balance", search => 0, title => $c->loc('Cash Balance'), literal_sql => "FORMAT(cash_balance / 100,2)" },
{ name => "debit", search => 0, title => $c->loc('Debit'), literal_sql => "FORMAT(cash_balance_interval / 100,2)" },
{ name => "topup_count", search => 0, title => $c->loc('#Top-ups') },
{ name => "timely_topup_count", search => 0, title => $c->loc('#Timely Top-ups') },
{ name => "underrun_profiles", search => 0, title => $c->loc('Underrun detected (Profiles)') },
{ name => "underrun_lock", search => 0, title => $c->loc('Underrun detected (Lock)') },
{ name => "initial_free_time_balance", search => 0, title => $c->loc('Initial Free-Time') },
{ name => "free_time_balance", search => 0, title => $c->loc('Free-Time Balance') },
{ name => "free_time_interval", search => 0, title => $c->loc('Free-Time spent') },
{ name => "topups", search => 0, title => $c->loc('#Top-ups (timely)'), literal_sql => 'CONCAT(topup_count," (",timely_topup_count,")")' },
{ name => "underrun_profiles", search => 0, title => $c->loc('Last Underrun (Profiles)') },
{ name => "underrun_lock", search => 0, title => $c->loc('Last Underrun (Lock)') },
);
}

@ -134,7 +134,6 @@ sub check_topup {
return 0 unless &{$err_code}('Voucher belongs to another reseller.');
}
$entities->{voucher} = $voucher if defined $entities;
} else {
my $package = undef;
if (defined $package_id) {

@ -446,7 +446,7 @@ $(function() {
<table class="table table-bordered table-striped table-highlight table-hover">
<thead>
<th>
<th colspan="4"></th>
<th colspan="6"></th>
</th>
</thead>
<tbody>
@ -454,17 +454,21 @@ $(function() {
[% money_format = format('%.2f') %]
<tr>
<td>[% c.loc('Cash balance') %]</td>
<td>[% money_format(balance.cash_balance / 100) %]</td>
<td>[% c.loc('Debit') %]</td>
<td>[% money_format( balance.cash_balance_interval / 100 ) %]</td>
<td>[% c.loc('Initial Cash Balance:') %]</td>
<td><b>[% money_format(balance.initial_cash_balance / 100) %]</b></td>
<td>[% c.loc('Cash Balance:') %]</td>
<td><b>[% money_format(balance.cash_balance / 100) %]</b></td>
<td>[% c.loc('Debit:') %]</td>
<td><b>[% money_format( balance.cash_balance_interval / 100 ) %]</b></td>
</tr>
<tr>
<td>[% c.loc('Free time balance') %]</td>
<td>[% balance.free_time_balance %]</td>
<td>[% c.loc('Free time spent') %]</td>
<td>[% balance.free_time_balance_interval %]</td>
<td>[% c.loc('Initial Free-Time Balance:') %]</td>
<td><b>[% balance.initial_free_time_balance %]</b></td>
<td>[% c.loc('Free-Time Balance:') %]</td>
<td><b>[% balance.free_time_balance %]</b></td>
<td>[% c.loc('Free-Time spent:') %]</td>
<td><b>[% balance.free_time_balance_interval %]</b></td>
</tr>
</tbody>
</table>
@ -477,22 +481,22 @@ $(function() {
</thead>
<tr>
<td>[% c.loc('Interval from') %]</td>
<td>[% balance.start %]</td>
<td>[% c.loc('Interval to') %]</td>
<td>[% balance.end %]</td>
<td>[% c.loc('Interval from:') %]</td>
<td><b>[% balance.start %]</b></td>
<td>[% c.loc('Interval to:') %]</td>
<td><b>[% balance.end %]</b></td>
</tr>
<tr>
<td>[% c.loc('"Timely" top-ups from') %]</td>
<td>[% timely_topup_start %]</td>
<td>[% c.loc('"Timely" top-ups to') %]</td>
<td>[% timely_topup_end %]</td>
<td>[% c.loc('"Timely" Top-ups from:') %]</td>
<td><b>[% timely_topup_start %]</b></td>
<td>[% c.loc('"Timely" Top-ups to:') %]</td>
<td><b>[% timely_topup_end %]</b></td>
</tr>
<tr>
<td colspan="2">[% c.loc('Balance will be discarded, if no tup-up happens until') %]</td>
<td colspan="2">[% notopup_expiration %]</td>
<td colspan="2">[% c.loc('Balance will be discarded, if no tup-up happens until:') %]</td>
<td colspan="2"><b>[% notopup_expiration %]</b></td>
</tr>
</tbody>
@ -509,16 +513,16 @@ $(function() {
[% money_format = format('%.2f') %]
<tr>
<td>[% c.loc('Actual profile package') %]</td>
<td>[% c.loc('Actual profile package:') %]</td>
<td>[% package.name %]</td>
<td>[% c.loc('Actual billing profile') %]</td>
<td>[% c.loc('Actual billing profile:') %]</td>
<td>[% mapping.billing_profile.name %]</td>
</tr>
<tr>
<td>[% c.loc('Balance threshold when underrun profiles get applied') %]</td>
<td>[% c.loc('Cash balance threshold when underrun profiles get applied:') %]</td>
<td>[% package.underrun_profile_threshold ? money_format( package.underrun_lock_threshold / 100 ) : '' %]</td>
<td>[% c.loc('Balance threshold when subscribers will be locked') %]</td>
<td>[% c.loc('Cash balance threshold when subscribers will be locked:') %]</td>
<td>[% package.underrun_lock_threshold ? money_format( package.underrun_lock_threshold / 100 ) : '' %]</td>
</tr>
</tbody>

@ -139,7 +139,7 @@ my $request_count = 0;
}
SKIP:
#SKIP:
{
my $profile_initial_1 = _create_billing_profile('INITIAL1');
my $profile_topup_1 = _create_billing_profile('TOPUP1');
@ -152,8 +152,6 @@ SKIP:
#underrun_profiles => [ { profile_id => $profile_underrun->{id}, }, ],
);
my $customer = _create_customer(billing_profile_definition => 'package',
profile_package_id => $package_1->{id},);
my $subscriber = _create_subscriber($customer);
@ -229,6 +227,59 @@ SKIP:
}
SKIP:
{
my $profile = _create_billing_profile('PROFILE_2');
my $customer = _create_customer(billing_profile_definition => 'id',
billing_profile_id => $profile->{id},);
my $cash_balance = 5; # euro
_perform_set_balance($customer,$cash_balance,0);
_check_topup_log('set cash balance: ',[
{
outcome => 'ok',
amount => $cash_balance,
profile_before_id => $profile->{id},
profile_after_id => $profile->{id},
cash_balance_before => 0,
cash_balance_after => $cash_balance,
},
],'contract_id='.$customer->{id});
# patch free time balance only:
$req = HTTP::Request->new('PATCH', $uri.'/api/customerbalances/'.$customer->{id});
$req->header('Prefer' => 'return=representation');
$req->header('Content-Type' => 'application/json-patch+json');
$req->content(JSON::to_json(
[ { op => 'replace', path => '/free_time_balance', value => 0 } ]
, { allow_nonref => 1, allow_blessed => 1, convert_blessed => 1, pretty => 0 }));
$res = $ua->request($req);
is($res->code, 200, "patch customer balances free time balance only");
my $customerbalance = JSON::from_json($res->decoded_content, { allow_nonref => 1, });
_check_topup_log('patch free time balance: ',[
{
outcome => 'ok',
amount => $cash_balance,
profile_before_id => $profile->{id},
profile_after_id => $profile->{id},
cash_balance_before => 0,
cash_balance_after => $cash_balance,
},
{
outcome => 'ok',
amount => 0,
profile_before_id => $profile->{id},
profile_after_id => $profile->{id},
cash_balance_before => $cash_balance,
cash_balance_after => $cash_balance,
},
],'contract_id='.$customer->{id});
}
done_testing;
sub _check_topup_log {
@ -452,6 +503,22 @@ sub _perform_topup_cash {
}
sub _perform_set_balance {
my ($customer,$cash_balance,$free_time_balance,$error_code) = @_;
$req = HTTP::Request->new('PUT', $uri.'/api/customerbalances/' . $customer->{id});
$req->header('Content-Type' => 'application/json');
my $req_data = {
cash_balance => $cash_balance,
free_time_balance => $free_time_balance,
};
$req->content(JSON::to_json($req_data));
$res = $ua->request($req);
$error_code //= 204;
is($res->code, $error_code, ($error_code == 204 ? 'perform' : 'attempt')." setting cash_balance to " . $cash_balance . ", free_time_balance to $free_time_balance");
}
sub _create_billing_profile {
my ($name) = @_;
$req = HTTP::Request->new('POST', $uri.'/api/billingprofiles/');

Loading…
Cancel
Save