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 -%]
-
- |
- [% c.loc('Cash') %] |
- [% c.loc('Free time') %] |
- |
-
+
+ | |
+
[% USE format %]
[% money_format = format('%.2f') %]
-
- [% 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);