ngcp-panel/lib/NGCP/Panel/Utils/BillingMappings.pm

702 lines
31 KiB

package NGCP::Panel::Utils::BillingMappings;
use strict;
use warnings;
use Sipwise::Base;
use NGCP::Panel::Utils::DateTime qw();
use DateTime::Format::Strptime qw();
#my $_c_global;
#my $_commit = \&DBI::db::commit;
#*DBI::db::commit = sub {
# $c->log->debug() if $c_global;
# return $_commit(@_);
#}
#my $_rollback = \&DBI::db::rollback;
#*DBI::db::rollback = sub {
# $c->log->debug() if $c_global;
# return $_rollback(@_);
#}
sub append_billing_mappings {
my %params = @_;
my ($c,$schema,$contract,$now,$mappings_to_create,$delete_mappings) = @params{qw/c schema contract now mappings_to_create delete_mappings/};
return unless $mappings_to_create;
$schema //= $c->model('DB');
my $dtf = $schema->storage->datetime_parser;
my $mappings = '';
foreach my $mapping (@$mappings_to_create) {
$mappings .= (defined $mapping->{start_date} ? $dtf->format_datetime($mapping->{start_date}) : '') . ',';
$mappings .= (defined $mapping->{end_date} ? $dtf->format_datetime($mapping->{end_date}) : '') . ',';
$mappings .= (defined $mapping->{billing_profile_id} ? $mapping->{billing_profile_id} : '') . ',';
$mappings .= (defined $mapping->{network_id} ? $mapping->{network_id} : '') . ',';
$mappings .= ';'; #last = 1 by default
}
$c->log->debug('create contract id ' . $contract->id . " billing mappings via proc: $mappings") if $c;
$c->model('DB')->txn_do(sub {
$c->model('DB')->storage->dbh->do('call billing.schedule_contract_billing_profile_network(?,?,?)',undef,
$contract->id,
((defined $now and $delete_mappings) ? $dtf->format_datetime($now) : undef),
$mappings
);
});
#$c->model('DB')->storage->dbh->do('call billing.schedule_contract_billing_profile_network(?,?,?)',undef,
# $contract->id,
# ((defined $now and $delete_mappings) ? $dtf->format_datetime($now) : undef),
# $mappings
#);
#my $contract_id = $contract->id;
#$schema->storage->dbh_do(sub {
# my ($storage, $dbh, @args) = @_;
# local $dbh->{AutoCommit} = 0;
# $dbh->do('call billing.schedule_contract_billing_profile_network(?,?,?)',undef,
# $contract_id,
# ((defined $now and $delete_mappings) ? $dtf->format_datetime($now) : undef),
# $mappings
# );
#});
}
sub get_actual_billing_mapping {
my %params = @_;
my ($c,$schema,$contract,$now) = @params{qw/c schema contract now/};
$schema //= $c->model('DB');
if ($now) {
$c->log->debug('local timezone is ' . DateTime::TimeZone->new( name => 'local' )->name()) if $c;
$now = NGCP::Panel::Utils::DateTime::set_local_tz($now);
} else {
$now = NGCP::Panel::Utils::DateTime::current_local;
}
my $contract_create = NGCP::Panel::Utils::DateTime::set_local_tz($contract->create_timestamp // $contract->modify_timestamp);
#my $dtf = $schema->storage->datetime_parser;
$now = $contract_create if $now < $contract_create; #if there is no mapping starting with or before $now, it would returns the mapping with max(id):
#$start = NGCP::Panel::Utils::DateTime::convert_tz($start,$tz->name,'local',$c);
my $effective_start_date = $schema->resultset('contracts_billing_profile_network_schedule')->search({
'profile_network.contract_id' => $contract->id,
effective_start_time => { '<=' => $now->epoch },
'profile_network.base' => 1,
},{
join => 'profile_network',
})->get_column('effective_start_time')->max;
if (defined $effective_start_date) {
my $bm = $schema->resultset('contracts_billing_profile_network_schedule')->search({
contract_id => $contract->id,
effective_start_time => $effective_start_date,
base => 1,
},{
join => 'profile_network',
})->first;
$bm = $bm->profile_network if $bm;
$c->log->debug("contract id " . $contract->id . " billing profile at $now (epoch = " . $now->epoch . ", tz = ".$now->time_zone.") is " .
$bm->billing_profile->name . " (effective start date = $effective_start_date)") if $c;
return $bm;
} else {
$c->log->error("no billing profile for contract id " . $contract->id . " at $now (" . $now->epoch . ")") if $c;
}
}
sub get_actual_billing_mapping_stmt {
my %params = @_;
my ($c,$schema,$contract,$now,$projection,$contract_id_alias) = @params{qw/c schema contract now projection contract_id_alias/};
$schema //= $c->model('DB');
if ($now) {
$now = NGCP::Panel::Utils::DateTime::set_local_tz($now);
} else {
$now = NGCP::Panel::Utils::DateTime::current_local;
}
$projection //= 'actual_billing_mapping.id';
$contract_id_alias //= 'me.id';
return sprintf(<<EOS
select
%s
from (select
contract_id,
max(effective_start_time) as effective_start_date
from billing.contracts_billing_profile_network_schedule cbpns join billing.contracts_billing_profile_network cbpn on cbpns.profile_network_id = cbpn.id
where
cbpns.effective_start_time <= %s
and cbpn.base = 1
group by cbpn.contract_id) as esd
join billing.contracts_billing_profile_network actual_billing_mapping on actual_billing_mapping.contract_id = esd.contract_id
join billing.contracts_billing_profile_network_schedule cbpns on cbpns.profile_network_id = actual_billing_mapping.id
join billing.billing_profiles billing_profile on actual_billing_mapping.billing_profile_id = billing_profile.id
left join billing.billing_networks billing_network on actual_billing_mapping.billing_network_id = billing_network.id
where esd.contract_id = %s and cbpns.effective_start_time = esd.effective_start_date and actual_billing_mapping.base = 1
EOS
, $projection, $now->epoch, $contract_id_alias);
}
sub prepare_billing_mappings {
my (%params) = @_;
my ($c,
$resource,
$old_resource,
$mappings_to_create,
$now,
$delete_mappings,
$err_code,
$billing_profile_field,
$billing_profiles_field,
$profile_package_field,
$billing_profile_definition_field) = @params{qw/
c
resource
old_resource
mappings_to_create
now
delete_mappings
err_code
billing_profile_field
billing_profiles_field
profile_package_field
billing_profile_definition_field
/};
my $schema = $c->model('DB');
if (!defined $err_code || ref $err_code ne 'CODE') {
$err_code = sub { return 0; };
}
my $profile_def_mode = $resource->{billing_profile_definition} // 'id';
$now //= NGCP::Panel::Utils::DateTime::current_local;
my $reseller_id = undef;
my $is_customer = 1;
if (defined $resource->{contact_id}) {
my $contact = $schema->resultset('contacts')->find($resource->{contact_id});
if ($contact) {
$reseller_id = $contact->reseller_id; #($contact->reseller_id // -1);
$is_customer = defined $reseller_id;
}
}
# my $product_id = undef; #any subsequent create will fail without product_id
#my $prepaid = undef;
my $billing_profile_id = undef;
if (defined $old_resource) {
# TODO: what about changed product, do we allow it?
my $billing_mapping;
if (exists $old_resource->{billing_mapping}) {
$billing_mapping = $old_resource->{billing_mapping}; #$schema->resultset('billing_mappings')->find($old_resource->{billing_mapping_id});
} elsif (exists $old_resource->{id}) {
$billing_mapping = get_actual_billing_mapping(schema => $schema,
contract => $schema->resultset('contracts')->find($old_resource->{id}),
now => $now);
#} else {
# return 0 unless &{$err_code}("No billing mapping or contract defined");
}
# $product_id = $billing_mapping->contract->product->id;
#$prepaid = $billing_mapping->billing_profile->prepaid;
$billing_profile_id = $billing_mapping->billing_profile->id;
# } else {
# if (exists $resource->{type} || exists $c->stash->{type}) {
# my $productclass = (exists $c->stash->{type} ? $c->stash->{type} : $resource->{type});
# my $product = $schema->resultset('products')->search_rs({ class => $productclass })->first;
# if ($product) {
# $product_id = $product->id;
# }
# } elsif (exists $resource->{product_id}) {
# $product_id = $resource->{product_id};
# }
}
if ('id' eq $profile_def_mode) {
my $delete = undef;
if (defined $old_resource) { #update
if (defined $resource->{billing_profile_id}) {
if ($billing_profile_id != $resource->{billing_profile_id}) {
#change profile:
$delete = 0; #1; #delete future mappings?
my $entities = {};
return 0 unless _check_profile_network(c => $c, reseller_id => $reseller_id, err_code => $err_code, entities => $entities,
resource => $resource,
profile_id_field => 'billing_profile_id',
field => $billing_profile_field,
);
my ($profile) = @$entities{qw/profile/};
push(@$mappings_to_create,{billing_profile_id => $profile->id,
network_id => undef,
# product_id => $product_id,
start_date => $now,
end_date => undef,
});
} else {
#not changed, don't touch mappings
$delete = 0;
}
} else {
#undef profile is not allowed
$delete = 0;
my $entities = {};
return 0 unless _check_profile_network(c => $c, reseller_id => $reseller_id, err_code => $err_code, entities => $entities,
resource => $resource,
profile_id_field => 'billing_profile_id',
field => $billing_profile_field,
);
}
} else { #create
$delete = 1; #for the sake of completeness
my $entities = {};
return 0 unless _check_profile_network(c => $c, reseller_id => $reseller_id, err_code => $err_code, entities => $entities,
resource => $resource,
profile_id_field => 'billing_profile_id',
field => $billing_profile_field,
);
my ($profile) = @$entities{qw/profile/};
push(@$mappings_to_create,{billing_profile_id => $profile->id,
network_id => undef,
# product_id => $product_id,
#we don't change the former behaviour in update situations:
start_date => undef,
end_date => undef,
});
}
if (defined $delete_mappings && ref $delete_mappings eq 'SCALAR') {
$$delete_mappings = $delete;
}
delete $resource->{profile_package_id};
} elsif ('profiles' eq $profile_def_mode) {
if (!defined $resource->{billing_profiles}) {
$resource->{billing_profiles} //= [];
}
if (ref $resource->{billing_profiles} ne "ARRAY") {
return 0 unless &{$err_code}("Invalid field 'billing_profiles'. Must be an array.",$billing_profiles_field);
}
my %interval_type_counts = ( open => 0, open_any_network => 0, 'open end' => 0, 'open start' => 0, 'start-end' => 0 );
my $dtf = $schema->storage->datetime_parser;
foreach my $mapping (@{$resource->{billing_profiles}}) {
if (ref $mapping ne "HASH") {
return 0 unless &{$err_code}("Invalid element in array 'billing_profiles'. Must be an object.",$billing_profiles_field);
}
my $entities = {};
return 0 unless _check_profile_network(c => $c, reseller_id => $reseller_id, err_code => $err_code, entities => $entities,
resource => $mapping,
field => $billing_profiles_field,
profile_id_field => 'profile_id',
network_id_field => 'network_id',
);
my ($profile,$network) = @$entities{qw/profile network/};
#if (defined $prepaid) {
# if ($profile->prepaid != $prepaid) {
# return 0 unless &{$err_code}("Future switching between prepaid and post-paid billing profiles is not supported (" . $profile->name . ").",$billing_profiles_field);
# }
#} else {
# $prepaid = $profile->prepaid;
#}
# TODO: what about changed product, do we allow it?
#my $product_class = delete $mapping->{type};
#unless( (defined $product_class ) && ($product_class eq "sipaccount" || $product_class eq "pbxaccount") ) {
# return 0 unless &{$err_code}("Mandatory 'type' parameter is empty or invalid, must be 'sipaccount' or 'pbxaccount'.");
#}
#my $product = $schema->resultset('products')->search_res({ class => $product_class })->first;
#unless($product) {
# return 0 unless &{$err_code}("Invalid 'type'.");
#} else {
# # add product_id just for form check (not part of the actual contract item)
# # and remove it after the check
# $mapping->{product_id} = $product->id;
#}
my $start = (defined $mapping->{start} ? NGCP::Panel::Utils::DateTime::from_string($mapping->{start}) : undef);
my $stop = (defined $mapping->{stop} ? NGCP::Panel::Utils::DateTime::from_string($mapping->{stop}) : undef);
if (!defined $start && !defined $stop) { #open interval
$interval_type_counts{open} += 1;
$interval_type_counts{open_any_network} += 1 unless $network;
} elsif (defined $start && !defined $stop) { #open end interval
my $start_str = $dtf->format_datetime($start);
if ($start <= $now) {
return 0 unless &{$err_code}("'start' timestamp ($start_str) is not in future.",$billing_profiles_field);
}
#if (exists $start_dupes{$start_str}) {
# $start_dupes{$start_str} += 1;
# return 0 unless &{$err_code}("Identical 'start' timestamps ($start_str) not allowed.");
#} else {
# $start_dupes{$start_str} = 1;
#}
$interval_type_counts{'open end'} += 1;
} elsif (!defined $start && defined $stop) { #open start interval
my $stop_str = $dtf->format_datetime($stop);
return 0 unless &{$err_code}("Interval with 'stop' timestamp ($stop_str) but no 'start' timestamp specified.",$billing_profiles_field);
$interval_type_counts{'open start'} //= 0;
$interval_type_counts{'open start'} += 1;
} else { #start-end interval
my $start_str = $dtf->format_datetime($start);
if ($start <= $now) {
return 0 unless &{$err_code}("'start' timestamp ($start_str) is not in future.",$billing_profiles_field);
}
my $stop_str = $dtf->format_datetime($stop);
if ($start >= $stop) {
return 0 unless &{$err_code}("'start' timestamp ($start_str) has to be before 'stop' timestamp ($stop_str).",$billing_profiles_field);
}
#if (exists $start_dupes{$start_str}) {
# $start_dupes{$start_str} += 1;
# return 0 unless &{$err_code}("Identical 'start' timestamps ($start_str) not allowed.");
#} else {
# $start_dupes{$start_str} = 1;
#}
$interval_type_counts{'start-end'} += 1;
}
push(@$mappings_to_create,{
billing_profile_id => $profile->id,
network_id => ($is_customer && defined $network ? $network->id : undef),
# product_id => $product_id,
start_date => $start,
end_date => $stop,
});
}
if (!defined $old_resource && $interval_type_counts{'open_any_network'} < 1) {
return 0 unless &{$err_code}("An initial interval without 'start' and 'stop' timestamps and no billing network is required.",$billing_profiles_field);
} elsif (defined $old_resource && $interval_type_counts{'open'} > 0) {
return 0 unless &{$err_code}("Adding intervals without 'start' and 'stop' timestamps is not allowed.",$billing_profiles_field);
}
if (defined $delete_mappings && ref $delete_mappings eq 'SCALAR') {
$$delete_mappings = 1; #always clear future mappings to place new ones
}
delete $resource->{profile_package_id};
} elsif ('package' eq $profile_def_mode) {
if (!$is_customer) {
return 0 unless &{$err_code}("Setting a profile package is supported for customer contracts only.",$billing_profile_definition_field);
}
my $delete = undef;
if (defined $old_resource) { #update
if (defined $old_resource->{profile_package_id} && !defined $resource->{profile_package_id}) {
#clear package: don't touch billing mappings (just clear profile package)
$delete = 0;
} elsif (!defined $old_resource->{profile_package_id} && defined $resource->{profile_package_id}) {
#set package: apply initial mappings
$delete = 0; #1; #delete future mappings?
my $entities = {};
return 0 unless _check_profile_package(c => $c, reseller_id => $reseller_id, err_code => $err_code, entities => $entities,
package_id => $resource->{profile_package_id},
field => $profile_package_field,
);
my ($package) = @$entities{qw/package/};
foreach my $mapping ($package->initial_profiles->all) {
push(@$mappings_to_create,{ #assume not terminated,
billing_profile_id => $mapping->profile_id,
network_id => ($is_customer ? $mapping->network_id : undef),
# product_id => $product_id,
start_date => $now,
end_date => undef,
});
}
} elsif (defined $old_resource->{profile_package_id} && defined $resource->{profile_package_id}) {
if ($old_resource->{profile_package_id} != $resource->{profile_package_id}) {
#change package: apply initial mappings
$delete = 0; #1; #delete future mappings?
my $entities = {};
return 0 unless _check_profile_package(c => $c, reseller_id => $reseller_id, err_code => $err_code, entities => $entities,
package_id => $resource->{profile_package_id},
field => $profile_package_field,
);
my ($package) = @$entities{qw/package/};
foreach my $mapping ($package->initial_profiles->all) {
push(@$mappings_to_create,{ #assume not terminated,
billing_profile_id => $mapping->profile_id,
network_id => ($is_customer ? $mapping->network_id : undef),
# product_id => $product_id,
start_date => $now,
end_date => undef,
});
}
} else {
#package unchanged: don't touch billing mappings
$delete = 0;
}
} else {
#package unchanged (null): don't touch billing mappings
$delete = 0;
}
} else { #create
$delete = 1; #for the sake of completeness
my $entities = {};
return 0 unless _check_profile_package(c => $c, reseller_id => $reseller_id, err_code => $err_code, entities => $entities,
package_id => $resource->{profile_package_id},
field => $profile_package_field,
);
my ($package) = @$entities{qw/package/};
foreach my $mapping ($package->initial_profiles->all) {
push(@$mappings_to_create,{ #assume not terminated,
billing_profile_id => $mapping->profile_id,
network_id => ($is_customer ? $mapping->network_id : undef),
# product_id => $product_id,
start_date => undef, #$now,
end_date => undef,
});
}
}
if (defined $delete_mappings && ref $delete_mappings eq 'SCALAR') {
$$delete_mappings = $delete;
}
} else {
return 0 unless &{$err_code}("Invalid 'billing_profile_definition'.",$billing_profile_definition_field);
}
delete $resource->{billing_profile_id};
delete $resource->{billing_profiles};
delete $resource->{billing_profile_definition};
return 1;
}
sub check_prepaid_profiles_exist {
my (%params) = @_;
my ($c, $mappings_to_create) = @params{qw/c mappings_to_create/};
my $schema = $c->model('DB');
foreach my $billing_profile_info (@$mappings_to_create) {
my $billing_profile = $schema->resultset('billing_profiles')->find($billing_profile_info->{billing_profile_id});
if ($billing_profile && $billing_profile->prepaid) {
#later we can put here all prepaid billing profiles in a array ref, if we will want to provide more informative error
return $billing_profile_info->{billing_profile_id};
}
}
return 0;
}
sub _check_profile_network {
my (%params) = @_;
my ($c,
$res,
$profile_id_field,
$network_id_field,
$field,
$reseller_id,
$err_code,
$entities) = @params{qw/
c
resource
profile_id_field
network_id_field
field
reseller_id
err_code entities
/};
my $schema = $c->model('DB');
if (!defined $err_code || ref $err_code ne 'CODE') {
$err_code = sub { return 0; };
}
unless(defined $res->{$profile_id_field}) {
return 0 unless &{$err_code}("Invalid '$profile_id_field', not defined.",$field);
}
my $profile = $schema->resultset('billing_profiles')->find($res->{$profile_id_field});
unless($profile) {
return 0 unless &{$err_code}("Invalid '$profile_id_field' ($res->{$profile_id_field}).",$field);
}
if ($profile->status eq 'terminated') {
return 0 unless &{$err_code}("Invalid '$profile_id_field' ($res->{$profile_id_field}), 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 contact doesn't match the reseller of the billing profile (" . $profile->name . ").",$field);
}
my $network;
if (defined $network_id_field && defined $res->{$network_id_field}) {
$network = $schema->resultset('billing_networks')->find($res->{$network_id_field});
unless($network) {
return 0 unless &{$err_code}("Invalid '$network_id_field' ($res->{$network_id_field}).",$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 contact doesn't match the reseller of the billing network (" . $network->name . ").",$field);
}
}
if (defined $entities and ref $entities eq 'HASH') {
$entities->{profile} = $profile;
$entities->{network} = $network;
}
return 1;
}
sub _check_profile_package {
my (%params) = @_;
my ($c,$res,$package_id,$reseller_id,$field,$err_code,$entities) = @params{qw/c resource package_id reseller_id field err_code entities/};
my $schema = $c->model('DB');
if (!defined $err_code || ref $err_code ne 'CODE') {
$err_code = sub { return 0; };
}
unless(defined $package_id) {
return 0 unless &{$err_code}("Invalid 'profile_package_id', not defined.",$field);
}
my $package = $schema->resultset('profile_packages')->find($package_id);
unless($package) {
return 0 unless &{$err_code}("Invalid 'profile_package_id'.",$field);
}
if (defined $reseller_id && defined $package->reseller_id && $reseller_id != $package->reseller_id) {
return 0 unless &{$err_code}("The reseller of the contact doesn't match the reseller of the profile package (" . $package->name . ").",$field);
}
if (defined $entities and ref $entities eq 'HASH') {
$entities->{package} = $package;
}
return 1;
}
sub resource_from_future_mappings {
my ($contract) = @_;
return resource_from_mappings($contract,1);
}
sub resource_from_mappings {
my ($contract,$future_only) = @_;
my $is_customer = (defined $contract->contact->reseller_id ? 1 : 0);
my @mappings_resource = ();
my $datetime_fmt = DateTime::Format::Strptime->new(
pattern => '%F %T',
); #validate_forms uses RFC3339 otherwise, which contains the tz offset part
foreach my $mapping (billing_mappings_ordered($future_only ? future_billing_mappings($contract->billing_mappings) : $contract->billing_mappings)->all) {
my $profile = $mapping->billing_profile;
if ($profile and 'terminated' eq $profile->status) {
next;
}
my $network = $mapping->network;
if ($network and 'terminated' eq $network->status) {
next;
}
my %m = $mapping->get_inflated_columns;
delete $m{id};
$m{start} = delete $m{start_date};
$m{stop} = delete $m{end_date};
$m{start} = $datetime_fmt->format_datetime($m{start}) if defined $m{start};
$m{stop} = $datetime_fmt->format_datetime($m{stop}) if defined $m{stop};
$m{effective_start_time} = $datetime_fmt->format_datetime(delete $m{effective_start_date});
$m{profile_id} = delete $m{billing_profile_id};
delete $m{contract_id};
delete $m{product_id};
delete $m{network_id} unless $is_customer;
push(@mappings_resource,\%m);
}
return \@mappings_resource;
}
sub billing_mappings_ordered {
my ($rs,$now,$actual_bm) = @_;
my $dtf;
$dtf = $rs->result_source->schema->storage->datetime_parser if defined $now;
my @select = ();
if ($now) {
push(@select,{ '' => \[ 'if(`me`.`start_date` is null,0,`me`.`start_date` > ?)', $dtf->format_datetime($now) ], -as => 'is_future' });
}
if ($actual_bm) {
#push(@select,{ '' => \[ '`me`.`id` = ?', $actual_bmid ], -as => 'is_actual' });
push(@select,{ '' => \[ '`me`.`id` = ?', $actual_bm->id ], -as => 'is_actual' });
}
return $rs->search_rs(
{},
{ order_by => { '-asc' => ['effective_start_date', 'id']},
(scalar @select == 1 ? ('+select' => $select[0]) : ()),
(scalar @select > 1 ? ('+select' => \@select) : ()),
});
}
sub future_billing_mappings {
my ($rs,$now) = @_;
$now //= NGCP::Panel::Utils::DateTime::current_local;
return $rs->search_rs({start_date => { '>' => $now },});
}
sub get_billingmappings_timeline_data {
my ($c,$contract,$range,$stacked) = @_;
unless ($range) {
$range = eval { $c->req->body_data; };
if ($@) {
$c->log->error('error decoding timeline json request: ' . $@);
}
}
my $start;
$start = NGCP::Panel::Utils::DateTime::from_string($range->{start}) if $range->{start};
my $end;
$end = NGCP::Panel::Utils::DateTime::from_string($range->{end}) if $range->{end};
$c->log->debug("timeline range $start - $end");
#the max start date (of mappings with NULL end date) less than
#the visible range end will become the range start:
my $max_start_date = $contract->billing_mappings->search({ ## no critic (ProhibitCommaSeparatedStatements)
($end ? (start_date => [ -or =>
{ '<=' => $end },
{ '=' => undef },
]) : ()),
end_date => { '=' => undef },
},{
order_by => { '-desc' => ['start_date', 'me.id']}, #NULL start dates at last
})->first;
#lower the range start, if required:
if ($max_start_date) {
if ($max_start_date->start_date) {
$start = $max_start_date->start_date if (not $start or $max_start_date->start_date < $start);
} else {
$start = $max_start_date->start_date;
}
}
my @timeline_events;
#if ($stacked) {
my $res = $contract->billing_mappings->search({ ## no critic (ProhibitCommaSeparatedStatements)
($end ? (start_date => ($start ? [ -and => {
'<=' => $end },{ #hide mappings beginning after range end
'>=' => $start #and beginning before range start (max_start_date).
},] : [ -or => { #if there is a mapping with NULL start only,
'<=' => $end },{ #include all mapping beginning before range end.
'=' => undef
},])) : ()),
},{
order_by => { '-asc' => ['start_date', 'me.id']},
prefetch => [ 'billing_profile' , 'network' ]
});
@timeline_events = map {
{ $_->get_columns,
billing_profile => { ($_->billing_profile ? ( name => $_->billing_profile->name, ) : ()) },
network => { ($_->network ? ( name => $_->network->name, ) : ()) },
};
} $res->all;
#} else {
# my $res = $c->model('DB')->resultset('contracts_billing_profile_network_schedule')->search({ ## no critic (ProhibitCommaSeparatedStatements)
# ($end ? (start_date => ($start ? [ -and => {
# '<=' => $end },{ #hide mappings beginning after range end
# '>=' => $start #and beginning before range start (max_start_date).
# },] : [ -or => { #if there is a mapping with NULL start only,
# '<=' => $end },{ #include all mapping beginning before range end.
# '=' => undef
# },])) : ()),
# },{
# join => { 'profile_network' => [ 'billing_profile', 'billing_network' ], },
# order_by => { '-asc' => ['effective_start_date', 'profile_network_id' ]},
# prefetch => [ 'billing_profile' , 'billing_network' ]
# });
# @timeline_events = map {
# { $_->get_columns,
# billing_profile => { ($_->billing_profile ? ( name => $_->billing_profile->name, ) : ()) },
# network => { ($_->network ? ( name => $_->network->name, ) : ()) },
# };
# } $res->all;
#}
return \@timeline_events;
}
1;