diff --git a/lib/NGCP/Panel/Controller/Invoice.pm b/lib/NGCP/Panel/Controller/Invoice.pm index 7ad1773ae5..b3d644df69 100644 --- a/lib/NGCP/Panel/Controller/Invoice.pm +++ b/lib/NGCP/Panel/Controller/Invoice.pm @@ -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 } diff --git a/lib/NGCP/Panel/Form/Invoice/Generate.pm b/lib/NGCP/Panel/Form/Invoice/Generate.pm index b5cd809b86..6b00fdfcd0 100644 --- a/lib/NGCP/Panel/Form/Invoice/Generate.pm +++ b/lib/NGCP/Panel/Form/Invoice/Generate.pm @@ -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 diff --git a/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm b/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm index 979b1e09a1..1d352f8f72 100644 --- a/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm +++ b/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm @@ -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) = @_; diff --git a/share/templates/invoice/invoice_generate.tt b/share/templates/invoice/invoice_generate_form.tt similarity index 100% rename from share/templates/invoice/invoice_generate.tt rename to share/templates/invoice/invoice_generate_form.tt diff --git a/share/templates/invoice/list.tt b/share/templates/invoice/list.tt index 039b2a9798..18da27199d 100644 --- a/share/templates/invoice/list.tt +++ b/share/templates/invoice/list.tt @@ -14,6 +14,8 @@ [%modal_script( m = mf_helper )%] [% site_config.title = c.loc('Invoices for [_1]', provider.name ) -%] + +
@@ -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' ] ) ; diff --git a/share/tools/generate_invoices.pl b/share/tools/generate_invoices.pl index 0adf97c06a..83b1fa3adc 100644 --- a/share/tools/generate_invoices.pl +++ b/share/tools/generate_invoices.pl @@ -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,", ), ] );