diff --git a/lib/NGCP/Panel/Controller/API/CustomerBalances.pm b/lib/NGCP/Panel/Controller/API/CustomerBalances.pm index 4bddeff582..4d28dee5ca 100644 --- a/lib/NGCP/Panel/Controller/API/CustomerBalances.pm +++ b/lib/NGCP/Panel/Controller/API/CustomerBalances.pm @@ -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 { diff --git a/lib/NGCP/Panel/Controller/API/CustomerBalancesItem.pm b/lib/NGCP/Panel/Controller/API/CustomerBalancesItem.pm index f7ff77f178..4adb4f5a17 100644 --- a/lib/NGCP/Panel/Controller/API/CustomerBalancesItem.pm +++ b/lib/NGCP/Panel/Controller/API/CustomerBalancesItem.pm @@ -62,7 +62,7 @@ sub auto :Private { $self->set_body($c); $self->log_request($c); - #$self->apply_fake_time($c); + #$self->apply_fake_time($c); } sub GET :Allow { @@ -119,7 +119,7 @@ sub PATCH :Allow { last unless $preference; my $json = $self->get_valid_patch_data( - c => $c, + c => $c, id => $id, media_type => 'application/json-patch+json', ); @@ -129,16 +129,18 @@ 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; my $form = $self->get_form($c); $item = $self->update_item($c, $item, $old_resource, $resource, $form, $now); 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; if ('minimal' eq $preference) { @@ -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); @@ -183,8 +186,8 @@ sub PUT :Allow { my $hal = $self->hal_from_item($c, $item, $form); last unless $self->add_update_journal_item_hal($c,$hal); - - $guard->commit; + + $guard->commit; if ('minimal' eq $preference) { $c->response->status(HTTP_NO_CONTENT); diff --git a/lib/NGCP/Panel/Controller/Customer.pm b/lib/NGCP/Panel/Controller/Customer.pm index dc9aaef83e..3dcb37eb77 100644 --- a/lib/NGCP/Panel/Controller/Customer.pm +++ b/lib/NGCP/Panel/Controller/Customer.pm @@ -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, diff --git a/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm b/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm index 8a8987d278..c5eaec9727 100644 --- a/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm +++ b/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm @@ -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', diff --git a/lib/NGCP/Panel/Form/Balance/CustomerBalanceAPI.pm b/lib/NGCP/Panel/Form/Balance/CustomerBalanceAPI.pm index 57ba150890..014ef9937a 100644 --- a/lib/NGCP/Panel/Form/Balance/CustomerBalanceAPI.pm +++ b/lib/NGCP/Panel/Form/Balance/CustomerBalanceAPI.pm @@ -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)', diff --git a/lib/NGCP/Panel/Form/Topup/Log.pm b/lib/NGCP/Panel/Form/Topup/Log.pm index 77d89ad7e4..d546023b02 100644 --- a/lib/NGCP/Panel/Form/Topup/Log.pm +++ b/lib/NGCP/Panel/Form/Topup/Log.pm @@ -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, ); diff --git a/lib/NGCP/Panel/Role/API/BalanceIntervals.pm b/lib/NGCP/Panel/Role/API/BalanceIntervals.pm index 75339062d4..3c26da5ecc 100644 --- a/lib/NGCP/Panel/Role/API/BalanceIntervals.pm +++ b/lib/NGCP/Panel/Role/API/BalanceIntervals.pm @@ -15,29 +15,29 @@ use NGCP::Panel::Utils::ProfilePackages qw(); use NGCP::Panel::Utils::DateTime; sub _contract_rs { - + my ($self, $c, $include_terminated,$now) = @_; - + my $item_rs = NGCP::Panel::Utils::Contract::get_contract_rs( schema => $c->model('DB'), include_terminated => (defined $include_terminated && $include_terminated ? 1 : 0), now => $now, - ); + ); if($c->user->roles eq "admin") { } elsif($c->user->roles eq "reseller") { - $item_rs = $item_rs->search({ + $item_rs = $item_rs->search({ 'contact.reseller_id' => $c->user->reseller_id },{ join => 'contact', }); } - return $item_rs; - + return $item_rs; + #my $item_rs = $c->model('DB')->resultset('contract_balances'); #if($c->user->roles eq "admin") { #} elsif($c->user->roles eq "reseller") { - # $item_rs = $item_rs->search({ + # $item_rs = $item_rs->search({ # 'contact.reseller_id' => $c->user->reseller_id # },{ # join => { contract => 'contact' }, @@ -47,10 +47,10 @@ sub _contract_rs { } sub _item_rs { - + my $self = shift; return $self->_contract_rs(@_); - + } sub get_form { @@ -60,7 +60,7 @@ sub get_form { sub hal_from_balance { my ($self, $c, $item, $form, $now, $use_root_collection_link) = @_; - + my $contract = $item->contract; my $is_customer = (defined $contract->contact->reseller_id ? 1 : 0); my $bm_start = NGCP::Panel::Utils::ProfilePackages::get_actual_billing_mapping(c => $c, contract => $contract, now => $item->start); @@ -75,30 +75,31 @@ sub hal_from_balance { $notopup_expiration = NGCP::Panel::Utils::ProfilePackages::get_notopup_expiration( package => $contract->profile_package, contract => $contract, - balance => $item) if $is_actual; + balance => $item) if $is_actual; #my $invoice = $item->invoice; - + 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( - pattern => '%F %T', + pattern => '%F %T', ); $resource{start} = delete $resource{start}; $resource{stop} = delete $resource{end}; $resource{start} = $datetime_fmt->format_datetime($resource{start}) if defined $resource{start}; $resource{stop} = $datetime_fmt->format_datetime($resource{stop}) if defined $resource{stop}; - + $resource{billing_profile_id} = $profile_at_start->id; $resource{timely_topup_start} = (defined $timely_start ? $datetime_fmt->format_datetime($timely_start) : undef); $resource{timely_topup_stop} = (defined $timely_end ? $datetime_fmt->format_datetime($timely_end) : undef); - + $resource{notopup_discard_expiry} = (defined $notopup_expiration ? $datetime_fmt->format_datetime($notopup_expiration) : undef); - + $resource{is_actual} = $is_actual; - + my $hal = NGCP::Panel::Utils::DataHal->new( links => [ NGCP::Panel::Utils::DataHalLink->new( @@ -143,14 +144,14 @@ sub contract_by_id { sub balances_rs { my ($self, $c, $contract, $now) = @_; - + $now //= NGCP::Panel::Utils::DateTime::current_local; NGCP::Panel::Utils::ProfilePackages::catchup_contract_balances(c => $c, contract => $contract, now => $now); - + return $self->apply_query_params($c,$self->can('query_params') ? $self->query_params : {},$contract->contract_balances); - + } sub balance_by_id { @@ -160,12 +161,12 @@ sub balance_by_id { my $balance = NGCP::Panel::Utils::ProfilePackages::catchup_contract_balances(c => $c, contract => $contract, now => $now); - + if (defined $id) { $balance = $contract->contract_balances->find($id); } return $balance; - + } 1; diff --git a/lib/NGCP/Panel/Role/API/CustomerBalances.pm b/lib/NGCP/Panel/Role/API/CustomerBalances.pm index 7653093608..392ce0d6ec 100644 --- a/lib/NGCP/Panel/Role/API/CustomerBalances.pm +++ b/lib/NGCP/Panel/Role/API/CustomerBalances.pm @@ -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; diff --git a/lib/NGCP/Panel/Utils/Billing.pm b/lib/NGCP/Panel/Utils/Billing.pm index 79a6bd3076..952c583690 100644 --- a/lib/NGCP/Panel/Utils/Billing.pm +++ b/lib/NGCP/Panel/Utils/Billing.pm @@ -251,7 +251,7 @@ sub insert_unique_billing_fees{ my($c,$schema,$profile,$fees,$return_created) = @params{qw/c schema profile fees return_created/}; $return_created //= 0; - #while we use lower id we don't need insert records from billing_fees, they are already contain in billing_fees with lower id + #while we use lower id we don't need insert records from billing_fees, they are already contain in billing_fees with lower id $profile->billing_fees_raw->delete(); $schema->storage->dbh_do(sub{ diff --git a/lib/NGCP/Panel/Utils/ProfilePackages.pm b/lib/NGCP/Panel/Utils/ProfilePackages.pm index a58168432c..45659b5a27 100644 --- a/lib/NGCP/Panel/Utils/ProfilePackages.pm +++ b/lib/NGCP/Panel/Utils/ProfilePackages.pm @@ -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)') }, ); } diff --git a/lib/NGCP/Panel/Utils/Voucher.pm b/lib/NGCP/Panel/Utils/Voucher.pm index cfa162e5f3..15b311e3fe 100644 --- a/lib/NGCP/Panel/Utils/Voucher.pm +++ b/lib/NGCP/Panel/Utils/Voucher.pm @@ -45,10 +45,10 @@ sub decrypt_code { sub check_topup { my %params = @_; my ($c,$plain_code,$voucher_id,$now,$subscriber_id,$contract,$package_id,$schema,$err_code,$entities,$resource) = @params{qw/c plain_code voucher_id now subscriber_id contract package_id schema err_code entities resource/}; - + $schema //= $c->model('DB'); $now //= NGCP::Panel::Utils::DateTime::current_local; - + if (!defined $err_code || ref $err_code ne 'CODE') { $err_code = sub { return 0; }; } @@ -71,7 +71,7 @@ sub check_topup { $entities->{subscriber} = $subscriber if defined $entities; $contract //= $subscriber->contract; } - + $entities->{contract} = $contract if defined $entities; unless($contract->status eq 'active') { @@ -80,17 +80,17 @@ sub check_topup { unless($contract->contact->reseller) { return 0 unless &{$err_code}('Contract is not a customer contract.'); } - + # if reseller, check if subscriber_id belongs to the calling reseller if($reseller_id && $reseller_id != $contract->contact->reseller_id) { return 0 unless &{$err_code}('Subscriber customer contract belongs to another reseller.'); } - + if (defined $plain_code || defined $voucher_id) { my $voucher; my $dtf = $schema->storage->datetime_parser; - + if (defined $plain_code) { $voucher = $schema->resultset('vouchers')->search_rs({ code => encrypt_code($c, $plain_code), @@ -120,21 +120,20 @@ sub check_topup { if (defined $resource) { $resource->{voucher_id} = undef if exists $resource->{voucher_id}; $resource->{voucher}->{id} = undef if (exists $resource->{voucher} && exists $resource->{voucher}->{id}); - } + } return 0 unless &{$err_code}("Invalid voucher ID $voucher_id, already used or expired."); - } + } } - + $entities->{voucher} = $voucher if defined $entities; if($voucher->customer_id && $contract->id != $voucher->customer_id) { return 0 unless &{$err_code}('Voucher is reserved for a different customer.'); - } + } unless($voucher->reseller_id == $contract->contact->reseller_id) { return 0 unless &{$err_code}('Voucher belongs to another reseller.'); } - - $entities->{voucher} = $voucher if defined $entities; + } else { my $package = undef; if (defined $package_id) { @@ -143,7 +142,7 @@ sub check_topup { if (defined $resource) { $resource->{package_id} = undef if exists $resource->{package_id}; $resource->{package}->{id} = undef if (exists $resource->{package} && exists $resource->{package}->{id}); - } + } return 0 unless &{$err_code}("Unknown profile package ID $package_id."); } $entities->{package} = $package if defined $entities; @@ -152,15 +151,15 @@ sub check_topup { } } } - + # TODO: add and check billing.vouchers.active flag for internal/emergency use - + return 1; - + } sub get_datatable_cols { - + my ($c,$hide_package) = @_; return ( { name => "id", "search" => 1, "title" => $c->loc("#") }, diff --git a/share/templates/customer/details.tt b/share/templates/customer/details.tt index 8316197457..1b3164bcc3 100644 --- a/share/templates/customer/details.tt +++ b/share/templates/customer/details.tt @@ -446,7 +446,7 @@ $(function() { + @@ -454,17 +454,21 @@ $(function() { [% money_format = format('%.2f') %] - - - - + + + + + + - - - - + + + + + +
-
[% c.loc('Cash balance') %][% money_format(balance.cash_balance / 100) %][% c.loc('Debit') %][% money_format( balance.cash_balance_interval / 100 ) %][% c.loc('Initial Cash Balance:') %][% money_format(balance.initial_cash_balance / 100) %][% c.loc('Cash Balance:') %][% money_format(balance.cash_balance / 100) %][% c.loc('Debit:') %][% money_format( balance.cash_balance_interval / 100 ) %]
[% c.loc('Free time balance') %][% balance.free_time_balance %][% c.loc('Free time spent') %][% balance.free_time_balance_interval %][% c.loc('Initial Free-Time Balance:') %][% balance.initial_free_time_balance %][% c.loc('Free-Time Balance:') %][% balance.free_time_balance %][% c.loc('Free-Time spent:') %][% balance.free_time_balance_interval %]
@@ -477,22 +481,22 @@ $(function() { - [% c.loc('Interval from') %] - [% balance.start %] - [% c.loc('Interval to') %] - [% balance.end %] + [% c.loc('Interval from:') %] + [% balance.start %] + [% c.loc('Interval to:') %] + [% balance.end %] - [% c.loc('"Timely" top-ups from') %] - [% timely_topup_start %] - [% c.loc('"Timely" top-ups to') %] - [% timely_topup_end %] + [% c.loc('"Timely" Top-ups from:') %] + [% timely_topup_start %] + [% c.loc('"Timely" Top-ups to:') %] + [% timely_topup_end %] - [% c.loc('Balance will be discarded, if no tup-up happens until') %] - [% notopup_expiration %] + [% c.loc('Balance will be discarded, if no tup-up happens until:') %] + [% notopup_expiration %] @@ -509,16 +513,16 @@ $(function() { [% money_format = format('%.2f') %] - [% c.loc('Actual profile package') %] + [% c.loc('Actual profile package:') %] [% package.name %] - [% c.loc('Actual billing profile') %] + [% c.loc('Actual billing profile:') %] [% mapping.billing_profile.name %] - [% c.loc('Balance threshold when underrun profiles get applied') %] + [% c.loc('Cash balance threshold when underrun profiles get applied:') %] [% package.underrun_profile_threshold ? money_format( package.underrun_lock_threshold / 100 ) : '' %] - [% c.loc('Balance threshold when subscribers will be locked') %] + [% c.loc('Cash balance threshold when subscribers will be locked:') %] [% package.underrun_lock_threshold ? money_format( package.underrun_lock_threshold / 100 ) : '' %] diff --git a/t/api-rest/api-topuplogs.t b/t/api-rest/api-topuplogs.t index f8e280bb45..ac33f00cf2 100644 --- a/t/api-rest/api-topuplogs.t +++ b/t/api-rest/api-topuplogs.t @@ -31,7 +31,7 @@ if ($is_local_env) { } } $panel_config //= 'ngcp_panel.conf'; - $catalyst_config = Config::General->new($panel_config); + $catalyst_config = Config::General->new($panel_config); } my %config = $catalyst_config->getall(); @@ -85,7 +85,7 @@ my $request_count = 0; #goto SKIP; { my $profile = _create_billing_profile('PROFILE_1'); - + my $customer = _create_customer(billing_profile_definition => 'id', billing_profile_id => $profile->{id},); my $subscriber = _create_subscriber($customer); @@ -97,32 +97,32 @@ my $request_count = 0; _check_topup_log('failing topup cash validation (subscriber_id): ',[ { outcome => 'failed', request_token => $request_token, message => 'Validation failed. field=\'subscriber_id\'' } ],'request_token='.$request_token); - + $request_token = $t."_".$request_count; $request_count++; _perform_topup_cash($subscriber,'invalid_amount',undef,$request_token,422); _check_topup_log('failing topup cash validation (amount): ',[ { outcome => 'failed', request_token => $request_token, message => 'Value cannot be converted to money' } ],'request_token='.$request_token); - + $request_token = $t."_".$request_count; $request_count++; _perform_topup_cash($subscriber,50,{ id => 'invalid' },$request_token,422); _check_topup_log('failing topup cash validation (package_id): ',[ { outcome => 'failed', request_token => $request_token, message => 'Validation failed. field=\'package_id\'' } - ],'request_token='.$request_token); - + ],'request_token='.$request_token); + $request_token = $t."_".$request_count; $request_count++; _perform_topup_voucher($subscriber,{ code => 'invalid' },$request_token,422); _check_topup_log('failing topup voucher validation (voucher code): ',[ { outcome => 'failed', request_token => $request_token, message => 'Invalid voucher code \'invalid\'' } - ],'request_token='.$request_token); - + ],'request_token='.$request_token); + $request_token = $t."_".$request_count; $request_count++; $request_token .= 'a' x (256 - length($request_token)); _perform_topup_voucher($subscriber,$voucher_1,$request_token,422); _check_topup_log('failing topup voucher validation (request_token): ',[ { outcome => 'failed', request_token => substr($request_token,0,255), message => 'Validation failed. field=\'request_token\'' }, #'Field should not exceed 255 characters' } ],'request_token='.substr($request_token,0,255)); - + $request_token = $t."_".$request_count; $request_count++; _perform_topup_voucher($subscriber,$voucher_1,$request_token); $request_token = $t."_".$request_count; $request_count++; @@ -130,16 +130,16 @@ my $request_count = 0; _check_topup_log('failing topup voucher validation (voucher used): ',[ { outcome => 'failed', request_token => $request_token, message => 'already used' } ],'request_token='.$request_token); - + $request_token = $t."_".$request_count; $request_count++; _perform_topup_voucher($subscriber,$voucher_2,$request_token,422); _check_topup_log('failing topup voucher validation (voucher expired): ',[ { outcome => 'failed', request_token => $request_token, message => 'expired' } - ],'request_token='.$request_token); - + ],'request_token='.$request_token); + } -SKIP: +#SKIP: { my $profile_initial_1 = _create_billing_profile('INITIAL1'); my $profile_topup_1 = _create_billing_profile('TOPUP1'); @@ -151,9 +151,7 @@ SKIP: topup_profiles => [ { profile_id => $profile_topup_1->{id}, }, ], #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); @@ -163,12 +161,12 @@ SKIP: _perform_topup_cash($subscriber,0.5,undef,$request_token_1); my $request_token_2 = $t."_".$request_count; $request_count++; _perform_topup_voucher($subscriber,$voucher_1,$request_token_2); - + _check_topup_log('successful topups - subscriber_id, outcome filter: ',[ { outcome => 'ok', request_token => $request_token_1 }, { outcome => 'ok', request_token => $request_token_2 }, ],'subscriber_id='.$subscriber->{id}.'&outcome=ok'); - + _check_topup_log('successful topups - contract_id filter: ',[ { outcome => 'ok', request_token => $request_token_1 }, { outcome => 'ok', request_token => $request_token_2 }, @@ -184,7 +182,7 @@ SKIP: topup_profiles => [ { profile_id => $profile_topup_2->{id}, }, ], #underrun_profiles => [ { profile_id => $profile_underrun->{id}, }, ], ); - + my $voucher_2 = _create_voucher(30,'test4'.$t,$customer,$package_2); my $request_token_3 = $t."_".$request_count; $request_count++; _perform_topup_voucher($subscriber,$voucher_2,$request_token_3); @@ -192,54 +190,107 @@ SKIP: _check_topup_log('successful topups - voucher_id filter: ',[ { outcome => 'ok', request_token => $request_token_3 }, ],'voucher_id='.$voucher_2->{id}); - + _check_topup_log('successful topups - amount_above filter: ',[ - { outcome => 'ok', request_token => $request_token_2 }, + { outcome => 'ok', request_token => $request_token_2 }, { outcome => 'ok', request_token => $request_token_3 }, ],'amount_above=1&subscriber_id='.$subscriber->{id}); _check_topup_log('successful topups - amount_below filter: ',[ - { outcome => 'ok', request_token => $request_token_1 }, + { outcome => 'ok', request_token => $request_token_1 }, { outcome => 'ok', request_token => $request_token_2 }, ],'amount_below=10&subscriber_id='.$subscriber->{id}); - + _check_topup_log('successful topups - timestamp_from filter: ',[ - { outcome => 'ok', request_token => $request_token_1 }, + { outcome => 'ok', request_token => $request_token_1 }, { outcome => 'ok', request_token => $request_token_2 }, - { outcome => 'ok', request_token => $request_token_3 }, + { outcome => 'ok', request_token => $request_token_3 }, ],'timestamp_from=2000-01-01T00:00:00&subscriber_id='.$subscriber->{id}); _check_topup_log('successful topups - balance before/after: ',[ - { outcome => 'ok', cash_balance_before => 1, cash_balance_after => 1.5, request_token => $request_token_1 }, + { outcome => 'ok', cash_balance_before => 1, cash_balance_after => 1.5, request_token => $request_token_1 }, { outcome => 'ok', cash_balance_before => 1.5, cash_balance_after => 11.5, request_token => $request_token_2 }, - { outcome => 'ok', cash_balance_before => 11.5, cash_balance_after => 41.5, request_token => $request_token_3 }, - ],'contract_id='.$customer->{id}); + { outcome => 'ok', cash_balance_before => 11.5, cash_balance_after => 41.5, request_token => $request_token_3 }, + ],'contract_id='.$customer->{id}); _check_topup_log('successful topups - package before/after: ',[ - { outcome => 'ok', package_before_id=> $package_1->{id}, package_after_id=> $package_1->{id}, request_token => $request_token_1 }, + { outcome => 'ok', package_before_id=> $package_1->{id}, package_after_id=> $package_1->{id}, request_token => $request_token_1 }, { outcome => 'ok', package_before_id=> $package_1->{id}, package_after_id=> $package_1->{id}, request_token => $request_token_2 }, - { outcome => 'ok', package_before_id=> $package_1->{id}, package_after_id=> $package_2->{id}, request_token => $request_token_3 }, - ],'contract_id='.$customer->{id}); - + { outcome => 'ok', package_before_id=> $package_1->{id}, package_after_id=> $package_2->{id}, request_token => $request_token_3 }, + ],'contract_id='.$customer->{id}); + _check_topup_log('successful topups - profile before/after: ',[ - { outcome => 'ok', profile_before_id => $profile_initial_1->{id}, profile_after_id => $profile_topup_1->{id}, request_token => $request_token_1 }, + { outcome => 'ok', profile_before_id => $profile_initial_1->{id}, profile_after_id => $profile_topup_1->{id}, request_token => $request_token_1 }, { outcome => 'ok', profile_before_id => $profile_topup_1->{id}, profile_after_id => $profile_topup_1->{id}, request_token => $request_token_2 }, - { outcome => 'ok', profile_before_id => $profile_topup_1->{id}, profile_after_id => $profile_topup_2->{id},request_token => $request_token_3 }, - ],'contract_id='.$customer->{id}); - + { outcome => 'ok', profile_before_id => $profile_topup_1->{id}, profile_after_id => $profile_topup_2->{id},request_token => $request_token_3 }, + ],'contract_id='.$customer->{id}); + +} + +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 { - + my ($label,$expected_topup_log,$filter_query) = @_; my $total_count = (scalar @$expected_topup_log); my $i = 0; my $nexturi = $uri.'/api/topuplogs/?page=1&rows=10&order_by_direction=asc&order_by=timestamp'.(defined $filter_query ? '&'.$filter_query : ''); do { $req = HTTP::Request->new('GET',$nexturi); - $res = $ua->request($req); + $res = $ua->request($req); #$res = $ua->get($nexturi); is($res->code, 200, $label."fetch topup log collection page"); my $collection = JSON::from_json($res->decoded_content); @@ -272,28 +323,28 @@ sub _check_topup_log { # TODO: I'd expect that to be an array ref in any case! ok(ref $collection->{_embedded}->{'ngcp:topuplogs'} eq "ARRAY", $label."check if 'ngcp:topuplogs' is array"); - + #my $page_items = {}; foreach my $log_record (@{ $collection->{_embedded}->{'ngcp:topuplogs'} }) { #$req = HTTP::Request->new('GET',$uri.$log_record->{_links}->{self}->{href}); - #$res = $ua->request($req); + #$res = $ua->request($req); #is($res->code, 200, $label."fetch topup log entry"); #my $got = JSON::from_json($res->decoded_content); #is_deeply($got,$log_record,$label.'check topup log entry deeply'); _compare_log_record($label,$log_record,$expected_topup_log->[$i]); $i++ } - + } while($nexturi); - + ok($i == $total_count,$label."check if all expected items are listed"); - + } sub _compare_log_record { my ($label,$got,$expected) = @_; - + foreach my $field (keys %$expected) { if ('message' eq $field) { ok($got->{$field} =~ /$expected->{$field}/,$label."check log '" . $field . "': " . $got->{$field} . " =~ /" . $expected->{$field} . '/'); @@ -305,7 +356,7 @@ sub _compare_log_record { } sub _create_customer { - + my (@further_opts) = @_; $req = HTTP::Request->new('POST', $uri.'/api/customers/'); $req->header('Content-Type' => 'application/json'); @@ -327,7 +378,7 @@ sub _create_customer { my $customer = JSON::from_json($res->decoded_content); $customer_map->{$customer->{id}} = $customer; return $customer; - + } sub _create_profile_package { @@ -362,11 +413,11 @@ sub _create_profile_package { } sub _create_voucher { - + my ($amount,$code,$customer,$package,@further_opts) = @_; my $dtf = DateTime::Format::Strptime->new( - pattern => '%F %T', - ); + pattern => '%F %T', + ); $req = HTTP::Request->new('POST', $uri.'/api/vouchers/'); $req->header('Content-Type' => 'application/json'); my $req_data = { @@ -390,7 +441,7 @@ sub _create_voucher { my $voucher = JSON::from_json($res->decoded_content); $voucher_map->{$voucher->{id}} = $voucher; return $voucher; - + } sub _create_subscriber { @@ -418,7 +469,7 @@ sub _create_subscriber { } sub _perform_topup_voucher { - + my ($subscriber,$voucher,$request_token,$error_code) = @_; $req = HTTP::Request->new('POST', $uri.'/api/topupvouchers/'); $req->header('Content-Type' => 'application/json'); @@ -431,11 +482,11 @@ sub _perform_topup_voucher { $res = $ua->request($req); $error_code //= 204; is($res->code, $error_code, ($error_code == 204 ? 'perform' : 'attempt')." perform topup with voucher " . $voucher->{code}); - + } sub _perform_topup_cash { - + my ($subscriber,$amount,$package,$request_token,$error_code) = @_; $req = HTTP::Request->new('POST', $uri.'/api/topupcash/'); $req->header('Content-Type' => 'application/json'); @@ -449,7 +500,23 @@ sub _perform_topup_cash { $res = $ua->request($req); $error_code //= 204; is($res->code, $error_code, ($error_code == 204 ? 'perform' : 'attempt')." topup with amount " . ( looks_like_number($amount) ? $amount * 100.0 . ' cents' : $amount) . ", " . ($package ? 'package id ' . $package->{id} : 'no package')); - + +} + +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 {