You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
786 lines
36 KiB
786 lines
36 KiB
package NGCP::Panel::Utils::ProfilePackages;
|
|
use strict;
|
|
use warnings;
|
|
|
|
#use TryCatch;
|
|
use NGCP::Panel::Utils::DateTime;
|
|
|
|
use constant INITIAL_PROFILE_DISCRIMINATOR => 'initial';
|
|
use constant UNDERRUN_PROFILE_DISCRIMINATOR => 'underrun';
|
|
use constant TOPUP_PROFILE_DISCRIMINATOR => 'topup';
|
|
|
|
use constant _DISCRIMINATOR_MAP => { initial_profiles => INITIAL_PROFILE_DISCRIMINATOR,
|
|
underrun_profiles => UNDERRUN_PROFILE_DISCRIMINATOR,
|
|
topup_profiles => TOPUP_PROFILE_DISCRIMINATOR};
|
|
|
|
use constant _CARRY_OVER_TIMELY_MODE => 'carry_over_timely';
|
|
use constant _CARRY_OVER_MODE => 'carry_over';
|
|
|
|
use constant _DEFAULT_CARRY_OVER_MODE => _CARRY_OVER_MODE;
|
|
|
|
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,
|
|
_CREATE_START_MODE . '' => 1};
|
|
|
|
use constant _DEFAULT_START_MODE => '1st';
|
|
use constant _DEFAULT_PROFILE_INTERVAL_UNIT => 'week';
|
|
use constant _DEFAULT_PROFILE_INTERVAL_COUNT => 1;
|
|
use constant _DEFAULT_PROFILE_FREE_TIME => 0;
|
|
use constant _DEFAULT_PROFILE_FREE_CASH => 0.0;
|
|
|
|
sub get_contract_balance {
|
|
my %params = @_;
|
|
my($c,$contract,$now,$schema,$stime,$etime) = @params{qw/c contract now schema stime etime/};
|
|
|
|
$schema //= $c->model('DB');
|
|
$now //= NGCP::Panel::Utils::DateTime::current_local;
|
|
|
|
my $balance = catchup_contract_balances(schema => $schema, contract => $contract, now => $now);
|
|
|
|
if (defined $stime || defined $etime) { #supported for backward compat only
|
|
$balance = $contract->contract_balances->search({
|
|
start => { '>=' => $stime },
|
|
end => { '<=' => $etime },
|
|
},{ order_by => { '-desc' => 'end'},})->first;
|
|
}
|
|
return $balance;
|
|
|
|
}
|
|
|
|
sub resize_actual_contract_balance {
|
|
my %params = @_;
|
|
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;
|
|
|
|
$now //= $contract->modify_timestamp;
|
|
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;
|
|
$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 && $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);
|
|
my $resized_balance_values = _get_resized_balance_values(schema => $schema,
|
|
balance => $actual_balance,
|
|
old_start_mode => $old_start_mode,
|
|
new_start_mode => $new_start_mode,
|
|
etime => $end_of_resized_interval);
|
|
#try {
|
|
# $schema->txn_do(sub {
|
|
$actual_balance->update({
|
|
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,
|
|
# now => $now) if _TOPUP_START_MODE eq $new_package->start_mode;
|
|
# });
|
|
#} catch($e) {
|
|
# if ($e =~ /Duplicate entry/) {
|
|
# #libswrate or rat-o-mat are interferring?
|
|
# $c->log->warn("Resizing contract balance failed: Duplicate entry. Ignoring!");
|
|
# } else {
|
|
# $c->log->error("Resizing contract balance failed: " . $e);
|
|
# $e->rethrow;
|
|
# }
|
|
#};
|
|
}
|
|
|
|
return $actual_balance;
|
|
|
|
}
|
|
|
|
sub catchup_contract_balances {
|
|
my %params = @_;
|
|
my($c,$contract,$old_package,$now,$schema) = @params{qw/c contract old_package now schema/};
|
|
|
|
$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 if !exists $params{old_package};
|
|
|
|
my ($start_mode,$interval_unit,$interval_value,$carry_over_mode,$has_package);
|
|
|
|
if (defined $contract->contact->reseller_id && $old_package) {
|
|
$start_mode = $old_package->balance_interval_start_mode;
|
|
$interval_unit = $old_package->balance_interval_unit;
|
|
$interval_value = $old_package->balance_interval_value;
|
|
$carry_over_mode = $old_package->carry_over_mode;
|
|
$has_package = 1;
|
|
} else {
|
|
$start_mode = _DEFAULT_START_MODE;
|
|
$carry_over_mode = _DEFAULT_CARRY_OVER_MODE;
|
|
$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;
|
|
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;
|
|
unless ($last_profile) {
|
|
$bm_actual = get_actual_billing_mapping(schema => $schema, contract => $contract, now => $last_balance->start);
|
|
$last_profile = $bm_actual->billing_mappings->first->billing_profile;
|
|
}
|
|
$bm_actual = get_actual_billing_mapping(schema => $schema, contract => $contract, now => $start_of_next_interval);
|
|
my $profile = $bm_actual->billing_mappings->first->billing_profile;
|
|
$interval_unit = $has_package ? $interval_unit : ($profile->interval_unit // _DEFAULT_PROFILE_INTERVAL_UNIT);
|
|
$interval_value = $has_package ? $interval_value : ($profile->interval_count // _DEFAULT_PROFILE_INTERVAL_COUNT);
|
|
|
|
my ($stime,$etime) = _get_balance_interval_start_end(
|
|
last_etime => $last_balance->end,
|
|
start_mode => $start_mode,
|
|
#now => $start_of_next_interval,
|
|
interval_unit => $interval_unit,
|
|
interval_value => $interval_value,
|
|
create => $contract->create_timestamp // $contract->modify_timestamp);
|
|
|
|
my $balance_values = _get_balance_values(schema => $schema,
|
|
stime => $stime,
|
|
etime => $etime,
|
|
start_mode => $start_mode,
|
|
contract => $contract,
|
|
profile => $profile,
|
|
carry_over_mode => $carry_over_mode,
|
|
last_balance => $last_balance,
|
|
last_profile => $last_profile,
|
|
);
|
|
$last_profile = $profile;
|
|
|
|
#try {
|
|
# $schema->txn_do(sub {
|
|
$last_balance = $schema->resultset('contract_balances')->create({
|
|
contract_id => $contract->id,
|
|
start => $stime,
|
|
end => $etime,
|
|
@$balance_values,
|
|
});
|
|
$last_balance->discard_changes();
|
|
# });
|
|
#} catch($e) {
|
|
# if ($e =~ /Duplicate entry/) {
|
|
# #libswrate or rat-o-mat are interferring?
|
|
# $c->log->warn("Creating contract balance failed: Duplicate entry. Ignoring!");
|
|
# $last_balance = $contract->contract_balances
|
|
# ->find({
|
|
# start => { '>=' => $stime },
|
|
# end => { '<=' => $etime },
|
|
# });
|
|
# } else {
|
|
# $c->log->error("Creating contract balance failed: " . $e);
|
|
# $e->rethrow;
|
|
# }
|
|
#};
|
|
}
|
|
|
|
return $last_balance;
|
|
|
|
}
|
|
|
|
sub topup_contract_balance {
|
|
my %params = @_;
|
|
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 {
|
|
my %params = @_;
|
|
my($c,$contract,$profile,$now,$schema) = @params{qw/c contract profile now schema/};
|
|
|
|
$schema //= $c->model('DB');
|
|
$contract = $schema->resultset('contracts')->find({id => $contract->id},{for => 'update'}); #lock record
|
|
$now //= $contract->create_timestamp // $contract->modify_timestamp;
|
|
|
|
my ($start_mode,$interval_unit,$interval_value,$initial_balance);
|
|
|
|
my $package = $contract->profile_package;
|
|
if (defined $contract->contact->reseller_id && $package) {
|
|
$start_mode = $package->balance_interval_start_mode;
|
|
$interval_unit = $package->balance_interval_unit;
|
|
$interval_value = $package->balance_interval_value;
|
|
$initial_balance = $package->initial_balance; #euro
|
|
} else {
|
|
$start_mode = _DEFAULT_START_MODE;
|
|
$interval_unit = $profile->interval_unit // _DEFAULT_PROFILE_INTERVAL_UNIT; #'month';
|
|
$interval_value = $profile->interval_count // _DEFAULT_PROFILE_INTERVAL_COUNT; #1;
|
|
$initial_balance = _DEFAULT_INITIAL_BALANCE;
|
|
}
|
|
|
|
my ($stime,$etime) = _get_balance_interval_start_end(
|
|
now => $now,
|
|
start_mode => $start_mode,
|
|
interval_unit => $interval_unit,
|
|
interval_value => $interval_value,
|
|
create => $contract->create_timestamp // $contract->modify_timestamp);
|
|
|
|
my $balance_values = _get_balance_values(schema => $schema,
|
|
stime => $stime,
|
|
etime => $etime,
|
|
start_mode => $start_mode,
|
|
now => $now,
|
|
profile => $profile,
|
|
initial_balance => $initial_balance, # * 100.0,
|
|
);
|
|
|
|
#my $balance;
|
|
#try {
|
|
# $schema->txn_do(sub {
|
|
my $balance = $schema->resultset('contract_balances')->create({
|
|
contract_id => $contract->id,
|
|
start => $stime,
|
|
end => $etime,
|
|
@$balance_values,
|
|
});
|
|
$balance->discard_changes();
|
|
# });
|
|
#} catch($e) {
|
|
# if ($e =~ /Duplicate entry/) {
|
|
# $c->log->warn("Creating contract balance failed: Duplicate entry. Ignoring!");
|
|
# } else {
|
|
# $c->log->error("Creating contract balance failed: " . $e);
|
|
# $e->rethrow;
|
|
# }
|
|
#};
|
|
return $balance;
|
|
|
|
}
|
|
|
|
sub _get_resized_balance_values {
|
|
my %params = @_;
|
|
my ($c,$balance,$old_start_mode,$new_start_mode,$etime,$schema) = @params{qw/c balance old_start_mode new_start_mode etime schema/};
|
|
|
|
$schema //= $c->model('DB');
|
|
my ($cash_balance, $free_time_balance) = ($balance->cash_balance,$balance->free_time_balance);
|
|
|
|
my $contract = $balance->contract;
|
|
my $contract_create = $contract->create_timestamp // $contract->modify_timestamp;
|
|
if ($balance->start <= $contract_create && $balance->end >= $contract_create) {
|
|
my $bm = get_actual_billing_mapping(schema => $schema, contract => $contract, now => $contract_create); #now => $balance->start); #end); !?
|
|
my $profile = $bm->billing_mappings->first->billing_profile;
|
|
my $old_ratio = _get_free_ratio($contract_create,$old_start_mode,$balance->start,$balance->end);
|
|
my $old_free_cash = $old_ratio * ($profile->interval_free_cash // _DEFAULT_PROFILE_FREE_CASH);
|
|
my $old_free_time = $old_ratio * ($profile->interval_free_time // _DEFAULT_PROFILE_FREE_TIME);
|
|
my $new_ratio = _get_free_ratio($contract_create,$new_start_mode,$balance->start,$etime);
|
|
my $new_free_cash = $new_ratio * ($profile->interval_free_cash // _DEFAULT_PROFILE_FREE_CASH);
|
|
my $new_free_time = $new_ratio * ($profile->interval_free_time // _DEFAULT_PROFILE_FREE_TIME);
|
|
$cash_balance = $new_free_cash - $old_free_cash;
|
|
$free_time_balance += $new_free_time - $old_free_time;
|
|
}
|
|
|
|
return [cash_balance => sprintf("%.4f",$cash_balance), free_time_balance => sprintf("%.0f",$free_time_balance)];
|
|
|
|
}
|
|
|
|
sub _get_balance_values {
|
|
my %params = @_;
|
|
my($c, $profile, $last_profile, $contract, $last_balance, $stime, $etime, $initial_balance, $carry_over_mode, $now, $start_mode, $schema) = @params{qw/c profile last_profile contract last_balance stime etime initial_balance carry_over_mode now start_mode schema/};
|
|
|
|
$schema //= $c->model('DB');
|
|
$now //= $contract->create_timestamp // $contract->modify_timestamp;
|
|
my ($cash_balance,$cash_balance_interval, $free_time_balance, $free_time_balance_interval) = (0.0,0.0,0,0);
|
|
|
|
my $ratio;
|
|
if ($last_balance) {
|
|
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;
|
|
#}
|
|
my $contract_create = $contract->create_timestamp // $contract->modify_timestamp;
|
|
$ratio = 1.0;
|
|
if ($last_balance->start <= $contract_create && $last_balance->end >= $contract_create) {
|
|
$ratio = _get_free_ratio($contract_create,$start_mode,$last_balance->start,$last_balance->end);
|
|
}
|
|
my $old_free_cash = $ratio * ($last_profile->interval_free_cash // _DEFAULT_PROFILE_FREE_CASH);
|
|
$cash_balance = $last_balance->cash_balance;
|
|
if ($last_balance->cash_balance_interval < $old_free_cash) {
|
|
$cash_balance += $last_balance->cash_balance_interval - $old_free_cash;
|
|
}
|
|
#$ratio * $last_profile->interval_free_time // _DEFAULT_PROFILE_FREE_TIME
|
|
}
|
|
$ratio = 1.0;
|
|
} else {
|
|
$cash_balance = (defined $initial_balance ? $initial_balance : _DEFAULT_INITIAL_BALANCE);
|
|
$ratio = _get_free_ratio($now,$start_mode,$stime, $etime);
|
|
}
|
|
|
|
my $free_cash = $ratio * ($profile->interval_free_cash // _DEFAULT_PROFILE_FREE_CASH);
|
|
$cash_balance += $free_cash;
|
|
$cash_balance_interval = 0.0;
|
|
|
|
my $free_time = $ratio * ($profile->interval_free_time // _DEFAULT_PROFILE_FREE_TIME);
|
|
$free_time_balance = $free_time;
|
|
$free_time_balance_interval = 0;
|
|
|
|
return [cash_balance => sprintf("%.4f",$cash_balance),
|
|
cash_balance_interval => sprintf("%.4f",$cash_balance_interval),
|
|
free_time_balance => sprintf("%.0f",$free_time_balance),
|
|
free_time_balance_interval => sprintf("%.0f",$free_time_balance_interval)];
|
|
|
|
}
|
|
|
|
sub _get_free_ratio {
|
|
my ($now,$start_mode,$stime,$etime) = @_;
|
|
if (_TOPUP_START_MODE ne $start_mode) {
|
|
my $ctime;
|
|
if (defined $now) {
|
|
$ctime = ($now->clone->truncate(to => 'day') > $stime ? $now->clone->truncate(to => 'day') : $now);
|
|
} else {
|
|
$now = NGCP::Panel::Utils::DateTime::current_local;
|
|
$ctime = $now->clone->truncate(to => 'day') > $stime ? $now->truncate(to => 'day') : $now;
|
|
}
|
|
#my $ctime = (defined $now ? $now->clone : NGCP::Panel::Utils::DateTime::current_local);
|
|
#$ctime->truncate(to => 'day') if $ctime->clone->truncate(to => 'day') > $stime;
|
|
my $start_of_next_interval = $etime->clone->add(seconds => 1);
|
|
return ($start_of_next_interval->epoch - $ctime->epoch) / ($start_of_next_interval->epoch - $stime->epoch);
|
|
}
|
|
return 1.0;
|
|
}
|
|
|
|
sub _get_balance_interval_start_end {
|
|
my (%params) = @_;
|
|
my ($now,$start_mode,$last_etime,$interval_unit,$interval_value,$create) = @params{qw/now start_mode last_etime interval_unit interval_value create/};
|
|
|
|
my ($stime,$etime,$ctime) = (undef,undef,$now // NGCP::Panel::Utils::DateTime::current_local);
|
|
|
|
unless ($last_etime) {
|
|
$stime = _get_interval_start($ctime,$start_mode);
|
|
} else {
|
|
$stime = $last_etime->clone->add(seconds => 1);
|
|
}
|
|
|
|
if (defined $stime) { #lets crash in the create statement
|
|
if (_TOPUP_START_MODE ne $start_mode) {
|
|
$etime = _add_interval($stime,$interval_unit,$interval_value,_START_MODE_PRESERVE_EOM->{$start_mode} ? $create : undef)->subtract(seconds => 1);
|
|
} 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);
|
|
}
|
|
|
|
sub _get_resized_interval_end {
|
|
my (%params) = @_;
|
|
my ($ctime, $create, $start_mode) = @params{qw/ctime create_timestamp start_mode/};
|
|
if (_CREATE_START_MODE eq $start_mode) {
|
|
my $start_of_next_interval;
|
|
if ($ctime->day >= $create->day) {
|
|
#e.g. ctime=30. Jan 2015 17:53, create=30. -> 28. Feb 2015 00:00
|
|
$start_of_next_interval = $ctime->clone->set(day => $create->day)->truncate(to => 'day')->add(months => 1, end_of_month => 'limit');
|
|
} else {
|
|
my $last_day_of_month = NGCP::Panel::Utils::DateTime::last_day_of_month($ctime);
|
|
if ($create->day > $last_day_of_month) {
|
|
#e.g. ctime=28. Feb 2015 17:53, create=30. -> 30. Mar 2015 00:00
|
|
$start_of_next_interval = $ctime->clone->add(months => 1)->set(day => $create->day)->truncate(to => 'day');
|
|
} else {
|
|
#e.g. ctime=15. Jul 2015 17:53, create=16. -> 16. Jul 2015 00:00
|
|
$start_of_next_interval = $ctime->clone->set(day => $create->day)->truncate(to => 'day');
|
|
}
|
|
}
|
|
return $start_of_next_interval->subtract(seconds => 1);
|
|
} elsif (_1ST_START_MODE eq $start_mode) {
|
|
return $ctime->clone->truncate(to => 'month')->add(months => 1)->subtract(seconds => 1);
|
|
} elsif (_TOPUP_START_MODE eq $start_mode) {
|
|
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;
|
|
}
|
|
|
|
sub _get_interval_start {
|
|
my ($ctime,$start_mode) = @_;
|
|
if (_CREATE_START_MODE eq $start_mode) {
|
|
return $ctime->clone->truncate(to => 'day');
|
|
} elsif (_1ST_START_MODE eq $start_mode) {
|
|
return $ctime->clone->truncate(to => 'month');
|
|
} 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;
|
|
}
|
|
|
|
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 ('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');
|
|
#'preserve' mode correction:
|
|
if (defined $align_eom_dt
|
|
&& $to->day > $align_eom_dt->day
|
|
&& $from->day == NGCP::Panel::Utils::DateTime::last_day_of_month($from)) {
|
|
my $delta = NGCP::Panel::Utils::DateTime::last_day_of_month($align_eom_dt) - $align_eom_dt->day;
|
|
$to->set(day => NGCP::Panel::Utils::DateTime::last_day_of_month($to) - $delta);
|
|
}
|
|
return $to;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub get_actual_billing_mapping {
|
|
my %params = @_;
|
|
my ($c,$schema,$contract,$now) = @params{qw/c schema contract now/};
|
|
$schema //= $c->model('DB');
|
|
$now //= NGCP::Panel::Utils::DateTime::current_local;
|
|
my $dtf = $schema->storage->datetime_parser;
|
|
return $schema->resultset('billing_mappings_actual')->search({ contract_id => $contract->id },{bind => [ ( $dtf->format_datetime($now) ) x 2],})->first;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sub check_balance_interval {
|
|
my (%params) = @_;
|
|
my ($c,$resource,$err_code) = @params{qw/c resource err_code/};
|
|
|
|
my $schema = $c->model('DB');
|
|
if (!defined $err_code || ref $err_code ne 'CODE') {
|
|
$err_code = sub { return 0; };
|
|
}
|
|
|
|
unless(defined $resource->{balance_interval_unit} && defined $resource->{balance_interval_value}){
|
|
return 0 unless &{$err_code}("Balance interval definition required.",'balance_interval');
|
|
}
|
|
unless($resource->{balance_interval_value} > 0) {
|
|
return 0 unless &{$err_code}("Balance interval has to be greater than 0 interval units.",'balance_interval');
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub check_carry_over_mode {
|
|
my (%params) = @_;
|
|
my ($c,$resource,$err_code) = @params{qw/c resource err_code/};
|
|
|
|
my $schema = $c->model('DB');
|
|
if (!defined $err_code || ref $err_code ne 'CODE') {
|
|
$err_code = sub { return 0; };
|
|
}
|
|
|
|
if (defined $resource->{carry_over_mode} && $resource->{carry_over_mode} eq _CARRY_OVER_TIMELY_MODE) {
|
|
unless(defined $resource->{timely_duration_unit} && defined $resource->{timely_duration_value}){
|
|
return 0 unless &{$err_code}("'timely' interval definition required.",'timely_duration');
|
|
}
|
|
unless($resource->{balance_interval_value} > 0) {
|
|
return 0 unless &{$err_code}("'timely' interval has to be greater than 0 interval units.",'timely_duration');
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub check_underrun_lock_level {
|
|
my (%params) = @_;
|
|
my ($c,$resource,$err_code) = @params{qw/c resource err_code/};
|
|
|
|
my $schema = $c->model('DB');
|
|
if (!defined $err_code || ref $err_code ne 'CODE') {
|
|
$err_code = sub { return 0; };
|
|
}
|
|
|
|
if (defined $resource->{underrun_lock_level}) {
|
|
unless(defined $resource->{underrun_lock_threshold}){
|
|
return 0 unless &{$err_code}("If specifying an underun lock level, 'underrun_lock_threshold' is required.",'underrun_lock_threshold');
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub check_profiles {
|
|
my (%params) = @_;
|
|
my ($c,$resource,$mappings_to_create,$err_code) = @params{qw/c resource mappings_to_create err_code/};
|
|
|
|
my $schema = $c->model('DB');
|
|
if (!defined $err_code || ref $err_code ne 'CODE') {
|
|
$err_code = sub { return 0; };
|
|
}
|
|
|
|
my $mappings_counts = {};
|
|
return 0 unless prepare_package_profile_set(c => $c, resource => $resource, field => 'initial_profiles', mappings_to_create => $mappings_to_create, mappings_counts => $mappings_counts, err_code => $err_code);
|
|
if ($mappings_counts->{count_any_network} < 1) {
|
|
return 0 unless &{$err_code}("An initial billing profile mapping with no billing network is required.",'initial_profiles');
|
|
}
|
|
$mappings_counts = {};
|
|
return 0 unless prepare_package_profile_set(c => $c, resource => $resource, field => 'underrun_profiles', mappings_to_create => $mappings_to_create, mappings_counts => $mappings_counts, err_code => $err_code);
|
|
if ($mappings_counts->{count} > 0 && ! defined $resource->{underrun_profile_threshold}) {
|
|
return 0 unless &{$err_code}("If specifying underung profile mappings, 'underrun_profile_threshold' is required.",'underrun_profile_threshold');
|
|
}
|
|
$mappings_counts = {};
|
|
return 0 unless prepare_package_profile_set(c => $c, resource => $resource, field => 'topup_profiles', mappings_to_create => $mappings_to_create, mappings_counts => $mappings_counts, err_code => $err_code);
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub prepare_profile_package {
|
|
my (%params) = @_;
|
|
|
|
my ($c,$resource,$mappings_to_create,$err_code) = @params{qw/c resource mappings_to_create err_code/};
|
|
|
|
my $schema = $c->model('DB');
|
|
if (!defined $err_code || ref $err_code ne 'CODE') {
|
|
$err_code = sub { return 0; };
|
|
}
|
|
|
|
return 0 unless check_carry_over_mode(c => $c, resource => $resource, err_code => $err_code);
|
|
return 0 unless check_underrun_lock_level(c => $c, resource => $resource, err_code => $err_code);
|
|
|
|
return 0 unless check_profiles(c => $c, resource => $resource, mappings_to_create => $mappings_to_create, err_code => $err_code);
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub prepare_package_profile_set {
|
|
my (%params) = @_;
|
|
|
|
my ($c,$resource,$field,$mappings_to_create,$mappings_counts,$err_code) = @params{qw/c resource field mappings_to_create mappings_counts err_code/};
|
|
|
|
my $schema = $c->model('DB');
|
|
if (!defined $err_code || ref $err_code ne 'CODE') {
|
|
$err_code = sub { return 0; };
|
|
}
|
|
|
|
my $reseller_id = $resource->{reseller_id};
|
|
|
|
$resource->{$field} //= [];
|
|
|
|
if (ref $resource->{$field} ne 'ARRAY') {
|
|
return 0 unless &{$err_code}("Invalid field '$field'. Must be an array.",$field);
|
|
}
|
|
|
|
if (defined $mappings_counts) {
|
|
$mappings_counts->{count} //= 0;
|
|
$mappings_counts->{count_any_network} //= 0;
|
|
}
|
|
|
|
my $prepaid = 0;
|
|
my $mappings = delete $resource->{$field};
|
|
foreach my $mapping (@$mappings) {
|
|
if (ref $mapping ne 'HASH') {
|
|
return 0 unless &{$err_code}("Invalid element in array '$field'. Must be an object.",$field);
|
|
}
|
|
if (defined $mappings_to_create) {
|
|
my $profile = $schema->resultset('billing_profiles')->find($mapping->{profile_id});
|
|
unless($profile) {
|
|
return 0 unless &{$err_code}("Invalid 'profile_id' ($mapping->{profile_id}).",$field);
|
|
}
|
|
if ($profile->status eq 'terminated') {
|
|
return 0 unless &{$err_code}("Invalid 'profile_id' ($mapping->{profile_id}), already terminated.",$field);
|
|
}
|
|
if (defined $reseller_id && defined $profile->reseller_id && $reseller_id != $profile->reseller_id) { #($profile->reseller_id // -1)) {
|
|
return 0 unless &{$err_code}("The reseller of the profile package doesn't match the reseller of the billing profile (" . $profile->name . ").",$field);
|
|
}
|
|
if (defined $prepaid) {
|
|
if ($profile->prepaid != $prepaid) {
|
|
return 0 unless &{$err_code}("Switching between prepaid and post-paid billing profiles is not supported (" . $profile->name . ").",$field);
|
|
}
|
|
} else {
|
|
$prepaid = $profile->prepaid;
|
|
}
|
|
my $network;
|
|
if (defined $mapping->{network_id}) {
|
|
$network = $schema->resultset('billing_networks')->find($mapping->{network_id});
|
|
unless($network) {
|
|
return 0 unless &{$err_code}("Invalid 'network_id'.",$field);
|
|
}
|
|
if (defined $reseller_id && defined $network->reseller_id && $reseller_id != $network->reseller_id) { #($network->reseller_id // -1)) {
|
|
return 0 unless &{$err_code}("The reseller of the profile package doesn't match the reseller of the billing network (" . $network->name . ").",$field);
|
|
}
|
|
}
|
|
push(@$mappings_to_create,{
|
|
profile_id => $profile->id,
|
|
network_id => (defined $network ? $network->id : undef),
|
|
discriminator => field_to_discriminator($field),
|
|
});
|
|
}
|
|
if (defined $mappings_counts) {
|
|
$mappings_counts->{count} += 1;
|
|
$mappings_counts->{count_any_network} += 1 unless $mapping->{network_id};
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub field_to_discriminator {
|
|
my ($field) = @_;
|
|
return _DISCRIMINATOR_MAP->{$field};
|
|
}
|
|
|
|
sub get_contract_count_stmt {
|
|
return "select count(distinct c.id) from `billing`.`contracts` c where c.`profile_package_id` = `me`.`id` and c.status != 'terminated'";
|
|
}
|
|
|
|
sub _get_profile_set_group_stmt {
|
|
my ($discriminator) = @_;
|
|
my $grp_stmt = "group_concat(if(bn.`name` is null,bp.`name`,concat(bp.`name`,'/',bn.`name`)) separator ', ')";
|
|
my $grp_len = 30;
|
|
return "select if(length(".$grp_stmt.") > ".$grp_len.", concat(left(".$grp_stmt.", ".$grp_len."), '...'), ".$grp_stmt.") from `billing`.`package_profile_sets` pps join `billing`.`billing_profiles` bp on bp.`id` = pps.`profile_id` left join `billing`.`billing_networks` bn on bn.`id` = pps.`network_id` where pps.`package_id` = `me`.`id` and pps.`discriminator` = '" . $discriminator . "'";
|
|
}
|
|
|
|
sub get_datatable_cols {
|
|
|
|
my ($c) = @_;
|
|
return (
|
|
{ name => "contract_cnt", "search" => 0, "title" => $c->loc("Used (contracts)"), },
|
|
{ name => 'initial_profiles_grp', accessor => "initial_profiles_grp", search => 0, title => $c->loc('Initial Profiles'),
|
|
literal_sql => _get_profile_set_group_stmt(INITIAL_PROFILE_DISCRIMINATOR) },
|
|
{ name => 'underrun_profiles_grp', accessor => "underrun_profiles_grp", search => 0, title => $c->loc('Underrun Profiles'),
|
|
literal_sql => _get_profile_set_group_stmt(UNDERRUN_PROFILE_DISCRIMINATOR) },
|
|
{ name => 'topup_profiles_grp', accessor => "topup_profiles_grp", search => 0, title => $c->loc('Top-up Profiles'),
|
|
literal_sql => _get_profile_set_group_stmt(TOPUP_PROFILE_DISCRIMINATOR) },
|
|
|
|
{ name => 'profile_name', accessor => "profiles_srch", search => 1, join => { profiles => 'billing_profile' },
|
|
literal_sql => 'billing_profile.name' },
|
|
{ name => 'network_name', accessor => "network_srch", search => 1, join => { profiles => 'billing_network' },
|
|
literal_sql => 'billing_network.name' },
|
|
);
|
|
|
|
}
|
|
|
|
1; |