From 3b3cab16161175c4ab07dfce55db19810a01c250 Mon Sep 17 00:00:00 2001 From: Rene Krenn Date: Tue, 3 Nov 2015 00:22:06 +0100 Subject: [PATCH] MT#16045 notopup expiry for balance intervals output including "timely" topup periods. +for /api/balanceintervals +and for panel UI, "Contract Balance" contract details section reworked api_dump.pl was simplified accordingly. Change-Id: Ib0c4942b1db059b4fe8ee3343838f516662ca166 (cherry picked from commit fa2c02ee5fffd5aa750b9bd7044df6d0abfdcc28) --- .../Panel/Controller/API/BalanceIntervals.pm | 2 +- .../Controller/API/BalanceIntervalsItem.pm | 39 +- lib/NGCP/Panel/Controller/Customer.pm | 17 +- .../Panel/Form/Balance/BalanceIntervalAPI.pm | 35 ++ lib/NGCP/Panel/Role/API/BalanceIntervals.pm | 33 +- lib/NGCP/Panel/Utils/ProfilePackages.pm | 95 +++- share/templates/customer/details.tt | 95 +++- share/tools/api_dump.pl | 486 ++++++++---------- t/api-rest/api-balanceintervals.t | 4 +- 9 files changed, 442 insertions(+), 364 deletions(-) diff --git a/lib/NGCP/Panel/Controller/API/BalanceIntervals.pm b/lib/NGCP/Panel/Controller/API/BalanceIntervals.pm index 8cb72e0bb9..3ad2edf77e 100644 --- a/lib/NGCP/Panel/Controller/API/BalanceIntervals.pm +++ b/lib/NGCP/Panel/Controller/API/BalanceIntervals.pm @@ -125,7 +125,7 @@ sub GET :Allow { contract => $contract, now => $now); #sleep(5); - my $hal = $self->hal_from_balance($c, $balance, $form, 0); #we prefer item collection links pointing to the contract's collection instead of this root collection + my $hal = $self->hal_from_balance($c, $balance, $form, $now, 0); #we prefer item collection links pointing to the contract's collection instead of this root collection $hal->_forcearray(1); push @embedded, $hal; my $link = Data::HAL::Link->new(relation => 'ngcp:'.$self->resource_name, href => sprintf('/%s%d/%d', $c->request->path, $contract->id, $balance->id)); diff --git a/lib/NGCP/Panel/Controller/API/BalanceIntervalsItem.pm b/lib/NGCP/Panel/Controller/API/BalanceIntervalsItem.pm index 894cc4564b..f22f58b1f0 100644 --- a/lib/NGCP/Panel/Controller/API/BalanceIntervalsItem.pm +++ b/lib/NGCP/Panel/Controller/API/BalanceIntervalsItem.pm @@ -104,15 +104,16 @@ sub GET :Allow { $c->model('DB')->set_transaction_isolation('READ COMMITTED'); my $guard = $c->model('DB')->txn_scope_guard; { + my $now = NGCP::Panel::Utils::DateTime::current_local; last unless $self->valid_id($c, $id); my $contract = $self->contract_by_id($c, $id); last unless $self->resource_exists($c, contract => $contract); - my $balances = $self->balances_rs($c,$contract); + my $balances = $self->balances_rs($c,$contract,$now); (my $total_count, $balances) = $self->paginate_order_collection($c, $balances); my (@embedded, @links); my $form = $self->get_form($c); for my $balance ($balances->all) { - my $hal = $self->hal_from_balance($c, $balance, $form); + my $hal = $self->hal_from_balance($c, $balance, $form, $now); $hal->_forcearray(1); push @embedded, $hal; my $link = Data::HAL::Link->new( @@ -169,20 +170,6 @@ sub OPTIONS :Allow { return; } - - - - - - - - - - - - - - sub item_base { my ($self,$c,$id) = @_; $c->stash->{contract_id} = $id; @@ -194,32 +181,25 @@ sub item_get { $c->model('DB')->set_transaction_isolation('READ COMMITTED'); my $guard = $c->model('DB')->txn_scope_guard; { + my $now = NGCP::Panel::Utils::DateTime::current_local; my $contract_id = $c->stash->{contract_id}; last unless $self->valid_id($c, $contract_id); my $contract = $self->contract_by_id($c, $contract_id); last unless $self->resource_exists($c, contract => $contract); my $balance = undef; - #if (API_JOURNALITEMTOP_RESOURCE_NAME and $id eq API_JOURNALITEMTOP_RESOURCE_NAME) { - # $balance = $self->balance_by_id($c,$contract_id); - #} els + if ($self->valid_id($c, $id)) { - $balance = $self->balance_by_id($c,$contract,$id); + $balance = $self->balance_by_id($c,$contract,$id,$now); } else { last; } last unless $self->resource_exists($c, balanceinterval => $balance); - my $hal = $self->hal_from_balance($c,$balance); + my $form = $self->get_form($c); + my $hal = $self->hal_from_balance($c,$balance,$form,$now); $guard->commit; - #my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( - # (map { # XXX Data::HAL must be able to generate links with multiple relations - # s|rel="(http://purl.org/sipwise/ngcp-api/#rel-resellers)"|rel="item $1"|; - # s/rel=self/rel="item self"/; - # $_ - # } $hal->http_headers), - #), $hal->as_json); $c->response->headers(HTTP::Headers->new($hal->http_headers)); $c->response->body($hal->as_json); return; @@ -249,8 +229,5 @@ sub item_head { sub end : Private { my ($self, $c) = @_; - #$self->reset_fake_time($c); $self->log_response($c); } - -# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/Customer.pm b/lib/NGCP/Panel/Controller/Customer.pm index 21b111397e..6b9c1a6b5d 100644 --- a/lib/NGCP/Panel/Controller/Customer.pm +++ b/lib/NGCP/Panel/Controller/Customer.pm @@ -398,13 +398,26 @@ sub base :Chained('list_customer') :PathPart('') :CaptureArgs(1) { { name => "amount_vat", search => 1, title => $c->loc("VAT Amount") }, { name => "amount_total", search => 1, title => $c->loc("Total Amount") }, ]); - + + my ($is_timely,$timely_start,$timely_end) = NGCP::Panel::Utils::ProfilePackages::get_timely_range( + package => $contract_first->profile_package, + contract => $contract_first, + balance => $balance, + now => $now); + my $notopup_expiration = NGCP::Panel::Utils::ProfilePackages::get_notopup_expiration( + package => $contract_first->profile_package, + contract => $contract_first, + balance => $balance); $c->stash(pbx_devices => $field_devs); $c->stash(product => $product); $c->stash(balance => $balance); - $c->stash(fraud => $contract_rs->first->contract_fraud_preference); + $c->stash(package => $contract_first->profile_package); + $c->stash(timely_topup_start => $timely_start); + $c->stash(timely_topup_end => $timely_end); + $c->stash(notopup_expiration => $notopup_expiration); + $c->stash(fraud => $contract_first->contract_fraud_preference); $c->stash(template => 'customer/details.tt'); $c->stash(contract => $contract_first); $c->stash(contract_rs => $contract_rs); diff --git a/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm b/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm index 0e216ee20b..24e215c9c1 100644 --- a/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm +++ b/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm @@ -8,6 +8,15 @@ has_field 'id' => ( type => 'Hidden', ); +has_field 'is_actual' => ( + type => 'Boolean', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Is this balance interval the actual one?'] + }, +); + has_field 'start' => ( type => '+NGCP::Panel::Field::DateTime', required => 0, @@ -26,6 +35,32 @@ has_field 'stop' => ( }, ); +has_field 'timely_topup_start' => ( + type => '+NGCP::Panel::Field::DateTime', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The datetime (YYYY-MM-DD HH:mm:ss) pointing the begin of the time range when top-ups will be considered \'timely\'.'] + }, +); +has_field 'timely_topup_stop' => ( + type => '+NGCP::Panel::Field::DateTime', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The datetime (YYYY-MM-DD HH:mm:ss) pointing the end of the time range until top-ups will be considered \'timely\'.'] + }, +); + +has_field 'notopup_discard_expiry' => ( + type => '+NGCP::Panel::Field::DateTime', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The datetime (YYYY-MM-DD HH:mm:ss) pointing the deadline, when the cash balance will be discarded if no top-up was performed.'] + }, +); + has_field 'billing_profile_id' => ( type => 'PosInteger', #required => 1, diff --git a/lib/NGCP/Panel/Role/API/BalanceIntervals.pm b/lib/NGCP/Panel/Role/API/BalanceIntervals.pm index 7e3b16e5b1..a46d297c6d 100644 --- a/lib/NGCP/Panel/Role/API/BalanceIntervals.pm +++ b/lib/NGCP/Panel/Role/API/BalanceIntervals.pm @@ -61,14 +61,25 @@ sub get_form { } sub hal_from_balance { - my ($self, $c, $item, $form, $use_root_collection_link) = @_; + 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); my $profile_at_start = $bm_start->billing_mappings->first->billing_profile; + my $is_actual = NGCP::Panel::Utils::DateTime::is_infinite_future($item->end) || NGCP::Panel::Utils::DateTime::set_local_tz($item->end) >= $now; + my ($is_timely,$timely_start,$timely_end) = NGCP::Panel::Utils::ProfilePackages::get_timely_range( + package => $contract->profile_package, + contract => $contract, + balance => $item, + now => $now); + my $notopup_expiration = undef; + $notopup_expiration = NGCP::Panel::Utils::ProfilePackages::get_notopup_expiration( + package => $contract->profile_package, + contract => $contract, + balance => $item) if $is_actual; #my $invoice = $item->invoice; - + my %resource = $item->get_inflated_columns; $resource{cash_balance} /= 100.0; $resource{cash_debit} = (delete $resource{cash_balance_interval}) / 100.0; @@ -82,6 +93,13 @@ sub hal_from_balance { $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 = Data::HAL->new( links => [ @@ -126,22 +144,24 @@ sub contract_by_id { } sub balances_rs { - my ($self, $c,$contract) = @_; + my ($self, $c, $contract, $now) = @_; + $now //= NGCP::Panel::Utils::DateTime::current_local; NGCP::Panel::Utils::ProfilePackages::catchup_contract_balances(c => $c, contract => $contract, - now => NGCP::Panel::Utils::DateTime::current_local); + now => $now); return $self->apply_query_params($c,$self->can('query_params') ? $self->query_params : {},$contract->contract_balances); } sub balance_by_id { - my ($self, $c, $contract, $id) = @_; + my ($self, $c, $contract, $id, $now) = @_; + $now //= NGCP::Panel::Utils::DateTime::current_local; my $balance = NGCP::Panel::Utils::ProfilePackages::catchup_contract_balances(c => $c, contract => $contract, - now => NGCP::Panel::Utils::DateTime::current_local); + now => $now); if (defined $id) { $balance = $contract->contract_balances->find($id); @@ -151,4 +171,3 @@ sub balance_by_id { } 1; -# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Utils/ProfilePackages.pm b/lib/NGCP/Panel/Utils/ProfilePackages.pm index b1e8a27852..c69dd5172e 100644 --- a/lib/NGCP/Panel/Utils/ProfilePackages.pm +++ b/lib/NGCP/Panel/Utils/ProfilePackages.pm @@ -138,7 +138,7 @@ sub resize_actual_contract_balance { @$resized_balance_values, }); $actual_balance->discard_changes(); - $c->log->debug('contract ' . $contract->id . ' contract_balance row resized: ' . Dumper({ $actual_balance->get_inflated_columns })); + $c->log->debug('contract ' . $contract->id . ' contract_balance row resized: ' . _dump_contract_balance($actual_balance)); if ($create_next_balance) { $actual_balance = catchup_contract_balances(c => $c, contract => $contract, @@ -304,7 +304,7 @@ PREPARE_BALANCE_CATCHUP: }); $last_balance->discard_changes(); - $c->log->debug('contract ' . $contract->id . ' contract_balance row created: ' . Dumper({ $last_balance->get_inflated_columns })); + $c->log->debug('contract ' . $contract->id . ' contract_balance row created: ' . _dump_contract_balance($last_balance)); } # in case of "topup" or "topup_interval" start modes, the current interval end can be @@ -422,23 +422,12 @@ sub topup_contract_balance { $contract->discard_changes(); } - my ($is_timely,$timely_duration_unit,$timely_duration_value) = (0,undef,undef); - if ($old_package - && ($timely_duration_unit = $old_package->timely_duration_unit) - && ($timely_duration_value = $old_package->timely_duration_value)) { - my $timely_end; - #if (_TOPUP_START_MODE ne $old_package->balance_interval_start_mode) { - if (!NGCP::Panel::Utils::DateTime::is_infinite_future($balance->end)) { - $timely_end = NGCP::Panel::Utils::DateTime::set_local_tz($balance->end); - } else { - $timely_end = _add_interval(NGCP::Panel::Utils::DateTime::set_local_tz($balance->start),$old_package->balance_interval_unit,$old_package->balance_interval_value, - _START_MODE_PRESERVE_EOM->{$old_package->balance_interval_start_mode} ? NGCP::Panel::Utils::DateTime::set_local_tz($contract->create_timestamp // $contract->modify_timestamp) : undef)->subtract(seconds => 1); - } - my $timely_start = _add_interval($timely_end,$timely_duration_unit,-1 * $timely_duration_value)->add(seconds => 1); - $timely_start = NGCP::Panel::Utils::DateTime::set_local_tz($balance->start) if $timely_start < NGCP::Panel::Utils::DateTime::set_local_tz($balance->start); - - $is_timely = ($now >= $timely_start && $now <= $timely_end ? 1 : 0); - } + my ($is_timely,$timely_start,$timely_end) = get_timely_range(package => $old_package, + balance => $balance, + contract => $contract, + now => $now); + + $c->log->debug('timely topup (' . NGCP::Panel::Utils::DateTime::to_string($timely_start) . ' - ' . NGCP::Panel::Utils::DateTime::to_string($timely_end) . ')') if $is_timely; $balance->update({ topup_count => $balance->topup_count + 1, timely_topup_count => $balance->timely_topup_count + $is_timely}); @@ -838,6 +827,56 @@ sub _get_notopup_expiration { return $notopup_expiration; } +sub get_notopup_expiration { + my %params = @_; + my ($package,$balance,$contract) = @params{qw/package balance contract/}; + $contract //= $balance->contract; + my $notopup_expiration = undef; + if ($package) { + my $start_mode = $package->balance_interval_start_mode; + my $interval_unit = $package->balance_interval_unit; + my $notopup_discard_intervals = $package->notopup_discard_intervals; + if (NGCP::Panel::Utils::DateTime::is_infinite_future($balance->end)) { + $notopup_expiration = _get_notopup_expiration(contract => $contract, + notopup_discard_intervals => $notopup_discard_intervals, + interval_unit => $interval_unit, + last_balance => $balance, + start_mode => $start_mode); + } else { + $notopup_expiration = _get_notopup_expiration(contract => $contract, + notopup_discard_intervals => $notopup_discard_intervals, + interval_unit => $interval_unit, + start_mode => $start_mode); + } + } + return $notopup_expiration; +} + +sub get_timely_range { + my %params = @_; + my ($package,$balance,$contract,$now) = @params{qw/package balance contract now/}; + $contract //= $balance->contract; + $now //= NGCP::Panel::Utils::DateTime::current_local; + my ($is_timely,$timely_start,$timely_end,$timely_duration_unit,$timely_duration_value) = (0,undef,undef,undef,undef); + if ($package + && ($timely_duration_unit = $package->timely_duration_unit) + && ($timely_duration_value = $package->timely_duration_value)) { + + #if (_TOPUP_START_MODE ne $old_package->balance_interval_start_mode) { + if (!NGCP::Panel::Utils::DateTime::is_infinite_future($balance->end)) { + $timely_end = NGCP::Panel::Utils::DateTime::set_local_tz($balance->end); + } else { + $timely_end = _add_interval(NGCP::Panel::Utils::DateTime::set_local_tz($balance->start),$package->balance_interval_unit,$package->balance_interval_value, + _START_MODE_PRESERVE_EOM->{$package->balance_interval_start_mode} ? NGCP::Panel::Utils::DateTime::set_local_tz($contract->create_timestamp // $contract->modify_timestamp) : undef)->subtract(seconds => 1); + } + $timely_start = _add_interval($timely_end,$timely_duration_unit,-1 * $timely_duration_value)->add(seconds => 1); + $timely_start = NGCP::Panel::Utils::DateTime::set_local_tz($balance->start) if $timely_start < NGCP::Panel::Utils::DateTime::set_local_tz($balance->start); + + $is_timely = ($now >= $timely_start && $now <= $timely_end ? 1 : 0); + } + return ($is_timely,$timely_start,$timely_end); +} + sub get_actual_billing_mapping { my %params = @_; my ($c,$schema,$contract,$now) = @params{qw/c schema contract now/}; @@ -1017,6 +1056,20 @@ sub lock_contracts { return []; } +sub _dump_contract_balance { + my $balance = shift; + my $row = { $balance->get_inflated_columns }; + my %dump = (); + foreach my $col (keys %$row) { + if ('DateTime' eq ref $row->{$col}) { + $dump{$col} = NGCP::Panel::Utils::DateTime::to_string($row->{$col}); + } else { + $dump{$col} = $row->{$col}; + } + } + return Dumper(\%dump); +} + sub check_balance_interval { my (%params) = @_; my ($c,$resource,$err_code) = @params{qw/c resource err_code/}; @@ -1302,8 +1355,8 @@ sub get_balanceinterval_datatable_cols { { 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 (Profiles)') }, - { name => "underrun_lock", search => 0, title => $c->loc('Underrun (Lock)') }, + { name => "underrun_profiles", search => 0, title => $c->loc('Underrun detected (Profiles)') }, + { name => "underrun_lock", search => 0, title => $c->loc('Underrun detected (Lock)') }, ); } diff --git a/share/templates/customer/details.tt b/share/templates/customer/details.tt index 172e8ca44c..f8a6b803b3 100644 --- a/share/templates/customer/details.tt +++ b/share/templates/customer/details.tt @@ -140,17 +140,19 @@ [% c.loc('actual') %] [% c.loc('Date') %] [% c.loc('Billing Profile Name') %] + [% c.loc('Prepaid') %] [% c.loc('Billing Network Name') %] [% FOR mapping IN billing_mappings_ordered_result.all -%] - [% IF mapping.get_column('is_actual') -%]*[% END -%] + [% mapping.start_date ? mapping.start_date : 'NULL' %] - [% mapping.end_date.defined ? mapping.end_date : 'NULL' %] [% mapping.billing_profile.name %] + [% mapping.network.name %] [% END -%] @@ -383,45 +385,92 @@ [% c.loc('Top-up Voucher') %] [% END -%] [% c.loc('Top-up Cash') %] + [% c.loc('Set Cash Balance') %]
[% END -%] - - - - - - + + [% USE format %] [% money_format = format('%.2f') %] - - + + + + + + + + + - + + + + +
[% c.loc('Cash') %][% c.loc('Free time') %]
+
[% c.loc('Current totals') %]
[% 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 %] -
- [% IF (c.user.roles == 'admin' || c.user.roles == 'reseller') && !c.user.read_only -%] - - [% c.loc('Edit') %] - - [% END -%] -
-
[% c.loc('Free time spent') %][% balance.free_time_balance_interval %]
+ + + + + + + + + + + + + - - - - + + + + + + + + + +
+
[% c.loc('Interval from') %][% balance.start %][% c.loc('Interval to') %][% balance.end %]
[% c.loc('Spent this interval') %][% money_format( balance.cash_balance_interval / 100 ) %][% balance.free_time_balance_interval %][% 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 %]
+ + + + + + + + [% USE format %] + [% money_format = format('%.2f') %] + + + + + + + + + + + + + + + +
+
[% c.loc('Actual profile package') %][% package.name %][% c.loc('Actual billing profile') %][% mapping.billing_profile.name %]
[% c.loc('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') %][% package.underrun_lock_threshold ? money_format( package.underrun_lock_threshold / 100 ) : '' %]
+ diff --git a/share/tools/api_dump.pl b/share/tools/api_dump.pl index f451b65a7b..455b8efef6 100755 --- a/share/tools/api_dump.pl +++ b/share/tools/api_dump.pl @@ -7,228 +7,231 @@ use DateTime::Format::Strptime qw(); use DateTime::Format::ISO8601 qw(); use Getopt::Long; -my $host = '127.0.0.1'; -my $port = 443; +#constants; +use constant CHMOD_UMASK => '0777'; + +use constant BALANCEINTERVALS_MODE => 'balanceintervals'; +use constant TOPUPLOG_MODE => 'topuplog'; + +use constant THIS_WEEK_PERIOD => 'this_week'; +use constant TODAY_PERIOD => 'today'; +use constant THIS_MONTH_PERIOD => 'this_month'; +use constant LAST_WEEK_PERIOD => 'last_week'; +use constant LAST_MONTH_PERIOD => 'last_month'; + +use constant MODE_STRINGS => (BALANCEINTERVALS_MODE,TOPUPLOG_MODE); +use constant PERIOD_STRINGS => (THIS_WEEK_PERIOD,TODAY_PERIOD,THIS_MONTH_PERIOD,LAST_WEEK_PERIOD,LAST_MONTH_PERIOD); + +#default option values and parameters: +my $host = '127.0.0.1'; #db01a +my $port = 1443; my $user = 'administrator'; my $pass = 'administrator'; -my $output_dir = ''; +my $output_dir = '/tmp'; + my $output_filename; my $verbose = 0; -my $period = ''; - +my $period = ''; #'topuplog' mode only + my $print_colnames = 1; my $linebreak = "\n"; my $col_separator = ";"; - + my %row_value_escapes = ( quotemeta($linebreak) => ' ', - quotemeta($col_separator) => ' '); + quotemeta($col_separator) => ' '); -GetOptions ("host=s" => \$host, - "port=i" => \$port, - "file=s" => \$output_filename, - "dir=s" => \$output_dir, - "user=s" => \$user, - "pass=s" => \$pass, - "period=s" => \$period, - 'verbose+' => \$verbose) or fatal("Error in command line arguments"); +#runtime globals: +my ($mode,$ua,$uri) = init(); -use constant BALANCEINTERVALS_MODE => 'balanceintervals'; -use constant TOPUPLOG_MODE => 'topuplog'; +#run the program: +exit(main()); -use constant THIS_WEEK_PERIOD => 'this_week'; -use constant TODAY_PERIOD => 'today'; -use constant THIS_MONTH_PERIOD => 'this_month'; -use constant LAST_WEEK_PERIOD => 'last_week'; -use constant LAST_MONTH_PERIOD => 'last_month'; +#subs; +sub init { + + umask oct(CHMOD_UMASK); + + GetOptions ("host=s" => \$host, + "port=i" => \$port, + "file=s" => \$output_filename, + "dir=s" => \$output_dir, + "user=s" => \$user, + "pass=s" => \$pass, + "period=s" => \$period, + 'verbose+' => \$verbose) or fatal("Error in command line arguments"); -my @mode_strings = (BALANCEINTERVALS_MODE,TOPUPLOG_MODE); -my @period_strings = (THIS_WEEK_PERIOD,TODAY_PERIOD,THIS_MONTH_PERIOD,LAST_WEEK_PERIOD,LAST_MONTH_PERIOD); -my $mode = shift @ARGV; + my $mode = shift @ARGV; -fatal('No mode argument specified, one of [' . join(', ',@mode_strings). "] required") unless $mode; + fatal('No mode argument specified, one of [' . join(', ',(MODE_STRINGS)). "] required") unless $mode; -$output_dir .= '/' if $output_dir && '/' ne substr($output_dir,-1); -$output_filename = "api_dump_" . $mode . "_results_" . datetime_to_string(current_local()) . ".txt" unless $output_filename; -$output_filename = $output_dir . $output_filename; + $output_dir .= '/' if $output_dir && '/' ne substr($output_dir,-1); + makedir($output_dir) if $output_dir && not -e $output_dir; + $output_filename = "api_dump_" . $mode . "_results_" . datetime_to_string(current_local()) . ".txt" unless $output_filename; + $output_filename = $output_dir . $output_filename; -my $uri = 'https://'.$host.':'.$port; -my ($netloc) = ($uri =~ m!^https?://(.*)/?.*$!); + my $uri = 'https://'.$host.':'.$port; + my ($netloc) = ($uri =~ m!^https?://(.*)/?.*$!); -my ($ua, $req, $res); -$ua = LWP::UserAgent->new; -$ua->ssl_opts( + my $ua = LWP::UserAgent->new; + $ua->ssl_opts( verify_hostname => 0, SSL_verify_mode => 0, ); -$ua->credentials($netloc, "api_admin_http", $user, $pass); - -if (BALANCEINTERVALS_MODE eq $mode) { + $ua->credentials($netloc, "api_admin_http", $user, $pass); - my @cols = ( - 'subscriber_id', - 'subscriber_status', - 'primary_number', - 'contract_id', - 'contract_status', - #'has_actual_balance_interval', - 'interval_start', - 'interval_stop', - 'cash_balance', - 'notopup_discard', - ); + return ($mode,$ua,$uri); - my $rowcount = 0; - my $fh = prepare_file($mode,$output_filename,\@cols); +} + +sub main { - process_collection($uri.'/api/subscribers',50,'ngcp:subscribers',sub { - my ($subscriber,$total_count,$customer_map,$package_map,$last_intervals_map,$first_intervals_map,$topup_intervals_map) = @_; - my $primary_number = get_primary_number($subscriber); - log_info("processing subscriber ID $subscriber->{id}: " . $primary_number); - my ($customer,$last_interval,$package,$first_interval,$topup_interval) = ({},{},{},undef,undef); + if (BALANCEINTERVALS_MODE eq $mode) { - $customer = get_item($subscriber->{_links},'ngcp:customers',$customer_map,$subscriber->{customer_id}); - $package = get_item($subscriber->{_links},'ngcp:profilepackages',$package_map,$customer->{profile_package_id}); + my @cols = ( + 'subscriber_id', + 'subscriber_status', + 'primary_number', + 'contract_id', + 'contract_status', + #'has_actual_balance_interval', + 'interval_start', + 'interval_stop', + 'cash_balance', + 'notopup_discard_expiry', + ); - if (exists $last_intervals_map->{$customer->{id}}) { - $last_interval = $last_intervals_map->{$customer->{id}}; - $topup_interval = $topup_intervals_map->{$customer->{id}}; - $first_interval = $first_intervals_map->{$customer->{id}}; - } else { - my $interval = undef; - my $interval_w_topup = undef; - my $initial_interval = undef; - process_collection($uri.'/api/balanceintervals/'.$customer->{id}.'?order_by=end&order_by_direction=desc', - (defined $package->{notopup_discard_intervals} ? 5 : 1),'ngcp:balanceintervals',sub { - my $balance_interval = shift; - log_info("processing balance interval: contract ID $subscriber->{customer_id}, " . $balance_interval->{start} . ' - ' . $balance_interval->{stop} ); - if (defined $package->{notopup_discard_intervals}) { - $interval = $balance_interval if !defined $interval; - if ($balance_interval->{topup_count} > 0) { - $interval_w_topup = $balance_interval; - return 0; - } else { - $initial_interval = $balance_interval; - } - return 1; - } else { - $interval = $balance_interval; + my $rowcount = 0; + my $fh = prepare_file($mode,$output_filename,\@cols); + + process_collection($uri.'/api/subscribers',50,'ngcp:subscribers',sub { + my ($subscriber,$total_count,$customer_map,$package_map,$intervals_map) = @_; + my $primary_number = get_primary_number($subscriber); + log_info("processing subscriber ID $subscriber->{id}: " . $primary_number); + my ($customer,$interval,$package) = ({},{},{}); + + $customer = get_item($subscriber->{_links},'ngcp:customers',$customer_map,$subscriber->{customer_id}); + $package = get_item($subscriber->{_links},'ngcp:profilepackages',$package_map,$customer->{profile_package_id}); + + if (exists $intervals_map->{$customer->{id}}) { + $interval = $intervals_map->{$customer->{id}}; + } else { + process_collection($uri.'/api/balanceintervals/'.$customer->{id}.'?order_by=end&order_by_direction=desc', + 1,'ngcp:balanceintervals',sub { + my $balance_interval = shift; + log_info("processing balance interval: contract ID $subscriber->{customer_id}, " . $balance_interval->{start} . ' - ' . $balance_interval->{stop} ); + $interval = $balance_interval if defined $balance_interval; return 0; - } - }); - $last_interval = $interval if defined $interval; - $last_intervals_map->{$customer->{id}} = $interval; - $topup_interval = $interval_w_topup if defined $interval_w_topup; - $topup_intervals_map->{$customer->{id}} = $topup_interval; - $first_interval = $initial_interval if !defined $interval_w_topup; - $first_intervals_map->{$customer->{id}} = $first_interval; - } + }); + $intervals_map->{$customer->{id}} = $interval; + } + + my %row = ( + 'subscriber_id' => $subscriber->{id}, + 'subscriber_status' => $subscriber->{status}, + 'primary_number' => $primary_number, + 'contract_id' => $customer->{id}, + 'contract_status' => $customer->{status}, + #'has_actual_balance_interval' => 1, + 'interval_start' => $interval->{start}, + 'interval_stop' => $interval->{stop}, + 'cash_balance' => $interval->{cash_balance}, + 'notopup_discard_expiry' => $interval->{notopup_discard_expiry}, + ); - my %row = ( - 'subscriber_id' => $subscriber->{id}, - 'subscriber_status' => $subscriber->{status}, - 'primary_number' => $primary_number, - 'contract_id' => $customer->{id}, - 'contract_status' => $customer->{status}, - #'has_actual_balance_interval' => 1, - 'interval_start' => $last_interval->{start}, - 'interval_stop' => $last_interval->{stop}, - 'cash_balance' => $last_interval->{cash_balance}, - 'notopup_discard' => get_notopup_expiration($package,$last_interval,$topup_interval,$first_interval), + $rowcount++; + + log_row($rowcount,$total_count,\%row,\@cols); + print $fh join($col_separator,map { escape_row_value($_); } @row{@cols}) . $linebreak; + + return 1; + },3); + + close_file($fh,$output_filename,$rowcount); + + } elsif (TOPUPLOG_MODE eq $mode) { + + my @cols = ( + 'username', + 'timestamp', + 'request_token', + 'subscriber_id', + 'primary_number', + 'contract_id', + 'outcome', + 'message', + 'type', + 'voucher_id', + 'voucher_code', + 'amount', + 'cash_balance_before', + 'cash_balance_after', + 'lock_level_before', + 'lock_level_after', + 'package_before', + 'package_after', + 'profile_before', + 'profile_after', ); - - $rowcount++; - log_row($rowcount,$total_count,\%row,\@cols); - print $fh join($col_separator,map { escape_row_value($_); } @row{@cols}) . $linebreak; + my $rowcount = 0; + my $fh = prepare_file($mode,$output_filename,\@cols); - return 1; - },5); - - close $fh; - log_info("$rowcount rows written to file '$output_filename'"); -} elsif (TOPUPLOG_MODE eq $mode) { - - my @cols = ( - 'username', - 'timestamp', - 'request_token', - 'subscriber_id', - 'primary_number', - 'contract_id', - 'outcome', - 'message', - 'type', - 'voucher_id', - 'voucher_code', - 'amount', - 'cash_balance_before', - 'cash_balance_after', - 'lock_level_before', - 'lock_level_after', - 'package_before', - 'package_after', - 'profile_before', - 'profile_after', - ); - - my $rowcount = 0; - my $fh = prepare_file($mode,$output_filename,\@cols); + my ($from,$to) = get_period_dts(); + my $query_string = (defined $from && defined $to ? '?timestamp_from=' . $from . '×tamp_to=' . $to : ''); + + process_collection($uri.'/api/topuplogs'.$query_string,100,'ngcp:topuplogs',sub { + my ($topuplog,$total_count,$subscriber_map,$voucher_map,$package_map,$profile_map) = @_; + log_info("processing topup log entry ID $topuplog->{id}"); + + my ($subscriber,$voucher,$package_before,$package_after,$profile_before,$profile_after) = ({},{},{},{},{},{}); + + $subscriber = get_item($topuplog->{_links},'ngcp:subscribers',$subscriber_map,$topuplog->{subscriber_id}); + $voucher = get_item($topuplog->{_links},'ngcp:vouchers',$voucher_map,$topuplog->{voucher_id}); + $package_before = get_item($topuplog->{_links},'ngcp:profilepackages',$package_map,$topuplog->{package_before_id}); + $package_after = get_item($topuplog->{_links},'ngcp:profilepackages',$package_map,$topuplog->{package_after_id}); + $profile_before = get_item($topuplog->{_links},'ngcp:billingprofiles',$profile_map,$topuplog->{profile_before_id}); + $profile_after = get_item($topuplog->{_links},'ngcp:billingprofiles',$profile_map,$topuplog->{profile_after_id}); - my ($from,$to) = get_period_dts(); - my $query_string = (defined $from && defined $to ? '?timestamp_from=' . $from . '×tamp_to=' . $to : ''); + my %row = ( + 'username' => $topuplog->{username}, + 'timestamp' => $topuplog->{timestamp}, + 'request_token' => $topuplog->{request_token}, + 'subscriber_id' => $topuplog->{subscriber_id}, + 'primary_number' => get_primary_number($subscriber), + 'contract_id' => $topuplog->{contract_id}, + 'outcome' => $topuplog->{outcome}, + 'message' => $topuplog->{message}, + 'type' => $topuplog->{type}, + 'voucher_id' => $topuplog->{voucher_id}, + 'voucher_code' => $voucher->{code}, + 'amount' => $topuplog->{amount}, + 'cash_balance_after' => $topuplog->{cash_balance_after}, + 'cash_balance_before' => $topuplog->{cash_balance_before}, + 'lock_level_after' => $topuplog->{lock_level_after}, + 'lock_level_before' => $topuplog->{lock_level_before}, + 'package_after' => $package_after->{name}, + 'package_before' => $package_before->{name}, + 'profile_after' => $profile_after->{name}, + 'profile_before' => $profile_before->{name}, + ); - process_collection($uri.'/api/topuplogs'.$query_string,100,'ngcp:topuplogs',sub { - my ($topuplog,$total_count,$subscriber_map,$voucher_map,$package_map,$profile_map) = @_; - log_info("processing topup log entry ID $topuplog->{id}"); - - my ($subscriber,$voucher,$package_before,$package_after,$profile_before,$profile_after) = ({},{},{},{},{},{}); - - $subscriber = get_item($topuplog->{_links},'ngcp:subscribers',$subscriber_map,$topuplog->{subscriber_id}); - $voucher = get_item($topuplog->{_links},'ngcp:vouchers',$voucher_map,$topuplog->{voucher_id}); - $package_before = get_item($topuplog->{_links},'ngcp:profilepackages',$package_map,$topuplog->{package_before_id}); - $package_after = get_item($topuplog->{_links},'ngcp:profilepackages',$package_map,$topuplog->{package_after_id}); - $profile_before = get_item($topuplog->{_links},'ngcp:billingprofiles',$profile_map,$topuplog->{profile_before_id}); - $profile_after = get_item($topuplog->{_links},'ngcp:billingprofiles',$profile_map,$topuplog->{profile_after_id}); - - my %row = ( - 'username' => $topuplog->{username}, - 'timestamp' => $topuplog->{timestamp}, - 'request_token' => $topuplog->{request_token}, - 'subscriber_id' => $topuplog->{subscriber_id}, - 'primary_number' => get_primary_number($subscriber), - 'contract_id' => $topuplog->{contract_id}, - 'outcome' => $topuplog->{outcome}, - 'message' => $topuplog->{message}, - 'type' => $topuplog->{type}, - 'voucher_id' => $topuplog->{voucher_id}, - 'voucher_code' => $voucher->{code}, - 'amount' => $topuplog->{amount}, - 'cash_balance_after' => $topuplog->{cash_balance_after}, - 'cash_balance_before' => $topuplog->{cash_balance_before}, - 'lock_level_after' => $topuplog->{lock_level_after}, - 'lock_level_before' => $topuplog->{lock_level_before}, - 'package_after' => $package_after->{name}, - 'package_before' => $package_before->{name}, - 'profile_after' => $profile_after->{name}, - 'profile_before' => $profile_before->{name}, - ); - - $rowcount++; + $rowcount++; + + log_row($rowcount,$total_count,\%row,\@cols); + print $fh join($col_separator,map { escape_row_value($_); } @row{@cols}) . $linebreak; + + return 1; + },4); - log_row($rowcount,$total_count,\%row,\@cols); - print $fh join($col_separator,map { escape_row_value($_); } @row{@cols}) . $linebreak; + close_file($fh,$output_filename,$rowcount); - return 1; - },4); + } else { + fatal("Mode argument '$mode' not implemented"); + } - close $fh; - log_info("$rowcount rows written to file '$output_filename'"); -} else { - fatal("Unknown mode argument '$mode' specified, valid modes are [" . join(', ',@mode_strings). "]") if $mode; + return 0; } -exit; - sub process_collection { my ($url,$page_size,$item_rel,$process_item,$num_of_helper_maps) = @_; my $nexturi = URI->new($url); @@ -283,75 +286,24 @@ sub get_item { sub get_request { my $url = shift; - $req = HTTP::Request->new('GET',$url); + my $req = HTTP::Request->new('GET',$url); log_debug("GET $url"); - $res = $ua->request($req); + my $res = $ua->request($req); my $result; eval { $result = JSON::from_json($res->decoded_content); }; if ($@) { - fatal("Error requesting api: ".$res->code); + fatal("Error requesting api: " . $res->code . ' ' . $res->message); } if ($res->code != 404) { - fatal("Error requesting api: ".$result->{message}) if $res->code != 200; + fatal("Error requesting api: " . $res->code . ' ' . $result->{message}) if $res->code != 200; } else { $result = {}; } return $result; } -sub get_notopup_expiration { - - my ($package,$last_interval,$topup_interval,$first_interval) = @_; - - my $notopup_expiration = undef; - - if ($package && defined $package->{notopup_discard_intervals}) { - my $start = undef; - my $notopup_discard_intervals = $package->{notopup_discard_intervals}; - if (is_infinite_future(datetime_from_string($last_interval->{stop}))) { - $start = datetime_from_string($last_interval->{start}); - } elsif (defined $topup_interval) { - $start = datetime_from_string($topup_interval->{start}); - $notopup_discard_intervals += 1; - } elsif (defined $first_interval) { - $start = datetime_from_string($first_interval->{start}); - $notopup_discard_intervals += 1; - } - if (defined $start) { - $notopup_expiration = add_interval($start, - $package->{balance_interval_unit}, - $notopup_discard_intervals); - $notopup_expiration = datetime_to_string($notopup_expiration); - } - } - - return $notopup_expiration; -} - -sub get_timely_start_end { - - my ($package,$interval) = @_; - - my ($timely_start,$timely_end) = (undef,undef); - - if ($package && defined $package->{timely_duration_value}) { - my $start = datetime_from_string($interval->{start}); - $timely_end = add_interval($start, - $package->{balance_interval_unit}, - $package->{balance_interval_value}); - - $timely_start = add_interval($timely_end, - $package->{timely_duration_unit}, - -1 * $package->{timely_duration_value},); - $timely_end = datetime_to_string($timely_end); - $timely_start = datetime_to_string($timely_start); - } - - return ($timely_start,$timely_end); -} - sub get_primary_number { my $subscriber = shift; return ($subscriber->{primary_number} ? $subscriber->{primary_number}->{cc} . ' ' . $subscriber->{primary_number}->{ac} . ' ' . $subscriber->{primary_number}->{sn} : $subscriber->{username} . ($subscriber->{domain} ? '@' . $subscriber->{domain} : '')); @@ -379,38 +331,15 @@ sub get_period_dts { } elsif (LAST_MONTH_PERIOD eq $period) { $from = $now->truncate(to => 'month')->subtract(seconds => 1)->truncate(to => 'month'); $to = $from->clone->add('months' => 1)->subtract(seconds => 1); - $label = 'last week'; + $label = 'last month'; } else { - fatal("Unknown period '$period' specified, valid periods are [" . join(', ',@period_strings). "]") if $period; + fatal("Unknown period '$period' specified, valid periods are [" . join(', ',(PERIOD_STRINGS)). "]") if $period; return ($from,$to); } log_info($label .': ' . datetime_to_string($from) . ' to ' . datetime_to_string($to)); return ($from,$to); } -sub add_interval { - my ($from,$interval_unit,$interval_value,$align_eom_dt) = @_; - if ('day' eq $interval_unit) { - return $from->clone->add(days => $interval_value); - } elsif ('hour' eq $interval_unit) { - return $from->clone->add(hours => $interval_value); - } elsif ('week' eq $interval_unit) { - return $from->clone->add(weeks => $interval_value); - } elsif ('month' eq $interval_unit) { - my $to = $from->clone->add(months => $interval_value, end_of_month => 'preserve'); - #DateTime's "preserve" mode would get from 30.Jan to 30.Mar, when adding 2 months - #When adding 1 month two times, we get 28.Mar or 29.Mar, so we adjust: - if (defined $align_eom_dt - && $to->day > $align_eom_dt->day - && $from->day == last_day_of_month($from)) { - my $delta = last_day_of_month($align_eom_dt) - $align_eom_dt->day; - $to->set(day => last_day_of_month($to) - $delta); - } - return $to; - } - return undef; -} - sub datetime_from_string { my $s = shift; $s =~ s/^(\d{4}\-\d{2}\-\d{2})\s+(\d.+)$/$1T$2/; @@ -427,17 +356,6 @@ sub datetime_to_string { return $dtf->format_datetime($dt); } -sub last_day_of_month { - my $dt = shift; - return DateTime->last_day_of_month(year => $dt->year, month => $dt->month, - time_zone => DateTime::TimeZone->new(name => 'local'))->day; -} - -sub is_infinite_future { - my $dt = shift; - return $dt->year >= 9999; -} - sub current_local { return DateTime->now( time_zone => DateTime::TimeZone->new(name => 'local') @@ -456,6 +374,20 @@ sub prepare_file { return $fh; } +sub close_file { + my ($fh,$output_filename,$rowcount) = @_; + close $fh; + chmod(oct(CHMOD_UMASK),$output_filename); + log_info("$rowcount rows written to file '$output_filename'"); +} + +sub makedir { + my ($dirpath) = @_; + mkdir $dirpath; + chmod oct(CHMOD_UMASK),$dirpath; + log_info("directory '$dirpath' created"); +} + sub escape_row_value { my $value = shift; foreach my $escape (keys %row_value_escapes) { diff --git a/t/api-rest/api-balanceintervals.t b/t/api-rest/api-balanceintervals.t index 0d49269127..377efe301b 100644 --- a/t/api-rest/api-balanceintervals.t +++ b/t/api-rest/api-balanceintervals.t @@ -147,7 +147,7 @@ my $gantt_events; if (_get_allow_fake_client_time()) { # && $enable_profile_packages) { - goto SKIP; + #goto SKIP; #goto THREADED; if ('Europe/Vienna' eq NGCP::Panel::Utils::DateTime::current_local()->time_zone->name) { my $package = _create_profile_package('create','hour',1); @@ -237,7 +237,7 @@ if (_get_allow_fake_client_time()) { # && $enable_profile_packages) { _set_time(); } - SKIP: + #SKIP: { my $profile_initial = _create_billing_profile('UNDERRUN1_INITIAL',prepaid => 0); my $profile_topup = _create_billing_profile('UNDERRUN1_TOPUP',prepaid => 0);