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.
ngcp-panel/share/tools/generate_invoices.pl

428 lines
19 KiB

#!/usr/bin/perl -w
use lib '/media/sf_/usr/share/VMHost/ngcp-panel/lib';
use strict;
use Getopt::Long;
use DBI;
use Data::Dumper;
use DateTime::TimeZone;
use Test::MockObject;
use Email::MIME;
use Email::Sender::Simple qw(sendmail);
use Email::Sender::Transport::SMTP;
use Template;
use Geography::Countries qw/country/;
#use IO::All;
#apt-get install libemail-send-perl
#apt-get install libemail-sender-perl
#apt-get install libtest-mockobject-perl
#apt-get install libnet-smtp-ssl-perl
#apt-get install libio-all-perl
use Sipwise::Base;
use NGCP::Panel;
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::Contract;
use NGCP::Panel::Utils::InvoiceTemplate;
use NGCP::Panel::Utils::Invoice;
use NGCP::Panel::Utils::Email;
#use NGCP::Panel::View::SVG;
my $debug = 0;
my ($dbuser, $dbpass);
my $mfile = '/etc/mysql/sipwise.cnf';
if(-f $mfile) {
open my $fh, "<", $mfile
or die "failed to open '$mfile': $!\n";
$_ = <$fh>; chomp;
s/^SIPWISE_DB_PASSWORD='(.+)'$/$1/;
$dbuser = 'sipwise'; $dbpass = $_;
} else {
$dbuser = 'root';
$dbpass = '';
}
print "using user '$dbuser' with pass '$dbpass'\n"
if($debug);
my $dbh = DBI->connect('dbi:mysql:billing;host=localhost', $dbuser, $dbpass)
or die "failed to connect to billing DB\n";
my $opt = {};
Getopt::Long::GetOptions($opt, 'reseller_id:i@', 'client_contact_id:i@', 'client_contract_id:i@', 'stime:s', 'etime:s', 'send!','sendonly!','resend','regenerate!','help|?')
or die 'could not process command-line options';
print Dumper $opt;
my $stime = $opt->{stime}
? NGCP::Panel::Utils::DateTime::from_string($opt->{stime})
: NGCP::Panel::Utils::DateTime::current_local()->truncate( to => 'month' );
my $etime = $opt->{etime}
? NGCP::Panel::Utils::DateTime::from_string($opt->{etime})
: $stime->clone->add( months => 1 )->subtract( seconds => 1 );
if( $opt->{client_contract_id} ){
$opt->{reseller_id} = [$dbh->selectrow_array('select distinct contacts.reseller_id from contracts inner join contacts on contracts.contact_id=contacts.id '.ify(' where contracts.id', @{$opt->{client_contract_id}}), undef, @{$opt->{client_contract_id}} )];
$opt->{client_contact_id} = [$dbh->selectrow_array('select distinct contracts.contact_id from contracts '.ify(' where contracts.id', @{$opt->{client_contract_id}}), undef, @{$opt->{client_contract_id}} )];
}
print Dumper $opt;
process_invoices();
sub process_invoices{
my $invoices = {};
foreach my $provider_contract( @{ get_providers_contracts() } ){
print "reseller_id=".$provider_contract->{reseller_core_id}.";\n";
my $provider_contact = get_provider_contact($provider_contract);
foreach my $client_contact (@{ get_provider_clients_contacts($provider_contract) } ){
print "reseller_id=".$provider_contract->{reseller_core_id}.";contact_id=".$client_contact->{id}.";\n";
foreach my $client_contract (@{ get_client_contracts($client_contact) }){
$invoices->{$client_contract->{id}} ||= [];
if(!$opt->{sendonly}){
print "reseller_id=".$provider_contract->{reseller_core_id}.";contact_id=".$client_contact->{id}.";contract_id=".$client_contract->{id}.";\n";
if( my $billing_profile = get_billing_profile($client_contract, $stime, $etime) ){
if(my $invoice = generate_invoice_data($provider_contract,$provider_contact,$client_contract,$client_contact,$billing_profile, $stime, $etime)){
push @{$invoices->{$client_contract->{id}}}, $invoice;
}
}else{#if billing profile
print "No billing profile;\n"
}
}else{
$invoices->{$client_contract->{id}} = $dbh->selectall_arrayref('select invoices.* from invoices
inner join contract_balances on invoices.id=contract_balances.invoice_id
inner join contracts on contracts.id=contract_balances.contract_id
'.ifp(' where ',
join(' and ',
!$opt->{resend}?' invoices.sent_date is null ':(),
(ify(' contracts.contract_id ', (@{$opt->{client_contract_id}}, $client_contract->{id}) )),
(ifk(' date(invoices.period_start) >= ?', v2a($stime->ymd))),
(ifk(' date(invoices.period_start) <= ?', v2a($etime->ymd))),
)
), { Slice => {} }, @{$opt->{client_contract_id}}, v2a($client_contract->{id}), v2a($stime->ymd),v2a($etime->ymd) );
}
if($opt->{send} || $opt->{sendonly}){
my $email_template = get_email_template($provider_contract);
email($email_template, $provider_contact, $client_contact, $invoices->{$client_contract->{id}} );
}
}#foreach client contract
}#foreach client contact
}#foreach reseller
}
sub get_providers_contracts{
return $dbh->selectall_arrayref('select contracts.*,resellers.id as reseller_core_id from resellers inner join contracts on resellers.contract_id=contracts.id where resellers.status != "terminated" '.ify(' and resellers.id ', @{$opt->{reseller_id}}), { Slice => {} }, @{$opt->{reseller_id}} );
}
sub get_provider_contact{
my($provider_contract) = @_;
#todo: use selectall_hashref to don't query every time
return $dbh->selectrow_hashref('select * from contacts where id=?', undef, $provider_contract->{contact_id} )
}
sub get_provider_clients_contacts{
my($provider_contract) = @_;
#according to /reseller/ajax_reseller_filter
my $contacts = $dbh->selectall_arrayref('select contacts.* from contacts where reseller_id = ?'.ify(' and contacts.id', @{$opt->{client_contact_id}}), { Slice => {} }, $provider_contract->{reseller_core_id}, @{$opt->{client_contact_id}} );
#foreach (@$contacts){
# $_->{country_name} = country($_->{country});
#}
return $contacts;
}
sub get_client_contracts{
my($client_contact) = @_;
return $dbh->selectall_arrayref('select contracts.* from contracts where contracts.contact_id=? '.ify(' and contracts.id', @{$opt->{client_contract_id}}), { Slice => {} }, $client_contact->{id}, @{$opt->{client_contract_id}} );
}
sub get_billing_profile{
my($client_contract, $stime, $etime) = @_;
$dbh->selectrow_hashref('select distinct billing_profiles.*
from billing_mappings
inner join billing_profiles on billing_mappings.billing_profile_id=billing_profiles.id
inner join contracts on contracts.id=billing_mappings.contract_id
inner join products on billing_mappings.product_id=products.id and products.class in("sipaccount","pbxaccount")
where
contracts.status != "terminated"
and contracts.id=?
and (billing_mappings.start_date <= ? OR billing_mappings.start_date IS NULL)
and (billing_mappings.end_date >= ? OR billing_mappings.end_date IS NULL)'
, undef, $client_contract->{id}, $etime->epoch, $stime->epoch
);
}
sub get_invoice_data_raw{
my($client_contract, $stime, $etime) = @_;
my $invoice_details_calls = $dbh->selectall_arrayref('select cdr.*,from_unixtime(cdr.start_time) as start_time,bzh.zone, bzh.detail as zone_detail
from accounting.cdr
LEFT JOIN billing.billing_zones_history bzh ON bzh.bz_id = cdr.source_customer_billing_zone_id
where
cdr.source_user_id != "0"
and cdr.call_status="ok"
and cdr.source_account_id=?
and cdr.start_time >= ?
and cdr.start_time <= ?
order by cdr.start_time
-- limit 25'
, { Slice => {} }
, $client_contract->{id},$stime->epoch,$etime->epoch
);
my $invoice_details_zones = $dbh->selectall_arrayref('select SUM(cdr.source_customer_cost) AS customercost, COUNT(*) AS number, SUM(cdr.duration) AS duration,sum(cdr.source_customer_free_time) as free_time, bzh.zone
from accounting.cdr
LEFT JOIN billing.billing_zones_history bzh ON bzh.bz_id = cdr.source_customer_billing_zone_id
where
cdr.source_user_id != "0"
and cdr.call_status="ok"
and cdr.source_account_id=?
and cdr.start_time >= ?
and cdr.start_time <= ?
group by bzh.zone
order by bzh.zone'
, {Slice => {} }
, $client_contract->{id},$stime->epoch,$etime->epoch
);
#/data for invoice generation
#my $i = 1;
#$invoice_details_calls = [map{[$i++,$_]} (@$invoice_details_calls) x 1];
#$i = 1;
#$invoice_details_zones = [map{[$i++,$_]} (@$invoice_details_zones) x 1];
my $stash = {
invoice_details_zones => $invoice_details_zones,
invoice_details_calls => $invoice_details_calls,
};
return $stash;
}
sub generate_invoice_data{
my($provider_contract,$provider_contact,$client_contract,$client_contact,$billing_profile, $stime, $etime) = @_;
state ($t,$svg_default);
if(!$t){
$t = NGCP::Panel::Utils::InvoiceTemplate::get_tt();
$svg_default = $t->context->insert('invoice/default/invoice_template_svg.tt');
#NGCP::Panel::Utils::InvoiceTemplate::preprocess_svg(\$svg_default);
}
my $svg = $dbh->selectrow_array('select data from invoice_templates where type = "svg" and reseller_id=?',undef,$provider_contract->{reseller_core_id});#is_active = 1 and
if($svg){
#NGCP::Panel::Utils::InvoiceTemplate::preprocess_svg(\$svg);
}else{
$svg = $svg_default;
print "No saved active template - no invoice;\n";
return;
}
my ($contract_balance,$invoice)=({},{});
($contract_balance,$invoice) = get_contract_balance($client_contract,$billing_profile,$contract_balance,$invoice,$stime,$etime);
#print Dumper $contract_balance;
$client_contact->{country} = country($client_contact->{country} || '');
$provider_contact->{country} = country($provider_contact->{country} || '');
# TODO: if not a full month, calculate fraction?
#TODO: to utils::contract and share with catalyst version
my $invoice_amounts = NGCP::Panel::Utils::Invoice::get_invoice_amounts(
customer_contract => $client_contract,
contract_balance => $contract_balance,
billing_profile => $billing_profile,
);
$invoice = {
%$invoice,
%$invoice_amounts,
};
my($invoice_data) = get_invoice_data_raw($client_contract, $stime, $etime);
my $out = '';
my $pdf = '';
$invoice->{data} = '';
my $vars = {
invoice => $invoice,
rescontact => $provider_contact,
customer => $client_contract,
custcontact => $client_contact,
billprof => $billing_profile,
calls => $invoice_data->{invoice_details_calls},
zones => {
totalcost => $contract_balance->{cash_balance_interval},
data => $invoice_data->{invoice_details_zones},
},
};
$out = $t->context->process(\$svg, $vars);
#for default template
$out = '<root>'.$out.'</root>';
NGCP::Panel::Utils::InvoiceTemplate::preprocess_svg(\$out);
#for default template
$out =~s/^<root>|<\/root>$//;
NGCP::Panel::Utils::InvoiceTemplate::svg_pdf(undef, \$out, \$pdf);
print "generated data for invoice.id=".$invoice->{id}."; invoice.serial=".$invoice->{serial}.";\n";
$invoice->{data} = $pdf;
#set sent_date to null after each data regeneration
$dbh->do('update invoices set sent_date=?,data=?,amount_net=?,amount_vat=?,amount_total=? where id=?',undef,undef,@$invoice{qw/data amount_net amount_vat amount_total id/});
return $invoice;
}
sub get_contract_balance{
my($client_contract,$billing_profile,$contract_balance,$invoice,$stime,$etime) = @_;
if(!($contract_balance = $dbh->selectrow_hashref('select * from contract_balances where contract_id=? and date(start)=? and date(end)=?',undef,$client_contract->{id},$stime->ymd,$etime->ymd))){
@$contract_balance{qw/cash_balance cash_balance_interval free_time_balance free_time_balance_interval/} = NGCP::Panel::Utils::Contract::get_contract_balance_values(
%$billing_profile,
stime => $stime,
etime => $etime,
);
$dbh->do('insert into contract_balances(contract_id,cash_balance,cash_balance_interval,free_time_balance,free_time_balance_interval,start,end,invoice_id)values(?,?,?,?,?,?,?,?)',undef,$client_contract->{id},@$contract_balance{qw/cash_balance cash_balance_interval free_time_balance free_time_balance_interval/},$stime->datetime, $etime->datetime,undef );
$invoice = get_invoice(undef, $client_contract->{id},$stime, $etime);
#my $contract_balance_id = $dbh->last_insert_id(undef,'billing','contract_balances','id');
#print "contract_balance_id=$contract_balance_id;\n";
#$contract_balance = $dbh->selectrow_hashref('select * from contract_balances where id=?',undef,);
$contract_balance = $dbh->selectrow_hashref('select * from contract_balances where contract_id=? and date(start)=? and date(end)=?',undef,$client_contract->{id},$stime->ymd,$etime->ymd);
#print Dumper $contract_balance;
}else{
$invoice = get_invoice($contract_balance->{invoice_id},$client_contract->{id},$stime, $etime);
}
return ($contract_balance,$invoice);
}
sub get_invoice{
my($invoice_id, $contract_id, $stime, $etime) = @_;
my $invoice;
if($opt->{regenerate}){
if($invoice_id){
$invoice = $dbh->selectrow_hashref('select * from invoices where id=?',undef, $invoice_id);
}else{
$invoice = $dbh->selectrow_hashref('select * from invoices where contract_id=? and date(period_start)=? and date(period_end)=?',undef, $contract_id, $stime->ymd, $etime->ymd);
}
}
if(!$invoice){
my $serial_tmp = "tmp".time.int(rand(99999));
$dbh->do('insert into invoices(contract_id,period_start,period_end,serial)values(?,?,?,?)', undef, $contract_id,$stime->ymd.' '.$stime->hms, $etime->ymd.' '.$etime->hms, $serial_tmp );
$invoice->{id} = $dbh->last_insert_id(undef,'billing','invoices','id');
#are necessary here for serial generation
@$invoice{qw/period_start period_end/} = ($stime,$etime);
$invoice->{serial} = NGCP::Panel::Utils::Invoice::get_invoice_serial(undef,{invoice => $invoice});
$dbh->do('update invoices set serial=? where id=?', undef, @$invoice{qw/serial id/} );
$invoice = $dbh->selectrow_hashref('select * from invoices where id=?',undef, $invoice->{id});
}
if($invoice->{id} && !$invoice_id){
$dbh->do('update contract_balances set invoice_id = ? where contract_id=? and start=? and end=?', undef, $invoice->{id},$contract_id, $stime->datetime, $etime->datetime );
}
#obj value will be used in email
$invoice = {
%$invoice,
period_start => $stime,
period_start_obj => $stime,
period_end => $etime,
period_end_obj => $etime,
};
return $invoice;
}
sub get_email_template{
my ($provider_contract) = @_;
state $templates;
state $template_default;
if(!$templates){
$templates = $dbh->selectall_hashref('select * from email_templates where name = ?','reseller_id',undef,"invoice_email");
$template_default = $dbh->selectrow_hashref('select * from email_templates where name = ?',undef,"invoice_default_email");
}
my $res = ( $templates->{$provider_contract->{reseller_core_id}} or $template_default );
return $res;
}
sub email{
#todo: repeat my old function based on templates and store into utils
my($email_template,$provider_contact,$client_contact,$client_invoices,$transport_in) = @_;
#print Dumper $client_invoices;
my @invoice_ids = map {$_->{id}} @$client_invoices;
$provider_contact->{id} //= '';
$client_contact->{id} //= '';
$client_contact->{email} //= '';
print "send email for: provider_contact_id=".$provider_contact->{id}.";client_contact_id=".$client_contact->{id}."; client_contact->email=".$client_contact->{email}."; invoice_ids=".join(",",@invoice_ids).";\n";
if(@$client_invoices < 1 ){
return;
}
$client_contact->{email} //= '';
if($client_contact->{email}){
my @attachments = map {
my $invoice = $_;
Email::MIME->create(
attributes => {
filename => "invoice_".$invoice->{serial}.".pdf",
content_type => "application/pdf",
encoding => "base64",
disposition => "attachment",
},
body => $invoice->{data},
);
} @$client_invoices;
my $invoice = $client_invoices->[0];
foreach (qw/period_start period_end/){
$invoice->{$_.'_obj'} = NGCP::Panel::Utils::DateTime::from_string($invoice->{$_}) unless $invoice->{$_.'_obj'};
}
my $tmpl_processed = NGCP::Panel::Utils::Email::process_template(undef,$email_template,{
provider => $provider_contact,
client => $client_contact,
invoice => $invoice,
});
#print Dumper $tmpl_processed;
my $email = Email::MIME->create(
header => [
From => $tmpl_processed->{from_email} || $provider_contact->{email},
To => $tmpl_processed->{to} || $client_contact->{email},
#To => 'ipeshinskaya@gmail.com',
#To => 'ipeshinskaya@sipwise.com',
Subject => $tmpl_processed->{subject}, #todo: ask sales about subject
],
parts => [
@attachments,
Email::MIME->create(
attributes => {
encoding => "quoted-printable",
content_type => "text/plain",
charset => "US-ASCII",
},
body_str => $tmpl_processed->{body},
),
]
);
#sendmail($email, { transport => $transport });
sendmail($email);
print "Error sending email: $@" if $@;
$dbh->do('update invoices set sent_date=now() where '.ify( ' id ', @invoice_ids ), undef, @invoice_ids);
}#we have correct "To"
}
sub ify{
my $key = shift;
return ( $#_ == 0 ) ? ' '.$key.' = ? ': ( ( $#_ > 0 ) ? ( ' '.$key. 'in('. ( '?,' x ( $#_ ) ) .'?) ' ) : ( wantarray ? () : '' ) );
}
sub ifp{
my ($prefix, $value) = @_;
return $value ? $prefix.$value : $value;
}
sub ifk{
my ($key, $value) = @_;
return $value ? $key : ();
}
sub v2a{
my($value) = @_;
return $value ? ($value): ();
}