MT#5879 InvoiceGeneration through web.

Todo: Send all contact->contracts invoices in one email from cli script.
Review invoice client ID - it should be contract Id, not contact Id
Review invoice data gathering
Complete generation
ipeshinskaya/InvoiceTemplate5
Irina Peshinskaya 12 years ago committed by Victor Seva
parent 275d6a7356
commit 5f0c59d3a7

@ -3,15 +3,16 @@ use Sipwise::Base;
use namespace::sweep;
BEGIN { extends 'Catalyst::Controller'; }
use DateTime qw();
use DateTime::Format::Strptime;
use HTTP::Status qw(HTTP_SEE_OTHER);
use File::Type;
use MIME::Base64 qw(encode_base64);
use NGCP::Panel::Utils::Contract;
use NGCP::Panel::Utils::Message;
use NGCP::Panel::Form::Invoice::Template;
use NGCP::Panel::Form::Invoice::Generate;
use NGCP::Panel::Model::DB::InvoiceTemplate;
use NGCP::Panel::Utils::InvoiceTemplate;
@ -218,43 +219,119 @@ sub invoice_generate :Chained('base') :PathPart('generate') :Args(0) {
#from parameters
$in = $c->request->parameters;
my $parser = DateTime::Format::Strptime->new(
#pattern => '%Y-%m-%d %H:%M',
pattern => '%Y-%m-%d',
);
if($in->{start}) {
$in->{stime} = $parser->parse_datetime($in->{start});
}
if($in->{end}) {
$in->{etime} = $parser->parse_datetime($in->{end});
}
$in->{provider_id} = $c->stash->{provider}->id;
#(undef,undef,@$in{qw/tt_id/}) = @_;
#$in->{client_contact_id} = $c->request->parameters->{client_contact_id};
#(undef,undef,@$in{qw/client_contact_id/}) = @_;
if($in->{tt_id}){
if($in->{invoice_id}){
#always was sure that i'm calm and even friendly person, but I would kill with pleasure author of dbix.
my $db_object;
($out->{tt_id},undef,$db_object) = $backend->getInvoiceTemplate( %$in );
$out->{tt_data}->{tt_id} = $db_object->get_column('id');
$out->{tt_data}->{provider_id} = $db_object->get_column('reseller_id');
foreach(qw/name is_active/){$out->{tt_data}->{$_} = $db_object->get_column($_);}
($out->{invoice_id},undef,$db_object) = $backend->getInvoiceTemplate( %$in );
$out->{invoice_data}->{invoice_id} = $db_object->get_column('id');
$out->{invoice_data}->{provider_id} = $db_object->get_column('reseller_id');
foreach(qw/name is_active/){$out->{invoice_data}->{$_} = $db_object->get_column($_);}
}
if(!$out->{tt_data}){
$out->{tt_data} = $in;
if(!$out->{invoice_data}){
$out->{invoice_data} = $in;
}
$validator = NGCP::Panel::Form::Invoice::Template->new( backend => $backend );
$validator = NGCP::Panel::Form::Invoice::Generate->new( backend => $backend );
$validator->remove_undef_in($in);
#need to think how to automate it - maybe through form showing param through args? what about args for uri_for_action?
#join('/',$c->controller,$c->action)
$validator->action( $c->uri_for_action('invoice/generate',[$in->{provider_id}]) );
$validator->action( $c->uri_for_action('invoice/invoice_generate',[$in->{provider_id}]) );
$validator->name( 'invoice_generate' );#from parameters
#my $posted = 0;
my $posted = exists $in->{submitid};
$c->log->debug("posted=$posted;");
#todo: validate that customer is not terminated and is sip/pbx account
$validator->process(
posted => $posted,
params => $in,
#item => $in,
item => $out->{tt_data},
#item => $out->{tt_data},
item => $out->{invoice_data},
#item => $out->{invoice_data},
);
my $in_validated = $validator->fif;
if($posted){
if($validator->validated) {
#copy/pasted from NGCP\Panel\Role\API\Customers.pm
my $customer = $backend->getInvoiceClientContactInfo($in);
my $contract_balance = $backend->getContractBalance($in);
if(!$contract_balance){
my $billing_profile = $backend->getBillingProfile($in);
NGCP::Panel::Utils::Contract::create_contract_balance(
c => $c,
profile => $billing_profile,
contract => $customer,
);
$contract_balance = $backend->getContractBalance($in);
}
#my $billing_mapping = $customer->billing_mappings->find($customer->get_column('bmid'));
#my $billing_profile_id = $billing_mapping->billing_profile->id;
#my $stime = NGCP::Panel::Utils::DateTime::current_local()->truncate(to => 'month');
#my $etime = $stime->clone->add(months => 1);
#my $contract_balance = $customer->contract_balances
# ->find({
# start => { '>=' => $stime },
# end => { '<' => $etime },
# });
#unless($contract_balance) {
# try {
# NGCP::Panel::Utils::Contract::create_contract_balance(
# c => $c,
# profile => $billing_mapping->billing_profile,
# contract => $customer,
# );
# } catch($e) {
# $self->log->error("Failed to create current contract balance for customer contract id '".$customer->id."': $e");
# $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.");
# return;
# };
# $contract_balance = $customer->contract_balances->find({
# start => { '>=' => $stime },
# end => { '<' => $etime },
# });
#}
try {
$backend->storeInvoiceTemplateInfo(%$in_validated);
#$backend->storeInvoiceTemplateInfo(%$in_validated);
$c->flash(messages => [{type => 'success', text => $c->loc(
$in->{tt_id}
$in->{invoice_id}
?'Invoice template updated'
:'Invoice template created'
) }]);
@ -263,7 +340,7 @@ sub invoice_generate :Chained('base') :PathPart('generate') :Args(0) {
c => $c,
error => $e,
desc => $c->loc(
$in->{tt_id}
$in->{invoice_id}
?'Failed to update invoice template.'
:'Failed to create invoice template.'
),
@ -272,7 +349,7 @@ sub invoice_generate :Chained('base') :PathPart('generate') :Args(0) {
$c->stash( messages => $c->flash->{messages} );
$c->stash( template => 'helpers/ajax_messages.tt' );
}else{
#$c->stash( m => {create_flag => !$in->{tt_id}} );
#$c->stash( m => {create_flag => !$in->{invoice_id}} );
#$c->stash( form => $validator );
##$c->stash( template => 'helpers/ajax_form_modal.tt' );
#$c->stash( template => 'invoice/template_info_form.tt' );
@ -282,10 +359,10 @@ sub invoice_generate :Chained('base') :PathPart('generate') :Args(0) {
if(!$validator->validated){
#$c->stash( in => $in );
#$c->stash( out => $out );
$c->stash( m => {create_flag => !$in->{tt_id}} );
$c->stash( m => {create_flag => !$in->{invoice_id}} );
$c->stash( form => $validator );
#$c->stash( template => 'helpers/ajax_form_modal.tt' );
$c->stash( template => 'invoice/template_info_form.tt' );
$c->stash( template => 'invoice/invoice_generate_form.tt' );
}
$c->detach( $c->view("SVG") );#to the sake of nowrapper
}

@ -1,17 +1,20 @@
package NGCP::Panel::Form::Invoice::Generate;
use Sipwise::Base;
use HTML::FormHandler::Moose;
#extends qw/HTML::FormHandler NGCP::Panel::Form::ValidatorBase/;
extends 'NGCP::Panel::Form::ValidatorBase';
extends 'HTML::FormHandler::Field::Compound';
use Moose::Util::TypeConstraints;
use HTML::FormHandler::Widget::Block::Bootstrap;
use DateTime;
use DateTime::Format::Strptime;
has '+widget_wrapper' => ( default => 'Bootstrap' );
has '+use_fields_for_input_without_param' => ( default => 1 );
sub build_render_list {[qw/fields actions/]}
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class { [qw/form-horizontal/] }
has_field 'submitid' => ( type => 'Hidden' );
#has_field 'contract.id' => (
@ -45,6 +48,55 @@ has_field 'end' => (
required => 1,
);
has_field 'save' => (
type => 'Submit',
value => 'Generate',
element_class => [qw/btn btn-primary/],
do_label => 0,
);
has_field 'client_contact_id' => (
type => 'Hidden',
required => 1,
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/start end/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
sub validate {
my $self = shift;
my $start = $self->field('start');
my $end = $self->field('end');
my $parser = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%d %H:%M:%S',
);
my $sdate = $parser->parse_datetime($start->value);
unless($sdate) {
$start->add_error("Invalid date format, must be YYYY-MM-DD hh:mm:ss");
}
my $edate = $parser->parse_datetime($end->value);
unless($edate) {
$end->add_error("Invalid date format, must be YYYY-MM-DD hh:mm:ss");
}
#unless(DateTime->compare($sdate, $edate) == -1) {
# my $err_msg = 'End time must be later than start time';
# $start->add_error($err_msg);
# $end->add_error($err_msg);
#}
#if(!$self->backend->checkSipPbxAccount()){
#}
}
1;
=head1 NAME

@ -219,14 +219,73 @@ sub checkInvoiceTemplateProvider{
return 0;
}
#sub getInvoiceClientContactInfo{
# my $self = shift;
# my (%params) = @_;
# my ($client_id) = @params{qw/client_id/};
# return $tt_record = $self->schema->resultset('contacts')->search({
# reseller_id => $client_id,
# });
#}
sub getInvoiceClientContactInfo{
my $self = shift;
my (%params) = @_;
my ($client_contact_id) = @params{qw/client_contact_id/};
return $self->schema->resultset('contacts')->search({
id => $client_contact_id,
});
}
sub getBillingProfile{
my $self = shift;
my (%params) = @_;
my ($client_contact_id, $stime, $etime) = @params{qw/client_contact_id start end/};
#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.contact_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)
return $self->schema->resultset('billing_profiles')->search({
'contract.contact_id' => $client_contact_id,
#'contract.status' => { '!=' => 'terminated' },
'product.class' => { '-in' => [qw/sipaccount pbxaccount/] },
'billing_mappings.start_date' => [
{ '<=' => $etime->epoch },
{ -is => undef },
],
'billing_mappings.end_date' => [
{ '>=' => $stime->epoch },
{ -is => undef },
],
},{
'join' => [ { 'billing_mappings' => [ 'product', 'contract' ] } ],
});
}
sub getContractBalance{
my $self = shift;
my (%params) = @_;
my ($client_contact_id, $stime, $etime) = @params{qw/client_contact_id start end/};
#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.contact_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)
return $self->schema->resultset('contract_balances')->search({
'contract.contact_id' => $client_contact_id,
#'contract.status' => { '!=' => 'terminated' },
'start' => [
{ '<=' => $etime->epoch },
{ '-is' => undef },
],
'end' => [
{ '>=' => $stime->epoch },
{ '-is' => undef },
],
},{
'join' => [ 'contract' ],
});
}
sub getProviderInvoiceList{
my $self = shift;
my (%params) = @_;

@ -14,6 +14,8 @@
[%modal_script( m = mf_helper )%]
[% site_config.title = c.loc('Invoices for [_1]', provider.name ) -%]
<script type="text/javascript" src="/js/background.js"></script>
<script type="text/javascript" src="/js/modalAjax.js"></script>
<div class="row">
<span>
@ -82,8 +84,8 @@ function applyClientFilter(table,tr,contact_id){
{ name => 'contracts.status', title => c.loc('Status'), search => 1 },
];
helper.dt_buttons = [
{ name = c.loc('Generate invoice'), uri = 'javascript:void(0);', onclick='', class = 'btn-small btn-primary', icon = 'icon-star' },
{ name => c.loc('Filter invoices'), uri=>'javascript:void(0);', onclick = "applyClientFilter(\\'\\',\$(this).closest(\\'tr\\'),'+full.id+');", class = 'btn-small btn-primary', icon = 'icon-glass', tooltip='Click twice to clear client filter.' },
{ name => c.loc('Generate invoice'), uri => 'javascript:void(0);', onclick => "fetch_into(\\'invoice_generate_form\\', \\'" _ c.uri_for_action('/invoice/invoice_generate', [ provider.id]) _ "\\',\\'item=invoice_generate&client_contact_id='+full.id+'\\',function(){modalFormScript();});void(0);", class => 'btn-small btn-primary', icon => 'icon-star' },
{ name => c.loc('Filter invoices'), uri=>'javascript:void(0);', onclick => "applyClientFilter(\\'\\',\$(this).closest(\\'tr\\'),'+full.id+');", class => 'btn-small btn-primary', icon => 'icon-glass', tooltip => 'Click twice to clear client filter.' },
];
helper.identifier = 'provider_client_list_ajax';
helper.ajax_uri = c.uri_for_action( '/invoice/ajax_datatables_data', [ provider.id, 'provider_client_list' ] ) ;

@ -88,90 +88,91 @@ foreach my $provider_contract( @{$dbh->selectall_arrayref('select contracts.*,re
#according to /reseller/ajax_reseller_filter
foreach my $client_contact (@{ $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}} ) } ){
my $client_contract = $dbh->selectrow_hashref('select contracts.* from contracts where contracts.contact_id=? ', undef, $client_contact->{id} );
if( my $billing_profile = $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.contact_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_contact->{id}, $etime->epoch, $stime->epoch
) ){
my ($contract_balance,$invoice)=({},{});
($contract_balance,$invoice) = get_contract_balance($client_contract,$billing_profile,$contract_balance,$invoice,$stime,$etime);
my $invoice_details_calls = $dbh->selectall_arrayref('select cdr.*,bzh.zone, bzh.detail as zone_detail
from accounting.cdr
LEFT JOIN billing.billing_zones_history bzh ON bzh.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 cost, 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.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
);
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 ($in, $out);
#tt_id used only as part in temporary directory
$in = {
no_fake_data => 1,
provider_id => $provider_contract->{reseller_core_id},
tt_type => 'svg',
tt_sourcestate => 'saved',
tt_id => $provider_contract->{reseller_core_id},
};
$out = {
tt_id => $provider_contract->{reseller_core_id},
};
my $stash = {
provider => $provider_contact,
client => $client_contact,
invoice => $invoice,
invoice_details_zones => $invoice_details_zones,
invoice_details_calls => $invoice_details_calls,
};
my $svg = $dbh->selectrow_array('select base64_saved from invoice_templates where is_active = 1 and type = "svg" and reseller_id=?',undef,$provider_contract->{reseller_core_id});
if($svg){
NGCP::Panel::Utils::InvoiceTemplate::preprocessInvoiceTemplateSvg($in,\$svg);
}else{
$svg = $svg_default;
foreach my $client_contract (@{ $dbh->selectall_arrayref('select contracts.* from contracts where contracts.contact_id=? ', { Slice => {} }, $client_contact->{id} ) }){
if( my $billing_profile = $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
) ){
my ($contract_balance,$invoice)=({},{});
($contract_balance,$invoice) = get_contract_balance($client_contract,$billing_profile,$contract_balance,$invoice,$stime,$etime);
my $invoice_details_calls = $dbh->selectall_arrayref('select cdr.*,bzh.zone, bzh.detail as zone_detail
from accounting.cdr
LEFT JOIN billing.billing_zones_history bzh ON bzh.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 cost, 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.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
);
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 ($in, $out);
#tt_id used only as part in temporary directory
$in = {
no_fake_data => 1,
provider_id => $provider_contract->{reseller_core_id},
tt_type => 'svg',
tt_sourcestate => 'saved',
tt_id => $provider_contract->{reseller_core_id},
};
$out = {
tt_id => $provider_contract->{reseller_core_id},
};
my $stash = {
provider => $provider_contact,
client => $client_contact,
invoice => $invoice,
invoice_details_zones => $invoice_details_zones,
invoice_details_calls => $invoice_details_calls,
};
my $svg = $dbh->selectrow_array('select base64_saved from invoice_templates where is_active = 1 and type = "svg" and reseller_id=?',undef,$provider_contract->{reseller_core_id});
if($svg){
NGCP::Panel::Utils::InvoiceTemplate::preprocessInvoiceTemplateSvg($in,\$svg);
}else{
$svg = $svg_default;
}
$svg = $view->getTemplateProcessed($c_mock,\$svg, $stash );
#print $svg;
#die();
NGCP::Panel::Utils::InvoiceTemplate::convertSvg2Pdf(undef,\$svg,$in,$out);
#binmode(STDOUT);
#print $out->{tt_string_pdf};
#die;
$dbh->do('update invoices set data=? where id=?',undef,$out->{tt_string_pdf},$invoice->{id});
email($provider_contact,$client_contact,$invoice,\$out->{tt_string_pdf});
}
$svg = $view->getTemplateProcessed($c_mock,\$svg, $stash );
#print $svg;
#die();
NGCP::Panel::Utils::InvoiceTemplate::convertSvg2Pdf(undef,\$svg,$in,$out);
#binmode(STDOUT);
#print $out->{tt_string_pdf};
#die;
$dbh->do('update invoices set data=? where id=?',undef,$out->{tt_string_pdf},$invoice->{id});
email($provider_contact,$client_contact,$invoice,\$out->{tt_string_pdf});
}
}
}
@ -240,7 +241,7 @@ sub email{
content_type => "text/plain",
charset => "US-ASCII",
},
body_str => "Dear Customer!\n\nEmail:$client_contact->{email}\n\nSome text from sales here. Using loc somehow?\n Best regards,",
body_str => "Dear Customer!\n\nEmail:$client_contact->{email}.\n\nSome text from sales here. Using loc somehow?\n Best regards,",
),
]
);

Loading…
Cancel
Save