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 fa2c02ee5f)
changes/75/3175/1
Rene Krenn 10 years ago
parent cc4ee33e74
commit 3b3cab1616

@ -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));

@ -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:

@ -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);

@ -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,

@ -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:

@ -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)') },
);
}

@ -140,17 +140,19 @@
<th>[% c.loc('actual') %]</th>
<th>[% c.loc('Date') %]</th>
<th>[% c.loc('Billing Profile Name') %]</th>
<th>[% c.loc('Prepaid') %]</th>
<th>[% c.loc('Billing Network Name') %]</th>
</tr>
</thead>
<tbody>
[% FOR mapping IN billing_mappings_ordered_result.all -%]
<tr class="sw_action_row [% IF !mapping.get_column('is_future') -%]ngcp-entry-disabled[% END -%]">
<td>[% IF mapping.get_column('is_actual') -%]*[% END -%]</td>
<td><input type="checkbox" disabled="disabled" [% IF mapping.get_column('is_actual') -%]checked="checked"[% END -%]/></td>
<td>
[% mapping.start_date ? mapping.start_date : 'NULL' %] - [% mapping.end_date.defined ? mapping.end_date : 'NULL' %]
</td>
<td>[% mapping.billing_profile.name %]</td>
<td><input type="checkbox" disabled="disabled" [% IF mapping.billing_profile.prepaid -%]checked="checked"[% END -%]/></td>
<td>[% mapping.network.name %]</td>
</tr>
[% END -%]
@ -383,45 +385,92 @@
<a class="btn btn-primary btn-large" href="[% c.uri_for_action("/customer/topup_voucher", [contract.id]) %]"><i class="icon-repeat"></i> [% c.loc('Top-up Voucher') %]</a>
[% END -%]
<a class="btn btn-primary btn-large" href="[% c.uri_for_action("/customer/topup_cash", [contract.id]) %]"><i class="icon-repeat"></i> [% c.loc('Top-up Cash') %]</a>
<a class="btn btn-primary btn-large" href="[% c.uri_for_action("/customer/edit_balance", [contract.id]) %]"><i class="icon-edit"></i> [% c.loc('Set Cash Balance') %]</a>
</span>
<div class="ngcp-separator"></div>
[% END -%]
<table class="table table-bordered table-striped table-highlight table-hover">
<thead>
<tr>
<th></th>
<th>[% c.loc('Cash') %]</th>
<th>[% c.loc('Free time') %]</th>
<th class="ngcp-actions-column"></th>
</tr>
<th>
<th colspan="4"></th>
</th>
</thead>
<tbody>
[% USE format %]
[% money_format = format('%.2f') %]
<tr class="sw_action_row">
<td>[% c.loc('Current totals') %]</td>
<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>
</tr>
<tr>
<td>[% c.loc('Free time balance') %]</td>
<td>[% balance.free_time_balance %]</td>
<td class="ngcp-actions-column">
<div class="sw_actions pull-right">
[% IF (c.user.roles == 'admin' || c.user.roles == 'reseller') && !c.user.read_only -%]
<a class="btn btn-small btn-primary"
href="[% c.uri_for_action("/customer/edit_balance", [contract.id]) %]">
<i class="icon-edit"></i> [% c.loc('Edit') %]
</a>
[% END -%]
</div>
</td>
<td>[% c.loc('Free time spent') %]</td>
<td>[% balance.free_time_balance_interval %]</td>
</tr>
</tbody>
</table>
<table class="table table-bordered table-striped table-highlight table-hover">
<thead>
<th>
<th colspan="4"></th>
</th>
</thead>
<tr>
<td>[% c.loc('Interval from') %]</td>
<td>[% balance.start %]</td>
<td>[% c.loc('Interval to') %]</td>
<td>[% balance.end %]</td>
</tr>
<tr>
<td>[% c.loc('Spent this interval') %]</td>
<td>[% money_format( balance.cash_balance_interval / 100 ) %]</td>
<td>[% balance.free_time_balance_interval %]</td>
<td></td>
<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>
</tr>
<tr>
<td colspan="2">[% c.loc('Balance will be discarded, if no tup-up happens until') %]</td>
<td colspan="2">[% notopup_expiration %]</td>
</tr>
</tbody>
</table>
<table class="table table-bordered table-striped table-highlight table-hover">
<thead>
<th>
<th colspan="4"></th>
</th>
</thead>
<tbody>
[% USE format %]
[% money_format = format('%.2f') %]
<tr>
<td>[% c.loc('Actual profile package') %]</td>
<td>[% package.name %]</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>[% package.underrun_profile_threshold ? money_format( package.underrun_lock_threshold / 100 ) : '' %]</td>
<td>[% c.loc('Balance threshold when subscribers will be locked') %]</td>
<td>[% package.underrun_lock_threshold ? money_format( package.underrun_lock_threshold / 100 ) : '' %]</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

@ -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 . '&timestamp_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 . '&timestamp_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) {

@ -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);

Loading…
Cancel
Save