|
|
|
|
@ -21,6 +21,11 @@ my $log_ident = 'rate-o-mat';
|
|
|
|
|
my $log_facility = 'daemon';
|
|
|
|
|
my $log_opts = 'ndely,cons,pid,nowait';
|
|
|
|
|
|
|
|
|
|
# if split_peak_parts is set to true, rate-o-mat will create a separate
|
|
|
|
|
# CDR every time a peak time border is crossed for either the customer,
|
|
|
|
|
# the reseller or the carrier billing profile.
|
|
|
|
|
my $split_peak_parts = 0;
|
|
|
|
|
|
|
|
|
|
# if the LNP database is used not just for LNP, but also for on-net
|
|
|
|
|
# billing, special routing or similar things, this should be set to
|
|
|
|
|
# better guess the correct LNP provider ID when selecting ported numbers
|
|
|
|
|
@ -53,6 +58,8 @@ my $DupDB_Port = $ENV{RATEOMAT_DUPLICATE_DB_PORT} ? int $ENV{RATEOMAT_DUPLICATE_
|
|
|
|
|
my $DupDB_User = $ENV{RATEOMAT_DUPLICATE_DB_USER};
|
|
|
|
|
my $DupDB_Pass = $ENV{RATEOMAT_DUPLICATE_DB_PASS};
|
|
|
|
|
|
|
|
|
|
$split_peak_parts = $ENV{RATEOMAT_SPLIT_PEAK_PARTS} // $split_peak_parts;
|
|
|
|
|
|
|
|
|
|
########################################################################
|
|
|
|
|
|
|
|
|
|
sub main;
|
|
|
|
|
@ -74,6 +81,7 @@ my $sth_offpeak_weekdays;
|
|
|
|
|
my $sth_offpeak_special;
|
|
|
|
|
my $sth_unrated_cdrs;
|
|
|
|
|
my $sth_update_cdr;
|
|
|
|
|
my $sth_create_cdr_fragment;
|
|
|
|
|
my $sth_provider_info;
|
|
|
|
|
my $sth_reseller_info;
|
|
|
|
|
my $sth_get_cbalances;
|
|
|
|
|
@ -478,17 +486,36 @@ EOS
|
|
|
|
|
"destination_carrier_cost = ?, destination_reseller_cost = ?, destination_customer_cost = ?, ".
|
|
|
|
|
"destination_carrier_free_time = ?, destination_reseller_free_time = ?, destination_customer_free_time = ?, ".
|
|
|
|
|
"destination_carrier_billing_fee_id = ?, destination_reseller_billing_fee_id = ?, destination_customer_billing_fee_id = ?, ".
|
|
|
|
|
"destination_carrier_billing_zone_id = ?, destination_reseller_billing_zone_id = ?, destination_customer_billing_zone_id = ? ".
|
|
|
|
|
"destination_carrier_billing_zone_id = ?, destination_reseller_billing_zone_id = ?, destination_customer_billing_zone_id = ?, ".
|
|
|
|
|
"frag_carrier_onpeak = ?, frag_reseller_onpeak = ?, frag_customer_onpeak = ?, is_fragmented = ?, ".
|
|
|
|
|
"duration = ? ".
|
|
|
|
|
"WHERE id = ?"
|
|
|
|
|
) or FATAL "Error preparing update cdr statement: ".$acctdbh->errstr;
|
|
|
|
|
|
|
|
|
|
if ($split_peak_parts) {
|
|
|
|
|
my @exclude_fragment_fields = qw(start_time duration is_fragmented);
|
|
|
|
|
my %exclude_fragment_fields = map {$_,1} @exclude_fragment_fields;
|
|
|
|
|
my @fragment_fields = grep {!$exclude_fragment_fields{$_}} @cdr_fields;
|
|
|
|
|
$sth_create_cdr_fragment = $acctdbh->prepare(
|
|
|
|
|
"INSERT INTO accounting.cdr
|
|
|
|
|
(".
|
|
|
|
|
join(',', @fragment_fields, @exclude_fragment_fields).
|
|
|
|
|
")
|
|
|
|
|
SELECT ".
|
|
|
|
|
join(',', @fragment_fields).",
|
|
|
|
|
start_time + ?,duration - ?,1
|
|
|
|
|
FROM accounting.cdr
|
|
|
|
|
WHERE id = ?
|
|
|
|
|
") or FATAL "Error preparing create cdr fragment statement: ".$acctdbh->errstr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$sth_provider_info = $billdbh->prepare(
|
|
|
|
|
"SELECT p.class, bm.billing_profile_id ".
|
|
|
|
|
"FROM billing.products p, billing.billing_mappings bm ".
|
|
|
|
|
"WHERE bm.contract_id = ? AND bm.product_id = p.id ".
|
|
|
|
|
"AND (bm.start_date IS NULL OR bm.start_date <= FROM_UNIXTIME(?)) ".
|
|
|
|
|
"AND (bm.end_date IS NULL OR bm.end_date >= FROM_UNIXTIME(?)) ".
|
|
|
|
|
"ORDER BY bm.start_date, bm.id DESC ".
|
|
|
|
|
"ORDER BY bm.start_date DESC, bm.id DESC ".
|
|
|
|
|
"LIMIT 1"
|
|
|
|
|
) or FATAL "Error preparing provider info statement: ".$billdbh->errstr;
|
|
|
|
|
|
|
|
|
|
@ -502,7 +529,7 @@ EOS
|
|
|
|
|
"AND r.contract_id = bm.contract_id ".
|
|
|
|
|
"AND (bm.start_date IS NULL OR bm.start_date <= FROM_UNIXTIME(?)) ".
|
|
|
|
|
"AND (bm.end_date IS NULL OR bm.end_date >= FROM_UNIXTIME(?)) ".
|
|
|
|
|
"ORDER BY bm.start_date, bm.id DESC ".
|
|
|
|
|
"ORDER BY bm.start_date DESC, bm.id DESC ".
|
|
|
|
|
"LIMIT 1"
|
|
|
|
|
) or FATAL "Error preparing reseller info statement: ".$billdbh->errstr;
|
|
|
|
|
|
|
|
|
|
@ -1478,6 +1505,8 @@ sub update_cdr
|
|
|
|
|
$cdr->{destination_carrier_free_time}, $cdr->{destination_reseller_free_time}, $cdr->{destination_customer_free_time},
|
|
|
|
|
$cdr->{destination_carrier_billing_fee_id}, $cdr->{destination_reseller_billing_fee_id}, $cdr->{destination_customer_billing_fee_id},
|
|
|
|
|
$cdr->{destination_carrier_billing_zone_id}, $cdr->{destination_reseller_billing_zone_id}, $cdr->{destination_customer_billing_zone_id},
|
|
|
|
|
$cdr->{frag_carrier_onpeak}, $cdr->{frag_reseller_onpeak}, $cdr->{frag_customer_onpeak},
|
|
|
|
|
$cdr->{is_fragmented} // 0, $cdr->{duration},
|
|
|
|
|
$cdr->{id})
|
|
|
|
|
or FATAL "Error executing update cdr statement: ".$sth->errstr;
|
|
|
|
|
|
|
|
|
|
@ -1545,8 +1574,6 @@ sub get_call_cost
|
|
|
|
|
my $r_onpeak = shift;
|
|
|
|
|
my $r_balances = shift;
|
|
|
|
|
|
|
|
|
|
$$r_rating_duration = 0; # ensure we start with zero length
|
|
|
|
|
|
|
|
|
|
my $src_user = $cdr->{source_cli};
|
|
|
|
|
my $src_user_domain = $cdr->{source_cli}.'@'.$cdr->{source_domain};
|
|
|
|
|
my $dst_user = $cdr->{destination_user_in};
|
|
|
|
|
@ -1574,6 +1601,8 @@ sub get_call_cost
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$$r_rating_duration = 0; # ensure we start with zero length
|
|
|
|
|
|
|
|
|
|
DEBUG "billing fee is ".(Dumper $r_profile_info);
|
|
|
|
|
|
|
|
|
|
#print Dumper $r_profile_info;
|
|
|
|
|
@ -1597,8 +1626,8 @@ sub get_call_cost
|
|
|
|
|
my $rate = 0;
|
|
|
|
|
my $offset = 0;
|
|
|
|
|
my $onpeak = 0;
|
|
|
|
|
my $init = 0;
|
|
|
|
|
my $duration = $cdr->{duration};
|
|
|
|
|
my $init = $cdr->{is_fragmented} // 0;
|
|
|
|
|
my $duration = (defined $cdr->{rating_duration} and $cdr->{rating_duration} < $cdr->{duration}) ? $cdr->{rating_duration} : $cdr->{duration};
|
|
|
|
|
my $prev_bal_id = undef;
|
|
|
|
|
my @cash_balance_rates = ();
|
|
|
|
|
my $prev_cash_balance = undef;
|
|
|
|
|
@ -1645,17 +1674,20 @@ sub get_call_cost
|
|
|
|
|
$r_profile_info->{on_init_interval} : $r_profile_info->{off_init_interval};
|
|
|
|
|
$rate = $onpeak == 1 ?
|
|
|
|
|
$r_profile_info->{on_init_rate} : $r_profile_info->{off_init_rate};
|
|
|
|
|
$$r_onpeak = $onpeak;
|
|
|
|
|
DEBUG "add init rate $rate per sec to costs";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
last if $split_peak_parts and defined($$r_onpeak) and $$r_onpeak != $onpeak
|
|
|
|
|
and !defined $cdr->{rating_duration};
|
|
|
|
|
|
|
|
|
|
$interval = $onpeak == 1 ?
|
|
|
|
|
$r_profile_info->{on_follow_interval} : $r_profile_info->{off_follow_interval};
|
|
|
|
|
$rate = $onpeak == 1 ?
|
|
|
|
|
$r_profile_info->{on_follow_rate} : $r_profile_info->{off_follow_rate};
|
|
|
|
|
DEBUG "add follow rate $rate per sec to costs";
|
|
|
|
|
}
|
|
|
|
|
$$r_onpeak = $onpeak;
|
|
|
|
|
$rate *= $interval;
|
|
|
|
|
DEBUG "interval is $interval, so rate for this interval is $rate";
|
|
|
|
|
|
|
|
|
|
@ -1818,6 +1850,7 @@ sub get_customer_call_cost
|
|
|
|
|
|
|
|
|
|
$cdr->{$dir."customer_billing_fee_id"} = $profile_info{fee_id};
|
|
|
|
|
$cdr->{$dir."customer_billing_zone_id"} = $profile_info{zone_id};
|
|
|
|
|
$cdr->{frag_customer_onpeak} = $onpeak if $split_peak_parts;
|
|
|
|
|
DEBUG "got call cost $$r_cost and free time $$r_free_time";
|
|
|
|
|
|
|
|
|
|
# we don't do prepaid for termination fees for now, so treat it as post-paid
|
|
|
|
|
@ -1911,6 +1944,7 @@ sub get_provider_call_cost
|
|
|
|
|
$cdr->{destination_reseller_billing_fee_id} = $profile_info{fee_id};
|
|
|
|
|
$cdr->{destination_reseller_billing_zone_id} = $profile_info{zone_id};
|
|
|
|
|
}
|
|
|
|
|
$cdr->{frag_reseller_onpeak} = $onpeak if $split_peak_parts;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
@ -1921,6 +1955,7 @@ sub get_provider_call_cost
|
|
|
|
|
$cdr->{destination_carrier_billing_fee_id} = $profile_info{fee_id};
|
|
|
|
|
$cdr->{destination_carrier_billing_zone_id} = $profile_info{zone_id};
|
|
|
|
|
}
|
|
|
|
|
$cdr->{frag_carrier_onpeak} = $onpeak if $split_peak_parts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
@ -1945,7 +1980,7 @@ sub rate_cdr
|
|
|
|
|
my $destination_reseller_free_time = 0;
|
|
|
|
|
|
|
|
|
|
my $direction;
|
|
|
|
|
my $rating_duration;
|
|
|
|
|
my @rating_durations;
|
|
|
|
|
|
|
|
|
|
unless($cdr->{call_status} eq "ok")
|
|
|
|
|
{
|
|
|
|
|
@ -2014,7 +2049,7 @@ sub rate_cdr
|
|
|
|
|
DEBUG "destination provider has billing profile $destination_provider_info{profile_id}, get reseller termination cost";
|
|
|
|
|
get_provider_call_cost($cdr, $type, "in",
|
|
|
|
|
\%destination_provider_info, \$destination_reseller_cost, \$destination_reseller_free_time,
|
|
|
|
|
\$rating_duration)
|
|
|
|
|
\$rating_durations[@rating_durations])
|
|
|
|
|
or FATAL "Error getting destination reseller cost for local destination_provider_id ".
|
|
|
|
|
$cdr->{destination_provider_id}." for cdr ".$cdr->{id}."\n";
|
|
|
|
|
DEBUG "destination reseller termination cost is $destination_reseller_cost";
|
|
|
|
|
@ -2026,7 +2061,7 @@ sub rate_cdr
|
|
|
|
|
DEBUG "get customer termination cost for destination_user_id $$cdr{destination_user_id}";
|
|
|
|
|
get_customer_call_cost($cdr, $type, "in",
|
|
|
|
|
\$destination_customer_cost, \$destination_customer_free_time,
|
|
|
|
|
\$rating_duration)
|
|
|
|
|
\$rating_durations[@rating_durations])
|
|
|
|
|
or FATAL "Error getting destination customer cost for local destination_user_id ".
|
|
|
|
|
$cdr->{destination_user_id}." for cdr ".$cdr->{id}."\n";
|
|
|
|
|
DEBUG "destination customer termination cost is $destination_customer_cost";
|
|
|
|
|
@ -2040,7 +2075,7 @@ sub rate_cdr
|
|
|
|
|
DEBUG "fetching source_carrier_cost based on destination_provider_info ".(Dumper \%destination_provider_info);
|
|
|
|
|
get_provider_call_cost($cdr, $type, "out",
|
|
|
|
|
\%destination_provider_info, \$source_carrier_cost, \$source_carrier_free_time,
|
|
|
|
|
\$rating_duration)
|
|
|
|
|
\$rating_durations[@rating_durations])
|
|
|
|
|
or FATAL "Error getting source carrier cost for cdr ".$cdr->{id}."\n";
|
|
|
|
|
} else {
|
|
|
|
|
WARNING "missing destination profile, so we can't calculate source_carrier_cost for destination_provider_info ".(Dumper \%destination_provider_info);
|
|
|
|
|
@ -2051,7 +2086,7 @@ sub rate_cdr
|
|
|
|
|
if($source_provider_info{profile_id}) {
|
|
|
|
|
get_provider_call_cost($cdr, $type, "out",
|
|
|
|
|
\%source_provider_info, \$source_reseller_cost, \$source_reseller_free_time,
|
|
|
|
|
\$rating_duration)
|
|
|
|
|
\$rating_durations[@rating_durations])
|
|
|
|
|
or FATAL "Error getting source reseller cost for cdr ".$cdr->{id}."\n";
|
|
|
|
|
} else {
|
|
|
|
|
# up to 2.8, there is one hardcoded reseller id 1, which doesn't have a billing profile, so skip this step here.
|
|
|
|
|
@ -2061,7 +2096,7 @@ sub rate_cdr
|
|
|
|
|
# get customer cost
|
|
|
|
|
get_customer_call_cost($cdr, $type, "out",
|
|
|
|
|
\$source_customer_cost, \$source_customer_free_time,
|
|
|
|
|
\$rating_duration)
|
|
|
|
|
\$rating_durations[@rating_durations])
|
|
|
|
|
or FATAL "Error getting source customer cost for local source_user_id ".
|
|
|
|
|
$cdr->{source_user_id}." for cdr ".$cdr->{id}."\n";
|
|
|
|
|
} else {
|
|
|
|
|
@ -2080,7 +2115,7 @@ sub rate_cdr
|
|
|
|
|
DEBUG "fetching destination_carrier_cost based on source_provider_info ".(Dumper \%source_provider_info);
|
|
|
|
|
get_provider_call_cost($cdr, $type, "in",
|
|
|
|
|
\%source_provider_info, \$destination_carrier_cost, \$destination_carrier_free_time,
|
|
|
|
|
\$rating_duration)
|
|
|
|
|
\$rating_durations[@rating_durations])
|
|
|
|
|
or FATAL "Error getting destination carrier cost for local destination_provider_id ".
|
|
|
|
|
$cdr->{destination_provider_id}." for cdr ".$cdr->{id}."\n";
|
|
|
|
|
} else {
|
|
|
|
|
@ -2090,7 +2125,7 @@ sub rate_cdr
|
|
|
|
|
DEBUG "fetching destination_reseller_cost based on source_provider_info ".(Dumper \%destination_provider_info);
|
|
|
|
|
get_provider_call_cost($cdr, $type, "in",
|
|
|
|
|
\%destination_provider_info, \$destination_reseller_cost, \$destination_reseller_free_time,
|
|
|
|
|
\$rating_duration)
|
|
|
|
|
\$rating_durations[@rating_durations])
|
|
|
|
|
or FATAL "Error getting destination reseller cost for local destination_provider_id ".
|
|
|
|
|
$cdr->{destination_provider_id}." for cdr ".$cdr->{id}."\n";
|
|
|
|
|
} else {
|
|
|
|
|
@ -2100,7 +2135,7 @@ sub rate_cdr
|
|
|
|
|
}
|
|
|
|
|
get_customer_call_cost($cdr, $type, "in",
|
|
|
|
|
\$destination_customer_cost, \$destination_customer_free_time,
|
|
|
|
|
\$rating_duration)
|
|
|
|
|
\$rating_durations[@rating_durations])
|
|
|
|
|
or FATAL "Error getting destination customer cost for local destination_user_id ".
|
|
|
|
|
$cdr->{destination_user_id}." for cdr ".$cdr->{id}."\n";
|
|
|
|
|
} else {
|
|
|
|
|
@ -2108,6 +2143,29 @@ sub rate_cdr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($split_peak_parts) {
|
|
|
|
|
# We require the onpeak/offpeak thresholds to be the same for all rating fee profiles used by any
|
|
|
|
|
# one particular CDR, so that CDR fragmentations are uniform across customer/carrier/reseller/etc
|
|
|
|
|
# entries. Mismatching onpeak/offpeak thresholds are a fatal error (which also results in a
|
|
|
|
|
# transaction rollback).
|
|
|
|
|
|
|
|
|
|
my %rating_durations;
|
|
|
|
|
for my $rd (@rating_durations) {
|
|
|
|
|
defined($rd) and $rating_durations{$rd} = 1;
|
|
|
|
|
}
|
|
|
|
|
scalar(keys(%rating_durations)) > 1
|
|
|
|
|
and FATAL "Error getting consistent rating fragment for cdr ".$cdr->{id}.". Rating profiles don't match.";
|
|
|
|
|
my $rating_duration = (keys(%rating_durations))[0] // $cdr->{duration};
|
|
|
|
|
|
|
|
|
|
if ($rating_duration < $cdr->{duration}) {
|
|
|
|
|
my $sth = $sth_create_cdr_fragment;
|
|
|
|
|
$sth->execute($rating_duration, $rating_duration, $cdr->{id})
|
|
|
|
|
or FATAL "Error executing create cdr fragment statement: ".$sth->errstr;
|
|
|
|
|
$cdr->{is_fragmented} = 1;
|
|
|
|
|
$cdr->{duration} = $rating_duration;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$cdr->{source_carrier_cost} = $source_carrier_cost;
|
|
|
|
|
$cdr->{source_reseller_cost} = $source_reseller_cost;
|
|
|
|
|
$cdr->{source_customer_cost} = $source_customer_cost;
|
|
|
|
|
@ -2289,6 +2347,7 @@ sub main
|
|
|
|
|
$sth_offpeak_special->finish;
|
|
|
|
|
$sth_unrated_cdrs->finish;
|
|
|
|
|
$sth_update_cdr->finish;
|
|
|
|
|
$split_peak_parts and $sth_create_cdr_fragment->finish;
|
|
|
|
|
$sth_provider_info->finish;
|
|
|
|
|
$sth_reseller_info->finish;
|
|
|
|
|
$sth_get_cbalances->finish;
|
|
|
|
|
|