MT#13903 topupvoucher and topupcash

+applying profile package and billing mappings
+testcase in api-balanceintervals.t

+caveats: to meet melita's user story, an additional 'topup_interval' interval start mode will be required. the currently implemented 'topup' start mode restarts intervals upon every topup and therefore does not provide constantinterval lengths.

Change-Id: I0a4898783c023749994e94e6909833a42debe259
changes/61/2261/1
Rene Krenn 10 years ago
parent 0cc70a0006
commit bd9f67040d

@ -8,6 +8,7 @@ use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use MooseX::ClassAttribute qw(class_has);
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::ProfilePackages;
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
BEGIN { extends 'Catalyst::Controller::ActionRole'; }
@ -80,6 +81,7 @@ sub POST :Allow {
return;
}
$c->model('DB')->set_transaction_isolation('READ COMMITTED');
my $guard = $c->model('DB')->txn_scope_guard;
{
my $resource = $self->get_valid_post_data(
@ -97,17 +99,41 @@ sub POST :Allow {
# the validation, so exclude them here
exceptions => [qw/package_id subscriber_id/],
);
my $reseller_id;
if($c->user->roles eq "admin") {
} elsif($c->user->roles eq "reseller") {
$resource->{reseller_id} = $c->user->reseller_id;
$reseller_id = $c->user->reseller_id;
}
# subscriber_id, package_id, amount
my $now = NGCP::Panel::Utils::DateTime::current_local;
my $subscriber = $c->model('DB')->resultset('voip_subscribers')->find($resource->{subscriber_id});
unless($subscriber) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Unknown subscriber_id.');
last;
}
my $customer = $subscriber->contract;
unless($customer->status eq 'active') {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Customer contract is not active.');
last;
}
unless($customer->contact->reseller) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Contract is not a customer contract.');
last;
}
# if reseller, check if subscriber_id belongs to the calling reseller
if($reseller_id && $reseller_id != $customer->contact->reseller_id) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Subscriber customer contract belongs to another reseller.');
last;
}
try {
# update contract balance, update customer package_id, billing profile mappings etc.
my $balance = NGCP::Panel::Utils::ProfilePackages::topup_contract_balance(c => $c,
contract => $customer,
#old_package => $customer->profile_package,
amount => $resource->{amount},
now => $now,
);
} catch($e) {
$c->log->error("failed to create cash topup: $e"); # TODO: user, message, trace, ...
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create cash topup.");

@ -7,7 +7,9 @@ use Data::HAL::Link qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use MooseX::ClassAttribute qw(class_has);
use NGCP::Panel::Utils::Voucher;
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::ProfilePackages;
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
BEGIN { extends 'Catalyst::Controller::ActionRole'; }
@ -16,9 +18,6 @@ require Catalyst::ActionRole::CheckTrailingSlash;
require Catalyst::ActionRole::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
use NGCP::Panel::Utils::Voucher;
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Form::Topup::VoucherAPI;
with 'NGCP::Panel::Role::API';
@ -83,6 +82,7 @@ sub POST :Allow {
return;
}
$c->model('DB')->set_transaction_isolation('READ COMMITTED');
my $guard = $c->model('DB')->txn_scope_guard;
{
my $resource = $self->get_valid_post_data(
@ -98,48 +98,58 @@ sub POST :Allow {
form => $form,
exceptions => [qw/subscriber_id/],
);
if($c->user->roles eq "admin") {
} elsif($c->user->roles eq "reseller") {
$resource->{reseller_id} = $c->user->reseller_id;
}
#my $reseller_id;
#if($c->user->roles eq "admin") {
#} elsif($c->user->roles eq "reseller") {
# $reseller_id = $c->user->reseller_id;
#}
my $code = NGCP::Panel::Utils::Voucher::encrypt_code($c, $resource->{code});
my $now = NGCP::Panel::Utils::DateTime::current_local;
my $subscriber = $c->model('DB')->resultset('voip_subscribers')->find($resource->{subscriber_id});
unless($subscriber) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Unknown subscriber_id.');
last;
}
my $customer = $subscriber->contract;
unless($customer->status eq 'active') {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Customer contract is not active.');
last;
}
unless($customer->contact->reseller) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Contract is not a customer contract.');
last;
}
my $voucher = $c->model('DB')->resultset('vouchers')->find({
code => $code,
used_by_subscriber_id => undef,
valid_until => { '<=' => $now },
reseller_id => $customer->contact->reseller_id,
},{
for => 'update',
});
unless($voucher) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Invalid voucher code or already used.');
last;
}
if($voucher->customer_id && $customer->id != $voucher->customer_id) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Voucher is reserved for a different customer.');
last;
}
unless($voucher->reseller_id == $customer->contact->reseller_id) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, 'Voucher belongs to another reseller.');
last;
}
# TODO: add and check billing.vouchers.active flag for internal/emergency use
try {
# TODO: add billing.vouchers.active flag for internal/emergency use
my $now = NGCP::Panel::Utils::DateTime::current_local;
my $subscriber = $c->model('DB')->resultset('voip_subscribers')->find($resource->{subscriber_id});
unless($subscriber) {
# TODO: error
}
my $customer = $subscriber->contract;
my $voucher = $c->model('DB')->resultset('voip_subscribers')->find({
code => $code,
used_by_subscriber_id => undef,
valid_until => { '<=' => $now },
reseller_id => $customer->contact->reseller_id, # TODO: make unique key code,reseller_id
},{
for => 'update',
});
unless($voucher) {
# TODO: invalid code or already used
}
if($voucher->customer_id && $customer->id != $voucher->customer_id) {
# TODO: error, voucher only to be used by a different customer
}
if($voucher->reseller_id != $customer->contact->reseller_id) {
# TODO: error, voucher only to be used by a different customer
}
# TODO: update customer package_id, billing profile mappings etc.
my $balance = undef; # TODO: get current contract balance
$balance->update({ cash_balance => $balance->cash_balance + $voucher->amount });
my $balance = NGCP::Panel::Utils::ProfilePackages::topup_contract_balance(c => $c,
contract => $customer,
#old_package => $customer->profile_package,
voucher => $voucher,
now => $now,
);
$voucher->update({
used_by_subscriber_id => $subscriber->id,

@ -38,6 +38,17 @@ class_has 'query_params' => (
second => sub {},
},
},
{
param => 'package_id',
description => 'Filter for vouchers belonging to a specific profile package',
query => {
first => sub {
my $q = shift;
{ package_id => $q };
},
second => sub {},
},
},
]},
);

@ -42,6 +42,7 @@ sub voucher_list :Chained('/') :PathPart('voucher') :CaptureArgs(0) {
$c->user->billing_data ? { name => "code", "search" => 1, "title" => $c->loc("Code") } : (),
{ name => "amount", "search" => 1, "title" => $c->loc("Amount") },
{ name => "reseller.name", "search" => 1, "title" => $c->loc("Reseller") },
{ name => "profile_package.name", "search" => 1, "title" => $c->loc("Profile Package") },
{ name => "valid_until", "search" => 1, "title" => $c->loc("Valid Until") },
{ name => "used_at", "search" => 1, "title" => $c->loc("Used At") },
{ name => "used_by_subscriber.id", "search" => 1, "title" => $c->loc("Used By Subscriber #") },
@ -139,6 +140,8 @@ sub edit :Chained('base') :PathPart('edit') {
my $params = $c->stash->{voucher};
$params->{valid_until} =~ s/^(\d{4}\-\d{2}\-\d{2}).*$/$1/;
$params->{reseller}{id} = delete $params->{reseller_id};
$params->{customer}{id} = delete $params->{customer_id};
$params->{package}{id} = delete $params->{package_id};
if($c->user->billing_data) {
$params->{code} = NGCP::Panel::Utils::Voucher::decrypt_code($c, $params->{code});
} else {
@ -161,6 +164,7 @@ sub edit :Chained('base') :PathPart('edit') {
fields => {
'reseller.create' => $c->uri_for('/reseller/create'),
'customer.create' => $c->uri_for('/customer/create'),
'package.create' => $c->uri_for('/package/create'),
},
back_uri => $c->req->uri,
);
@ -173,7 +177,9 @@ sub edit :Chained('base') :PathPart('edit') {
}
delete $form->values->{reseller};
$form->values->{customer_id} = $form->values->{customer}{id};
$form->values->{package_id} = $form->values->{package}{id};
delete $form->values->{customer};
delete $form->values->{package};
if($form->values->{valid_until} =~ /^\d{4}\-\d{2}\-\d{2}$/) {
$form->values->{valid_until} = NGCP::Panel::Utils::DateTime::from_string($form->values->{valid_until})
->add(days => 1)->subtract(seconds => 1);
@ -190,6 +196,8 @@ sub edit :Chained('base') :PathPart('edit') {
});
delete $c->session->{created_objects}->{reseller};
delete $c->session->{created_objects}->{customer};
delete $c->session->{created_objects}->{profile_package};
NGCP::Panel::Utils::Message->info(
c => $c,
desc => $c->loc('Billing voucher successfully updated'),
@ -235,6 +243,7 @@ sub create :Chained('voucher_list') :PathPart('create') :Args(0) {
fields => {
'reseller.create' => $c->uri_for('/reseller/create'),
'customer.create' => $c->uri_for('/customer/create'),
'package.create' => $c->uri_for('/package/create'),
},
back_uri => $c->req->uri,
);
@ -248,6 +257,8 @@ sub create :Chained('voucher_list') :PathPart('create') :Args(0) {
delete $form->values->{reseller};
$form->values->{customer_id} = $form->values->{customer}{id};
delete $form->values->{customer};
$form->values->{package_id} = $form->values->{package}{id};
delete $form->values->{package};
$form->values->{created_at} = NGCP::Panel::Utils::DateTime::current_local;
if($form->values->{valid_until} =~ /^\d{4}\-\d{2}\-\d{2}$/) {
$form->values->{valid_until} = NGCP::Panel::Utils::DateTime::from_string($form->values->{valid_until})
@ -257,6 +268,8 @@ sub create :Chained('voucher_list') :PathPart('create') :Args(0) {
my $voucher = $c->model('DB')->resultset('vouchers')->create($form->values);
$c->session->{created_objects}->{voucher} = { id => $voucher->id };
delete $c->session->{created_objects}->{reseller};
delete $c->session->{created_objects}->{customer};
delete $c->session->{created_objects}->{profile_package};
NGCP::Panel::Utils::Message->info(
c => $c,
desc => $c->loc('Billing voucher successfully created'),
@ -330,6 +343,7 @@ sub voucher_upload :Chained('voucher_list') :PathPart('upload') :Args(0) {
->add(days => 1)->subtract(seconds => 1);
}
$row->{customer_id} = undef if(defined $row->{customer_id} && $row->{customer_id} eq "");
$row->{package_id} = undef if(defined $row->{package_id} && $row->{package_id} eq "");
$row->{code} = NGCP::Panel::Utils::Voucher::encrypt_code($c, $row->{code});
push @vouchers, $row;
}

@ -35,14 +35,15 @@ has_field 'billing_profile_id' => (
},
);
has_field 'invoice_id' => (
type => 'PosInteger',
#required => 1,
element_attr => {
rel => ['tooltip'],
title => ['The id of the invoice containing this invoice.']
},
);
#we leave this out for now
#has_field 'invoice_id' => (
# type => 'PosInteger',
# #required => 1,
# element_attr => {
# rel => ['tooltip'],
# title => ['The id of the invoice containing this invoice.']
# },
#);
has_field 'cash_balance' => (
type => 'Money',

@ -47,7 +47,8 @@ has_field 'interval_charge' => (
type => 'Money',
element_attr => {
rel => ['tooltip'],
title => ['The base fee charged per billing interval (a monthly fixed fee, e.g. 10) in Euro/Dollars/etc. This fee can be used on the invoice.']
#title => ['The base fee charged per billing interval (a monthly fixed fee, e.g. 10) in Euro/Dollars/etc. This fee can be used on the invoice.'] #cents???
title => ['The base fee charged per billing interval (a monthly fixed fee, e.g. 100) in cents. This fee can be used on the invoice.'] #cents???
},
default => '0',
);
@ -65,7 +66,8 @@ has_field 'interval_free_cash' => (
type => 'Money',
element_attr => {
rel => ['tooltip'],
title => ['The included free money per billing interval (in Euro, Dollars etc., e.g. 10).']
#title => ['The included free money per billing interval (in Euro, Dollars etc., e.g. 10).'] #cents???
title => ['The included free money per billing interval (in cents, e.g. 10000).']
},
default => '0',
);

@ -54,7 +54,7 @@ has_field 'initial_balance' => (
type => 'Money',
element_attr => {
rel => ['tooltip'],
title => ['The initial balance (in the effective profile\'s currency) that will be set for the very first balance interval.']
title => ['The initial balance (in cents) that will be set for the very first balance interval.']
},
);
@ -160,7 +160,7 @@ has_field 'underrun_lock_threshold' => (
type => 'Money',
element_attr => {
rel => ['tooltip'],
title => ['The balance threshold for the underrun lock level to come into effect.']
title => ['The balance threshold (in cents) for the underrun lock level to come into effect.']
},
);
@ -176,7 +176,7 @@ has_field 'underrun_profile_threshold' => (
type => 'Money',
element_attr => {
rel => ['tooltip'],
title => ['The balance threshold for underrun profiles to come into effect.']
title => ['The balance threshold (in cents) for underrun profiles to come into effect.']
},
);
@ -214,7 +214,7 @@ has_field 'service_charge' => (
type => 'Money',
element_attr => {
rel => ['tooltip'],
title => ['The service charge amount will be subtracted from the voucher amount.']
title => ['The service charge amount (in cents) will be subtracted from the voucher amount.']
},
);

@ -39,10 +39,12 @@ has_field 'description' => (
has_field 'initial_balance' => (
type => 'Money',
label => 'Initial Balance',
label => 'Initial Balance',
#inflate_method => sub { return $_[1] * 100.0 },
#deflate_method => sub { return $_[1] / 100.0 },
element_attr => {
rel => ['tooltip'],
title => ['The initial balance (in the effective profile\'s currency) that will be set for the very first balance interval.']
title => ['The initial balance (in cents) that will be set for the very first balance interval.']
},
default => 0,
);
@ -148,10 +150,12 @@ has_field 'notopup_discard_intervals' => (
has_field 'underrun_lock_threshold' => (
type => 'Money',
label => 'Underrun lock threshold',
label => 'Underrun lock threshold',
#inflate_method => sub { return $_[1] * 100.0 },
#deflate_method => sub { return $_[1] / 100.0 },
element_attr => {
rel => ['tooltip'],
title => ['The balance threshold for the underrun lock level to come into effect.']
title => ['The balance threshold (in cents) for the underrun lock level to come into effect.']
},
);
@ -166,10 +170,12 @@ has_field 'underrun_lock_level' => (
has_field 'underrun_profile_threshold' => (
type => 'Money',
label => 'Underrun profile threshold',
label => 'Underrun profile threshold',
#inflate_method => sub { return $_[1] * 100.0 },
#deflate_method => sub { return $_[1] / 100.0 },
element_attr => {
rel => ['tooltip'],
title => ['The balance threshold for underrun profiles to come into effect.']
title => ['The balance threshold (in cents) for underrun profiles to come into effect.']
},
);
@ -223,10 +229,12 @@ has_field 'topup_lock_level' => (
has_field 'service_charge' => (
type => 'Money',
label => 'Service Charge',
label => 'Service Charge',
#inflate_method => sub { return $_[1] * 100.0 },
#deflate_method => sub { return $_[1] / 100.0 },
element_attr => {
rel => ['tooltip'],
title => ['The service charge amount will be subtracted from the voucher amount upon every top-up.']
title => ['The service charge amount (in cents) will be subtracted from the voucher amount upon every top-up.']
},
default => 0,
);

@ -24,7 +24,7 @@ has_field 'subscriber_id' => (
},
);
has_field 'amount' => (
has_field 'code' => (
type => 'Text',
required => 1,
maxlength => 128,

@ -15,7 +15,7 @@ has_field 'reseller' => (
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/reseller code amount valid_until customer/],
render_list => [qw/reseller code amount valid_until customer package/],
);

@ -52,6 +52,14 @@ has_field 'customer' => (
},
);
has_field 'package' => (
type => '+NGCP::Panel::Field::ProfilePackage',
#validate_when_empty => 1,
element_attr => {
rel => ['tooltip'],
title => ['The profile package the customer will switch with the top-up.']
},
);
has_field 'save' => (
type => 'Submit',
@ -63,7 +71,7 @@ has_field 'save' => (
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/code amount valid_until customer/],
render_list => [qw/code amount valid_until customer package/],
);
has_block 'actions' => (

@ -61,6 +61,15 @@ has_field 'package_id' => (
},
);
has_field 'package' => (
type => '+NGCP::Panel::Field::ProfilePackage',
#required => 1,
element_attr => {
rel => ['tooltip'],
title => ['The profile package the customer will switch with the top-up.']
},
);
sub validate_valid_until {
my ($self, $field) = @_;

@ -67,7 +67,7 @@ sub hal_from_balance {
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 $invoice = $item->invoice;
#my $invoice = $item->invoice;
my %resource = $item->get_inflated_columns;
$resource{cash_balance} /= 100.0;
@ -99,7 +99,7 @@ sub hal_from_balance {
Data::HAL::Link->new(relation => 'ngcp:customerbalances', href => sprintf("/api/customerbalances/%d", $contract->id)) ) :
Data::HAL::Link->new(relation => 'ngcp:contracts', href => sprintf("/api/contracts/%d", $contract->id)) ),
Data::HAL::Link->new(relation => 'ngcp:billingprofiles', href => sprintf("/api/billingprofiles/%d", $profile_at_start->id)),
($invoice ? Data::HAL::Link->new(relation => 'ngcp:invoices', href => sprintf("/api/invoices/%d", $invoice->id)) : ()),
#($invoice ? Data::HAL::Link->new(relation => 'ngcp:invoices', href => sprintf("/api/invoices/%d", $invoice->id)) : ()),
],
relation => 'ngcp:'.$self->resource_name,
);

@ -42,6 +42,11 @@ sub hal_from_item {
$resource{initial_profiles} = _get_profiles_mappings($item,'initial_profiles');
$resource{topup_profiles} = _get_profiles_mappings($item,'topup_profiles');
$resource{underrun_profiles} = _get_profiles_mappings($item,'underrun_profiles');
#$resource{initial_balance} /= 100.0 if exists $resource{initial_balance} && defined $resource{initial_balance}; #prevent auto-vivivication..
#$resource{service_charge} /= 100.0 if exists $resource{service_charge} && defined $resource{service_charge};
#$resource{underrun_lock_threshold} /= 100.0 if exists $resource{underrun_lock_threshold} && defined $resource{underrun_lock_threshold};
#$resource{underrun_profile_threshold} /= 100.0 if exists $resource{underrun_profile_threshold} && defined $resource{underrun_profile_threshold};
my @profile_links = ();
my @network_links = ();
@ -120,6 +125,11 @@ sub update_item {
delete $resource->{id};
my $schema = $c->model('DB');
#$resource{initial_balance} *= 100.0 if exists $resource{initial_balance} && defined $resource{initial_balance}; #prevent auto-vivivication..
#$resource{service_charge} *= 100.0 if exists $resource{service_charge} && defined $resource{service_charge};
#$resource{underrun_lock_threshold} *= 100.0 if exists $resource{underrun_lock_threshold} && defined $resource{underrun_lock_threshold};
#$resource{underrun_profile_threshold} *= 100.0 if exists $resource{underrun_profile_threshold} && defined $resource{underrun_profile_threshold};
$form //= $self->get_form($c);
## TODO: for some reason, formhandler lets missing reseller slip thru
$resource->{reseller_id} //= undef;

@ -25,6 +25,11 @@ sub infinite_past {
#$dt->epoch calls should be okay if perl >= 5.12.0
}
sub is_infinite_past {
my $dt = shift;
return $dt->year <= 1000;
}
sub infinite_future {
#... to '9999-12-31 23:59:59'
return DateTime->new(year => 9999, month => 12, day => 31, hour => 23, minute => 59, second => 59,
@ -37,6 +42,16 @@ sub infinite_future {
);
}
sub is_infinite_future {
my $dt = shift;
return $dt->year >= 9999;
}
sub is_infinite {
my $dt = shift;
return is_infinite_past($dt) || is_infinite_future($dt);
}
sub set_fake_time {
my ($o) = @_;
if (defined $o) {

@ -23,6 +23,7 @@ use constant _DEFAULT_INITIAL_BALANCE => 0.0;
use constant _TOPUP_START_MODE => 'topup';
use constant _1ST_START_MODE => '1st';
use constant _CREATE_START_MODE => 'create';
#use constant _TOPUP_INTERVAL_START_MODE => 'topup_interval';
use constant _START_MODE_PRESERVE_EOM => { _TOPUP_START_MODE . '' => 0,
_1ST_START_MODE . '' => 0,
@ -55,10 +56,11 @@ sub get_contract_balance {
sub resize_actual_contract_balance {
my %params = @_;
my($c,$contract,$old_package,$actual_balance,$now,$schema) = @params{qw/c contract old_package balance now schema/};
my($c,$contract,$old_package,$actual_balance,$is_topup,$now,$schema) = @params{qw/c contract old_package balance is_topup now schema/};
$schema //= $c->model('DB');
$contract = $schema->resultset('contracts')->find({id => $contract->id},{for => 'update'}); #lock record
$is_topup //= 0;
return $actual_balance unless defined $contract->contact->reseller_id;
@ -66,18 +68,35 @@ sub resize_actual_contract_balance {
my $new_package = $contract->profile_package;
my ($old_start_mode,$new_start_mode);
my $create_next_balance = 0;
if (defined $old_package && !defined $new_package) {
$old_start_mode = $old_package->balance_interval_start_mode if $old_package->balance_interval_start_mode ne _DEFAULT_START_MODE;
$new_start_mode = _DEFAULT_START_MODE;
} elsif (!defined $old_package && defined $new_package) {
$old_start_mode = _DEFAULT_START_MODE;
$new_start_mode = $new_package->balance_interval_start_mode if $new_package->balance_interval_start_mode ne _DEFAULT_START_MODE;
} elsif (defined $old_package && defined $new_package && $old_package->balance_interval_start_mode ne $new_package->balance_interval_start_mode) { #&& $old_package->id != $new_package->id ?
$old_start_mode = $old_package->balance_interval_start_mode;
$new_start_mode = $new_package->balance_interval_start_mode;
$create_next_balance = _TOPUP_START_MODE eq $new_package->balance_interval_start_mode && $is_topup;
} elsif (defined $old_package && defined $new_package) {
if ($old_package->balance_interval_start_mode ne $new_package->balance_interval_start_mode) { #&& $old_package->id != $new_package->id ?
$old_start_mode = $old_package->balance_interval_start_mode;
$new_start_mode = $new_package->balance_interval_start_mode;
$create_next_balance = _TOPUP_START_MODE eq $new_package->balance_interval_start_mode && $is_topup;
} elsif (_TOPUP_START_MODE eq $new_package->balance_interval_start_mode && $is_topup) {
$old_start_mode = _TOPUP_START_MODE;
$new_start_mode = _TOPUP_START_MODE;
$create_next_balance = 1;
}
#} elsif (_TOPUP_INTERVAL_START_MODE eq $new_package->balance_interval_start_mode && $is_topup) {
# $old_start_mode = _TOPUP_INTERVAL_START_MODE;
# $new_start_mode = _TOPUP_INTERVAL_START_MODE;
# xx$create_next_balance = 1;
#}
}
if ($old_start_mode && $new_start_mode) {
if ($old_start_mode && $new_start_mode && $actual_balance->start < $now) {
my $end_of_resized_interval = _get_resized_interval_end(ctime => $now,
create_timestamp => $contract->create_timestamp // $contract->modify_timestamp,
start_mode => $new_start_mode);
@ -92,6 +111,15 @@ sub resize_actual_contract_balance {
end => $end_of_resized_interval,
@$resized_balance_values,
});
$actual_balance->discard_changes();
if ($create_next_balance) {
$actual_balance = catchup_contract_balances(schema => $schema,
contract => $contract,
old_package => $old_package,
now => $end_of_resized_interval->clone->add(seconds => 1),
);
}
#catchup_contract_balances(schema => $schema,
# contract => $contract,
# old_package => $new_package,
@ -119,7 +147,7 @@ sub catchup_contract_balances {
$schema //= $c->model('DB');
$contract = $schema->resultset('contracts')->find({id => $contract->id},{for => 'update'}); #lock record
$now //= $contract->modify_timestamp;
$old_package //= $contract->profile_package;
$old_package = $contract->profile_package if !exists $params{old_package};
my ($start_mode,$interval_unit,$interval_value,$carry_over_mode,$has_package);
@ -135,10 +163,10 @@ sub catchup_contract_balances {
$has_package = 0;
}
#my $first_balance = $contract->contract_balances->search(undef,{ order_by => { '-asc' => 'start'},})->first;
my $last_balance = $contract->contract_balances->search(undef,{ order_by => { '-desc' => 'end'},})->first;
my $last_profile;
#my $end_of_today = $now->clone->truncate(to => 'day')->add(days => 1)->subtract(seconds => 1);
while ($last_balance && $last_balance->end < $now) {
while ($last_balance && !NGCP::Panel::Utils::DateTime::is_infinite_future($last_balance->end) && $last_balance->end < $now) { #comparison takes 100++ sec if loaded lastbalance contains +inf
my $start_of_next_interval = $last_balance->end->clone->add(seconds => 1);
my $bm_actual;
@ -201,10 +229,83 @@ sub catchup_contract_balances {
}
sub XXtopup_create_contract_balance {
sub topup_contract_balance {
my %params = @_;
my($c,$contract,$now,$profile,$schema) = @params{qw/c contract now profile schema/};
return create_initial_contract_balance(c => $c,contract => $contract,now => $now,$profile,$schema,is_topup => 1);
my($c,$contract,$package,$voucher,$amount,$now,$schema) = @params{qw/c contract package voucher amount now schema/};
$schema //= $c->model('DB');
$contract = $schema->resultset('contracts')->find({id => $contract->id},{for => 'update'}); #lock record
$now //= NGCP::Panel::Utils::DateTime::current_local;
my $voucher_package = ($voucher ? $voucher->profile_package : $package);
my $old_package = $contract->profile_package;
$package = $voucher_package // $old_package;
my $topup_amount = ($voucher ? $voucher->amount : $amount) // 0.0;
my $mappings_to_create = [];
if ($package) { #always apply (old or new) topup profiles
$topup_amount -= $package->service_charge;
my $bm_actual = get_actual_billing_mapping(c => $c, contract => $contract, now => $now);
my $product_id = $bm_actual->billing_mappings->first->product->id;
foreach my $mapping ($package->topup_profiles->all) {
push(@$mappings_to_create,{ #assume not terminated,
billing_profile_id => $mapping->profile_id,
network_id => $mapping->network_id,
product_id => $product_id,
start_date => $now,
end_date => undef,
});
}
}
if ($voucher_package && (!$old_package || $voucher_package->id != $old_package->id)) {
$contract->update({ profile_package_id => $voucher_package->id,
#modify_timestamp => $now,
});
}
foreach my $mapping (@$mappings_to_create) {
$contract->billing_mappings->create($mapping);
}
$contract->discard_changes();
my $balance = catchup_contract_balances(c => $c,
contract => $contract,
old_package => $old_package,
now => $now);
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) {
$timely_end = $balance->end;
} else {
$timely_end = _add_interval($balance->start,$old_package->balance_interval_unit,$old_package->balance_interval_value,
_START_MODE_PRESERVE_EOM->{$old_package->balance_interval_start_mode} ? $contract->create_timestamp : undef)->subtract(seconds => 1);
}
my $timely_start = _add_interval($timely_end,$timely_duration_unit,-1 * $timely_duration_value)->add(seconds => 1);
$timely_start = $balance->start if $timely_start < $balance->start;
$is_timely = ($now >= $timely_start && $now <= $timely_end ? 1 : 0);
}
$balance = resize_actual_contract_balance(c => $c,
contract => $contract,
old_package => $old_package,
balance => $balance,
now => $now,
is_topup => 1,
);
$balance->update({ cash_balance => $balance->cash_balance + $topup_amount,
topup_count => $balance->topup_count + 1,
timely_topup_count => $balance->timely_topup_count + $is_timely});
$balance->discard_changes();
return $balance;
}
sub create_initial_contract_balance {
@ -243,7 +344,7 @@ sub create_initial_contract_balance {
start_mode => $start_mode,
now => $now,
profile => $profile,
initial_balance => $initial_balance * 100.0,
initial_balance => $initial_balance, # * 100.0,
);
#my $balance;
@ -305,7 +406,7 @@ sub _get_balance_values {
my $ratio;
if ($last_balance) {
if (_CARRY_OVER_MODE eq $carry_over_mode || (_CARRY_OVER_TIMELY_MODE eq $carry_over_mode && $last_balance->timely_count > 0)) {
if (_CARRY_OVER_MODE eq $carry_over_mode || (_CARRY_OVER_TIMELY_MODE eq $carry_over_mode && $last_balance->timely_topup_count > 0)) {
#if (!defined $last_profile) {
# my $bm_last = get_actual_billing_mapping(schema => $schema, contract => $contract, now => $last_balance->start); #end); !?
# $last_profile = $bm_last->billing_mappings->first->billing_profile;
@ -379,6 +480,17 @@ sub _get_balance_interval_start_end {
} else {
$etime = NGCP::Panel::Utils::DateTime::infinite_future;
}
#if (_TOPUP_START_MODE eq $start_mode) {
# $etime = NGCP::Panel::Utils::DateTime::infinite_future;
#} elsif (_TOPUP_INTERVAL_START_MODE eq $start_mode) {
# if ($first_balance) {
# $etime = _add_interval($stime,$interval_unit,$interval_value,_START_MODE_PRESERVE_EOM->{$start_mode} ? $first_balance->end : undef)->subtract(seconds => 1);
# } else {
# $etime = NGCP::Panel::Utils::DateTime::infinite_future;
# }
#} else {
# $etime = _add_interval($stime,$interval_unit,$interval_value,_START_MODE_PRESERVE_EOM->{$start_mode} ? $create : undef)->subtract(seconds => 1);
#}
}
return ($stime,$etime);
@ -409,6 +521,10 @@ sub _get_resized_interval_end {
return $ctime->clone; #->add(seconds => 1);
#return NGCP::Panel::Utils::DateTime::infinite_future;
}
#} elsif (_TOPUP_INTERVAL_START_MODE eq $start_mode) {
# return $ctime->clone; #->add(seconds => 1);
# #return NGCP::Panel::Utils::DateTime::infinite_future;
#}
return undef;
}
@ -421,6 +537,9 @@ sub _get_interval_start {
} elsif (_TOPUP_START_MODE eq $start_mode) {
return $ctime->clone; #->truncate(to => 'day');
}
#} elsif (_TOPUP_INTERVAL_START_MODE eq $start_mode) {
# return $ctime->clone; #->truncate(to => 'day');
#}
return undef;
}

@ -53,6 +53,7 @@ log4perl.appender.Default.layout.ConversionPattern=%d{ISO8601} [%p] [%F +%L] %m{
element_order amount
element_order valid_until
element_order customer_id
element_order package_id
</voucher_csv>
<dialogic>

@ -69,6 +69,8 @@ if ($is_local_env) {
);
}
my $infinite_future;
{
my $future = NGCP::Panel::Utils::DateTime::infinite_future;
my $past = NGCP::Panel::Utils::DateTime::infinite_past;
@ -77,7 +79,8 @@ if ($is_local_env) {
my $dtf = DateTime::Format::Strptime->new(
pattern => '%F %T',
);
is($dtf->format_datetime($future),'9999-12-31 23:59:59','check if infinite future is 9999-12-31 23:59:59');
$infinite_future = $dtf->format_datetime($future);
is($infinite_future,'9999-12-31 23:59:59','check if infinite future is 9999-12-31 23:59:59');
is($dtf->format_datetime($past),'1000-01-01 00:00:00','check if infinite past is 1000-01-01 00:00:00');
foreach my $offset ((0,'+'. 80*365*24*60*60 .'s','-'. 80*365*24*60*60 .'s')) {
@ -118,22 +121,6 @@ if ($is_local_env) {
my $t = time;
my $default_reseller_id = 1;
$req = HTTP::Request->new('POST', $uri.'/api/billingprofiles/');
$req->header('Content-Type' => 'application/json');
$req->header('Prefer' => 'return=representation');
$req->content(JSON::to_json({
name => "test profile $t",
handle => "testprofile$t",
reseller_id => $default_reseller_id,
}));
$res = $ua->request($req);
is($res->code, 201, "POST test billing profile");
my $billingprofile_uri = $uri.'/'.$res->header('Location');
$req = HTTP::Request->new('GET', $billingprofile_uri);
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed billing profile");
my $billingprofile = JSON::from_json($res->decoded_content);
# first, create a contact
$req = HTTP::Request->new('POST', $uri.'/api/customercontacts/');
$req->header('Content-Type' => 'application/json');
@ -150,12 +137,95 @@ $res = $ua->request($req);
is($res->code, 200, "fetch customer contact");
my $custcontact = JSON::from_json($res->decoded_content);
$req = HTTP::Request->new('POST', $uri.'/api/domains/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
domain => 'test' . ($t-1) . '.example.org',
reseller_id => $default_reseller_id,
}));
$res = $ua->request($req);
is($res->code, 201, "POST test domain");
$req = HTTP::Request->new('GET', $uri.'/'.$res->header('Location'));
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed test domain");
my $domain = JSON::from_json($res->decoded_content);
my %customer_map :shared = ();
my $package_map = {};
my $voucher_map = {};
my $subscriber_map = {};
my $profile_map = {};
my $billingprofile = _create_billing_profile("test_default");
if (_get_allow_fake_client_time()) {
{
my $network_a = _create_billing_network_a();
my $network_b = _create_billing_network_b();
my $profile_base_any = _create_billing_profile('BASE_ANY');
my $profile_base_a = _create_billing_profile('BASE_NETWORK_A');
my $profile_base_b = _create_billing_profile('BASE_NETWORK_B');
my $profile_silver_a = _create_billing_profile('SILVER_NETWORK_A');
my $profile_silver_b = _create_billing_profile('SILVER_NETWORK_B');
my $profile_gold_a = _create_billing_profile('GOLD_NETWORK_A');
my $profile_gold_b = _create_billing_profile('GOLD_NETWORK_B');
my $base_package = _create_base_profile_package($profile_base_any,$profile_base_a,$profile_base_b,$network_a,$network_b);
my $silver_package = _create_silver_profile_package($base_package,$profile_silver_a,$profile_silver_b,$network_a,$network_b);
my $extension_package = _create_extension_profile_package($base_package,$profile_silver_a,$profile_silver_b,$network_a,$network_b);
my $gold_package = _create_gold_profile_package($base_package,$profile_gold_a,$profile_gold_b,$network_a,$network_b);
_set_time(NGCP::Panel::Utils::DateTime::from_string('2015-06-03 13:00:00'));
my $v_silver_1 = _create_voucher(10,'SILVER1'.$t,undef,$silver_package);
my $v_extension_1 = _create_voucher(2,'EXTENSION1'.$t,undef,$extension_package);
my $v_gold_1 = _create_voucher(20,'GOLD1'.$t,undef,$gold_package);
_set_time(NGCP::Panel::Utils::DateTime::from_string('2015-06-03 13:00:00'));
my $customer_x = _create_customer($base_package);
my $subscriber_x = _create_subscriber($customer_x);
_set_time(NGCP::Panel::Utils::DateTime::from_string('2015-06-21 13:00:00'));
_perform_topup_voucher($subscriber_x,$v_silver_1);
_set_time(NGCP::Panel::Utils::DateTime::from_string('2015-07-21 13:00:00'));
_check_interval_history($customer_x,[
{ start => '~2015-06-03 13:00:00', stop => '~2015-06-21 13:00:00', cash => 0, profile => $profile_base_b->{id} },
{ start => '~2015-06-21 13:00:00', stop => $infinite_future, cash => 8, profile => $profile_silver_b->{id} },
]);
#_set_time(NGCP::Panel::Utils::DateTime::from_string('2015-06-03 13:00:00'));
#my $customer_y = _create_customer($base_package);
#my $subscriber_y = _create_subscriber($customer_y);
#_set_time(NGCP::Panel::Utils::DateTime::from_string('2015-03-04 13:00:00'));
#_perform_topup_voucher($subscriber,$v_extension_1);
# #my $voucher = _create_voucher(10,'A'.$t);
#
# #my $customer = _create_customer();
#
# #$voucher = _create_voucher(11,'B'.$t,$customer);
#
# my $prof_package_topup20 = _create_profile_package('topup');
#
# my $voucher = _create_voucher(20,'C'.$t,undef,$prof_package_topup20);
_set_time();
}
my $prof_package_create30d = _create_profile_package('create','day',30);
my $prof_package_1st30d = _create_profile_package('1st','day',30);
@ -170,11 +240,16 @@ if (_get_allow_fake_client_time()) {
{
_set_time(NGCP::Panel::Utils::DateTime::from_string('2014-12-30 13:00:00'));
my $customer_topup = _create_customer($prof_package_topup); #create closest to now
my $customer_wo = _create_customer();
my $customer_create1m = _create_customer($prof_package_create1m);
_set_time(NGCP::Panel::Utils::DateTime::from_string('2015-04-02 02:00:00'));
_check_interval_history($customer_topup,[
{ start => '~2014-12-30 13:00:00', stop => $infinite_future},
]);
_check_interval_history($customer_wo,[
{ start => '2014-12-01 00:00:00', stop => '2014-12-31 23:59:59'},
@ -189,7 +264,7 @@ if (_get_allow_fake_client_time()) {
{ start => '2015-01-30 00:00:00', stop => '2015-02-27 23:59:59'},
{ start => '2015-02-28 00:00:00', stop => '2015-03-29 23:59:59'},
{ start => '2015-03-30 00:00:00', stop => '2015-04-29 23:59:59'},
]);
]);
_set_time();
}
@ -206,7 +281,7 @@ if (_get_allow_fake_client_time()) {
$ts = '2014-03-01 13:00:00';
_set_time(NGCP::Panel::Utils::DateTime::from_string($ts));
_check_interval_history($customer,[
{ start => '2014-01-01 00:00:00', stop => '2014-01-31 23:59:59'},
{ start => '2014-02-01 00:00:00', stop => '2014-02-28 23:59:59'},
@ -223,7 +298,7 @@ if (_get_allow_fake_client_time()) {
$ts = '2014-04-01 13:00:00';
_set_time(NGCP::Panel::Utils::DateTime::from_string($ts));
_check_interval_history($customer,[
{ start => '2014-01-01 00:00:00', stop => '2014-01-31 23:59:59'},
{ start => '2014-02-01 00:00:00', stop => '2014-02-28 23:59:59'},
@ -273,7 +348,7 @@ if (_get_allow_fake_client_time()) {
]);
_switch_package($customer,$prof_package_1st1m);
_check_interval_history($customer,[
{ start => '2014-01-01 00:00:00', stop => '2014-01-31 23:59:59'},
{ start => '2014-02-01 00:00:00', stop => '2014-02-28 23:59:59'},
@ -281,7 +356,7 @@ if (_get_allow_fake_client_time()) {
{ start => '2014-03-07 00:00:00', stop => '2014-04-30 23:59:59'},
{ start => '2014-05-01 00:00:00', stop => '2014-05-31 23:59:59'},
]);
my $t1 = $ts;
$ts = '2014-08-03 13:00:00';
_set_time(NGCP::Panel::Utils::DateTime::from_string($ts));
@ -314,8 +389,55 @@ if (_get_allow_fake_client_time()) {
_check_interval_history($customer,[
{ start => '2014-08-07 00:00:00', stop => '2014-08-20 23:59:59'},
{ start => '2014-08-21 00:00:00', stop => '2014-09-30 23:59:59'},
],NGCP::Panel::Utils::DateTime::from_string($t1));
$t1 = $ts;
#my $t1 = '2014-09-03 13:00:00';
$ts = '2014-10-04 13:00:00';
_set_time(NGCP::Panel::Utils::DateTime::from_string($ts));
_switch_package($customer,$prof_package_topup);
_check_interval_history($customer,[
{ start => '2014-10-01 00:00:00', stop => '~2014-10-04 13:00:00'},
{ start => '~2014-10-04 13:00:00', stop => $infinite_future},
],NGCP::Panel::Utils::DateTime::from_string($t1));
my $voucher = _create_voucher(10,'topup_start_mode_test'.$t,$customer,$prof_package_create1m);
my $subscriber = _create_subscriber($customer);
#_check_interval_history($customer,[
# { start => '2014-10-01 00:00:00', stop => '~2014-10-04 13:00:00'},
# { start => '~2014-10-04 13:00:00', stop => $infinite_future},
#],NGCP::Panel::Utils::DateTime::from_string($t1));
_perform_topup_voucher($subscriber,$voucher);
_check_interval_history($customer,[
{ start => '2014-10-01 00:00:00', stop => '~2014-10-04 13:00:00'},
{ start => '~2014-10-04 13:00:00', stop => '2014-10-06 23:59:59'},
],NGCP::Panel::Utils::DateTime::from_string($t1));
$t1 = $ts;
$ts = '2014-12-09 13:00:00';
_set_time(NGCP::Panel::Utils::DateTime::from_string($ts));
_check_interval_history($customer,[
{ start => '~2014-10-04 13:00:00', stop => '2014-10-06 23:59:59'},
{ start => '2014-10-07 00:00:00', stop => '2014-11-06 23:59:59'},
{ start => '2014-11-07 00:00:00', stop => '2014-12-06 23:59:59'},
{ start => '2014-12-07 00:00:00', stop => '2015-01-06 23:59:59'},
],NGCP::Panel::Utils::DateTime::from_string($t1));
_switch_package($customer);
_check_interval_history($customer,[
{ start => '~2014-10-04 13:00:00', stop => '2014-10-06 23:59:59'},
{ start => '2014-10-07 00:00:00', stop => '2014-11-06 23:59:59'},
{ start => '2014-11-07 00:00:00', stop => '2014-12-06 23:59:59'},
{ start => '2014-12-07 00:00:00', stop => '2014-12-31 23:59:59'},
],NGCP::Panel::Utils::DateTime::from_string($t1));
_set_time();
}
@ -422,7 +544,7 @@ sub _check_interval_history {
my $i = 0;
my $limit = '';
$limit = '&start=' . DateTime::Format::ISO8601->parse_datetime($limit_dt) if defined $limit_dt;
my $label = 'interval history of contract with ' . ($customer->{profile_package_id} ? 'package ' . $profile_map->{$customer->{profile_package_id}}->{name} : 'no package') . ': ';
my $label = 'interval history of contract with ' . ($customer->{profile_package_id} ? 'package ' . $package_map->{$customer->{profile_package_id}}->{name} : 'no package') . ': ';
my $nexturi = $uri.'/api/balanceintervals/'.$customer->{id}.'/?page=1&rows=10&order_by_direction=asc&order_by=start'.$limit;
do {
$req = HTTP::Request->new('GET',$nexturi);
@ -495,12 +617,39 @@ sub _compare_interval {
my ($got,$expected,$label) = @_;
if ($expected->{start}) {
is(NGCP::Panel::Utils::DateTime::from_string($got->{start}),NGCP::Panel::Utils::DateTime::from_string($expected->{start}),$label . "check interval " . $got->{id} . " start timestmp");
#is(NGCP::Panel::Utils::DateTime::from_string($got->{start}),NGCP::Panel::Utils::DateTime::from_string($expected->{start}),$label . "check interval " . $got->{id} . " start timestmp");
if (substr($expected->{start},0,1) eq '~') {
_is_ts_approx($got->{start},$expected->{start},$label . "check interval " . $got->{id} . " start timestamp");
} else {
is($got->{start},$expected->{start},$label . "check interval " . $got->{id} . " start timestmp");
}
}
if ($expected->{stop}) {
is(NGCP::Panel::Utils::DateTime::from_string($got->{stop}),NGCP::Panel::Utils::DateTime::from_string($expected->{stop}),$label . "check interval " . $got->{id} . " stop timestmp");
#is(NGCP::Panel::Utils::DateTime::from_string($got->{stop}),NGCP::Panel::Utils::DateTime::from_string($expected->{stop}),$label . "check interval " . $got->{id} . " stop timestmp");
if (substr($expected->{stop},0,1) eq '~') {
_is_ts_approx($got->{stop},$expected->{stop},$label . "check interval " . $got->{id} . " stop timestamp");
} else {
is($got->{stop},$expected->{stop},$label . "check interval " . $got->{id} . " stop timestmp");
}
}
if ($expected->{cash}) {
is($got->{cash_balance},$expected->{cash},$label . "check interval " . $got->{id} . " cash balance");
}
if ($expected->{profile}) {
is($got->{profile_id},$expected->{profile_id},$label . "check interval " . $got->{id} . " billing profile");
}
}
sub _is_ts_approx {
my ($got,$expected,$label) = @_;
$got = NGCP::Panel::Utils::DateTime::from_string($got);
$expected = NGCP::Panel::Utils::DateTime::from_string(substr($expected,1));
my $lower = $expected->clone->subtract(seconds => 5);
my $upper = $expected->clone->add(seconds => 5);
ok($got >= $lower && $got <= $upper,$label . ' approximately (' . $got . ')');
}
sub _fetch_intervals_worker {
@ -573,7 +722,7 @@ sub _switch_package {
[ { op => 'replace', path => '/profile_package_id', value => ($package ? $package->{id} : undef) } ]
));
$res = $ua->request($req);
is($res->code, 200, "patch customer from " . ($customer->{profile_package_id} ? 'package ' . $profile_map->{$customer->{profile_package_id}}->{name} : 'no package') . " to " .
is($res->code, 200, "patch customer from " . ($customer->{profile_package_id} ? 'package ' . $package_map->{$customer->{profile_package_id}}->{name} : 'no package') . " to " .
($package ? $package->{name} : 'no package'));
return JSON::from_json($res->decoded_content);
@ -625,11 +774,290 @@ sub _create_profile_package {
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed profilepackage - '" . $name . "'");
my $package = JSON::from_json($res->decoded_content);
$profile_map->{$package->{id}} = $package;
$package_map->{$package->{id}} = $package;
return $package;
}
sub _create_billing_network_a {
$req = HTTP::Request->new('POST', $uri.'/api/billingnetworks/');
$req->header('Content-Type' => 'application/json');
$req->header('Prefer' => 'return=representation');
$req->content(JSON::to_json({
name => "test billing network A ".$t,
description => "test billing network A description ".$t,
reseller_id => $default_reseller_id,
blocks => [{ip=>'fdfe::5a55:caff:fefa:9089',mask=>128},
{ip=>'fdfe::5a55:caff:fefa:908a'},
{ip=>'fdfe::5a55:caff:fefa:908b',mask=>128},],
}));
$res = $ua->request($req);
is($res->code, 201, "POST test billingnetwork A");
$req = HTTP::Request->new('GET', $uri.'/'.$res->header('Location'));
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed billingnetwork A");
my $billingnetwork = JSON::from_json($res->decoded_content);
}
sub _create_billing_network_b {
$req = HTTP::Request->new('POST', $uri.'/api/billingnetworks/');
$req->header('Content-Type' => 'application/json');
$req->header('Prefer' => 'return=representation');
$req->content(JSON::to_json({
name => "test billing network B ".$t,
description => "FIRST test billing network B description ".$t,
reseller_id => $default_reseller_id,
blocks => [{ip=>'10.0.4.7',mask=>26}, #0..63
{ip=>'10.0.4.99',mask=>26}, #64..127
{ip=>'10.0.5.9',mask=>24},
{ip=>'10.0.6.9',mask=>24},],
}));
$res = $ua->request($req);
is($res->code, 201, "POST test billingnetwork B");
$req = HTTP::Request->new('GET', $uri.'/'.$res->header('Location'));
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed billingnetwork B");
return JSON::from_json($res->decoded_content);
}
sub _create_base_profile_package {
my ($profile_base_any,$profile_base_a,$profile_base_b,$network_a,$network_b) = @_;
$req = HTTP::Request->new('POST', $uri.'/api/profilepackages/');
$req->header('Content-Type' => 'application/json');
$req->header('Prefer' => 'return=representation');
#$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
#my $name = $start_mode . ($interval_unit ? '/' . $interval_value . ' ' . $interval_unit : '');
$req->content(JSON::to_json({
name => "base profile package " . $t,
description => "base test profile package description " . $t,
reseller_id => $default_reseller_id,
initial_profiles => [{ profile_id => $profile_base_any->{id}, },
{ profile_id => $profile_base_a->{id}, network_id => $network_a->{id} },
{ profile_id => $profile_base_b->{id}, network_id => $network_b->{id} }],
balance_interval_start_mode => 'topup',
balance_interval_value => 1,
balance_interval_unit => 'month',
carry_over_mode => 'carry_over_timely',
timely_duration_value => 1,
timely_duration_unit => 'month',
}));
$res = $ua->request($req);
is($res->code, 201, "POST test base profilepackage");
my $profilepackage_uri = $uri.'/'.$res->header('Location');
$req = HTTP::Request->new('GET', $profilepackage_uri);
$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed base profilepackage");
my $package = JSON::from_json($res->decoded_content);
$package_map->{$package->{id}} = $package;
return $package;
}
sub _create_silver_profile_package {
my ($base_package,$profile_silver_a,$profile_silver_b,$network_a,$network_b) = @_;
$req = HTTP::Request->new('POST', $uri.'/api/profilepackages/');
$req->header('Content-Type' => 'application/json');
$req->header('Prefer' => 'return=representation');
#$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
#my $name = $start_mode . ($interval_unit ? '/' . $interval_value . ' ' . $interval_unit : '');
$req->content(JSON::to_json({
name => "silver profile package " . $t,
description => "silver test profile package description " . $t,
reseller_id => $default_reseller_id,
initial_profiles => $base_package->{initial_profiles},
balance_interval_start_mode => 'topup',
balance_interval_value => 1,
balance_interval_unit => 'month',
carry_over_mode => 'carry_over_timely',
timely_duration_value => 1,
timely_duration_unit => 'month',
service_charge => 200,
topup_profiles => [ #{ profile_id => $profile_silver_any->{id}, },
{ profile_id => $profile_silver_a->{id}, network_id => $network_a->{id} } ,
{ profile_id => $profile_silver_b->{id}, network_id => $network_b->{id} } ],
}));
$res = $ua->request($req);
is($res->code, 201, "POST test silver profilepackage");
my $profilepackage_uri = $uri.'/'.$res->header('Location');
$req = HTTP::Request->new('GET', $profilepackage_uri);
$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed silver profilepackage");
my $package = JSON::from_json($res->decoded_content);
$package_map->{$package->{id}} = $package;
return $package;
}
sub _create_extension_profile_package {
my ($base_package,$profile_silver_a,$profile_silver_b,$network_a,$network_b) = @_;
$req = HTTP::Request->new('POST', $uri.'/api/profilepackages/');
$req->header('Content-Type' => 'application/json');
$req->header('Prefer' => 'return=representation');
#$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
#my $name = $start_mode . ($interval_unit ? '/' . $interval_value . ' ' . $interval_unit : '');
$req->content(JSON::to_json({
name => "extension profile package " . $t,
description => "extension test profile package description " . $t,
reseller_id => $default_reseller_id,
initial_profiles => $base_package->{initial_profiles},
balance_interval_start_mode => 'topup',
balance_interval_value => 1,
balance_interval_unit => 'month',
carry_over_mode => 'carry_over_timely',
timely_duration_value => 1,
timely_duration_unit => 'month',
service_charge => 200,
topup_profiles => [ #{ profile_id => $profile_silver_any->{id}, },
{ profile_id => $profile_silver_a->{id}, network_id => $network_a->{id} } ,
{ profile_id => $profile_silver_b->{id}, network_id => $network_b->{id} } ],
}));
$res = $ua->request($req);
is($res->code, 201, "POST test extension profilepackage");
my $profilepackage_uri = $uri.'/'.$res->header('Location');
$req = HTTP::Request->new('GET', $profilepackage_uri);
$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed extension profilepackage");
my $package = JSON::from_json($res->decoded_content);
$package_map->{$package->{id}} = $package;
return $package;
}
sub _create_gold_profile_package {
my ($base_package,$profile_gold_a,$profile_gold_b,$network_a,$network_b) = @_;
$req = HTTP::Request->new('POST', $uri.'/api/profilepackages/');
$req->header('Content-Type' => 'application/json');
$req->header('Prefer' => 'return=representation');
#$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
#my $name = $start_mode . ($interval_unit ? '/' . $interval_value . ' ' . $interval_unit : '');
$req->content(JSON::to_json({
name => "gold profile package " . $t,
description => "gold test profile package description " . $t,
reseller_id => $default_reseller_id,
initial_profiles => $base_package->{initial_profiles},
balance_interval_start_mode => 'topup',
balance_interval_value => 1,
balance_interval_unit => 'month',
carry_over_mode => 'carry_over',
#timely_duration_value => 1,
#timely_duration_unit => 'month',
service_charge => 500,
topup_profiles => [ #{ profile_id => $profile_gold_any->{id}, },
{ profile_id => $profile_gold_a->{id}, network_id => $network_a->{id} } ,
{ profile_id => $profile_gold_b->{id}, network_id => $network_b->{id} } ],
}));
$res = $ua->request($req);
is($res->code, 201, "POST test gold profilepackage");
my $profilepackage_uri = $uri.'/'.$res->header('Location');
$req = HTTP::Request->new('GET', $profilepackage_uri);
$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed gold profilepackage");
my $package = JSON::from_json($res->decoded_content);
$package_map->{$package->{id}} = $package;
return $package;
}
sub _create_voucher {
my ($amount,$code,$customer,$package,$valid_until_dt) = @_;
my $dtf = DateTime::Format::Strptime->new(
pattern => '%F %T',
);
$req = HTTP::Request->new('POST', $uri.'/api/vouchers/');
$req->header('Content-Type' => 'application/json');
$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
$req->content(JSON::to_json({
amount => $amount * 100.0,
code => $code,
customer_id => ($customer ? $customer->{id} : undef),
package_id => ($package ? $package->{id} : undef),
reseller_id => $default_reseller_id,
valid_until => $dtf->format_datetime($valid_until_dt ? $valid_until_dt : NGCP::Panel::Utils::DateTime::current_local->add(years => 1)),
}));
$res = $ua->request($req);
my $label = 'test voucher (' . ($customer ? 'for customer ' . $customer->{id} : 'no customer') . ', ' . ($package ? 'for package ' . $package->{id} : 'no package') . ')';
is($res->code, 201, "create " . $label);
$req = HTTP::Request->new('GET', $uri.'/'.$res->header('Location'));
$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
$res = $ua->request($req);
is($res->code, 200, "fetch " . $label);
my $voucher = JSON::from_json($res->decoded_content);
$voucher_map->{$voucher->{id}} = $voucher;
return $voucher;
}
sub _create_subscriber {
my ($customer) = @_;
$req = HTTP::Request->new('POST', $uri.'/api/subscribers/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
domain_id => $domain->{id},
username => 'test_customer_subscriber_' . (scalar keys %$subscriber_map) . '_'.$t,
password => 'test_customer_subscriber_password',
customer_id => $customer->{id},
#status => "active",
}));
$res = $ua->request($req);
is($res->code, 201, "POST test subscriber");
$req = HTTP::Request->new('GET', $uri.'/'.$res->header('Location'));
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed test subscriber");
my $subscriber = JSON::from_json($res->decoded_content);
$subscriber_map->{$subscriber->{id}} = $subscriber;
return $subscriber;
}
sub _perform_topup_voucher {
my ($subscriber,$voucher) = @_;
$req = HTTP::Request->new('POST', $uri.'/api/topupvouchers/');
$req->header('Content-Type' => 'application/json');
$req->header('X-Fake-Clienttime' => _get_rfc_1123_now());
$req->content(JSON::to_json({
code => $voucher->{code},
subscriber_id => $subscriber->{id},
}));
$res = $ua->request($req);
is($res->code, 204, "perform topup with voucher " . $voucher->{code});
}
sub _create_billing_profile {
my ($name) = @_;
$req = HTTP::Request->new('POST', $uri.'/api/billingprofiles/');
$req->header('Content-Type' => 'application/json');
$req->header('Prefer' => 'return=representation');
$req->content(JSON::to_json({
name => $name." $t",
handle => $name."_$t",
reseller_id => $default_reseller_id,
}));
$res = $ua->request($req);
is($res->code, 201, "POST test billing profile " . $name);
$req = HTTP::Request->new('GET', $uri.'/'.$res->header('Location'));
$res = $ua->request($req);
is($res->code, 200, "fetch POSTed billing profile" . $name);
my $billingprofile = JSON::from_json($res->decoded_content);
$profile_map->{$billingprofile->{id}} = $billingprofile;
return $billingprofile;
}
sub _get_allow_delay_commit {
my $allow_delay_commit = 0;
my $cfg = $config{api_debug_opts};

Loading…
Cancel
Save