diff --git a/lib/NGCP/Panel/Controller/API/Invoices.pm b/lib/NGCP/Panel/Controller/API/Invoices.pm index ce8e9051e5..984b2639fd 100644 --- a/lib/NGCP/Panel/Controller/API/Invoices.pm +++ b/lib/NGCP/Panel/Controller/API/Invoices.pm @@ -82,7 +82,7 @@ sub create_item { my $period = $form->values->{period}; my $item; try { - my($contract_id,$customer,$tmpl,$stime,$etime,$invoice_data) = NGCP::Panel::Utils::Invoice::check_invoice_data($c, { + my ($contract,$tmpl,$stime,$etime,$invoice_data) = NGCP::Panel::Utils::Invoice::check_invoice_data($c, { contract_id => $contract_id, tmpl_id => $tmpl_id, period_start => $period_start, @@ -90,8 +90,7 @@ sub create_item { period => $period, }); $item = NGCP::Panel::Utils::Invoice::create_invoice($c,{ - contract_id => $contract_id, - customer => $customer, + contract => $contract, stime => $stime, etime => $etime, tmpl => $tmpl, diff --git a/lib/NGCP/Panel/Controller/Contract.pm b/lib/NGCP/Panel/Controller/Contract.pm index 9b1d6c4110..93cfe7a38e 100644 --- a/lib/NGCP/Panel/Controller/Contract.pm +++ b/lib/NGCP/Panel/Controller/Contract.pm @@ -586,6 +586,48 @@ sub is_valid_noreseller_contact { } } +sub all_contracts_list :Chained('contract_list') :PathPart('all_contracts') :CaptureArgs(0) { + my ($self, $c) = @_; + + my $now = NGCP::Panel::Utils::DateTime::current_local; + $c->stash->{contract_dt_columnsX} = NGCP::Panel::Utils::Datatables::set_columns($c, [ + { name => "id", search => 1, title => $c->loc("#") }, + { name => "external_id", search => 1, title => $c->loc("External #") }, + { name => "contact.email", search => 1, title => $c->loc("Contact Email") }, + #{ name => 'billing_profile_name', accessor => "billing_profile_name", search => 0, title => $c->loc('Billing Profile'), + # literal_sql => NGCP::Panel::Utils::BillingMappings::get_actual_billing_mapping_stmt(c => $c, now => $now, projection => 'billing_profile.name' ) }, + { name => "status", search => 1, title => $c->loc("Status") }, + { name => "product.name", search => 0, title => $c->loc("Product") }, + ]); + + my $rs_all_contracts = NGCP::Panel::Utils::Contract::get_contract_rs( + schema => $c->model('DB'), + now => $now, + include_terminated => 0, + ); + + $c->stash(rs_all_contracts => $rs_all_contracts); + + #my @product_ids = map { $_->id; } $c->model('DB')->resultset('products')->search_rs({ 'class' => ['sippeering'] })->all; + #my $base_rs = $c->stash->{contract_select_rs}; + #$c->stash->{peering_rs} = $base_rs->search({ + # 'product_id' => { -in => [ @product_ids ] }, + #}); + + $c->stash(ajax_uri => $c->uri_for_action("/contract/all_contracts_ajax")); +} + +sub all_contracts_root :Chained('all_contracts_list') :PathPart('') :Args(0) { + +} + +sub all_contracts_ajax :Chained('all_contracts_list') :PathPart('ajax') :Args(0) { + my ($self, $c) = @_; + + my $rs = $c->stash->{rs_all_contracts}; + NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{contract_dt_columnsX}); + $c->detach( $c->view("JSON") ); +} 1; diff --git a/lib/NGCP/Panel/Controller/Invoice.pm b/lib/NGCP/Panel/Controller/Invoice.pm index 2908880bd4..2453cfcf3e 100644 --- a/lib/NGCP/Panel/Controller/Invoice.pm +++ b/lib/NGCP/Panel/Controller/Invoice.pm @@ -41,8 +41,9 @@ sub inv_list :Chained('/') :PathPart('invoice') :CaptureArgs(0) :Does(ACL) :ACLD $c->stash->{inv_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ { name => 'id', search => 1, title => $c->loc('#') }, - { name => 'contract.id', search => 1, title => $c->loc('Customer #') }, - { name => 'contract.contact.email', search => 1, title => $c->loc('Customer Email') }, + { name => 'contract.id', search => 1, title => $c->loc('Contract #') }, + { name => 'contract.contact.email', search => 1, title => $c->loc('Contract Email') }, + { name => 'contract.product.name', search => 1, title => $c->loc('Product #') }, { name => 'serial', search => 1, title => $c->loc('Serial') }, ]); @@ -169,9 +170,9 @@ sub create :Chained('inv_list') :PathPart('create') :Args() :Does(ACL) :ACLDetac my $tmpl_id = $form->values->{template}{id}; delete $form->values->{template}; my $period = delete $form->values->{period}; - my($customer,$tmpl,$stime,$etime,$invoice_data); + my($contract,$tmpl,$stime,$etime,$invoice_data); - ($contract_id,$customer,$tmpl,$stime,$etime,$invoice_data) = NGCP::Panel::Utils::Invoice::check_invoice_data($c, { + ($contract,$tmpl,$stime,$etime,$invoice_data) = NGCP::Panel::Utils::Invoice::check_invoice_data($c, { contract_id => $contract_id, tmpl_id => $tmpl_id, period_start => undef, @@ -182,8 +183,7 @@ sub create :Chained('inv_list') :PathPart('create') :Args() :Does(ACL) :ACLDetac $schema->set_transaction_isolation('READ COMMITTED'); $schema->txn_do(sub { NGCP::Panel::Utils::Invoice::create_invoice($c,{ - contract_id => $contract_id, - customer => $customer, + contract => $contract, stime => $stime, etime => $etime, tmpl => $tmpl, diff --git a/lib/NGCP/Panel/Controller/InvoiceTemplate.pm b/lib/NGCP/Panel/Controller/InvoiceTemplate.pm index b8d8dde61d..b1c6fcb8b3 100644 --- a/lib/NGCP/Panel/Controller/InvoiceTemplate.pm +++ b/lib/NGCP/Panel/Controller/InvoiceTemplate.pm @@ -33,6 +33,7 @@ sub template_list :Chained('/') :PathPart('invoicetemplate') :CaptureArgs(0) :Do { name => 'reseller.name', search => 1, title => $c->loc('Reseller') }, { name => 'name', search => 1, title => $c->loc('Name') }, { name => 'type', search => 1, title => $c->loc('Type') }, + { name => 'category', search => 1, title => $c->loc('Category') }, ]); $c->stash(template => 'invoice/template_list.tt'); @@ -119,6 +120,7 @@ sub create :Chained('template_list_restricted') :PathPart('create') :Args() { } } } + $form->process( posted => $posted, params => $c->request->params, @@ -142,6 +144,8 @@ sub create :Chained('template_list_restricted') :PathPart('create') :Args() { $form->values->{reseller_id} = $c->user->reseller_id; } delete $form->values->{reseller}; + + $c->log->debug("roles: " . $c->user->roles); my $dup_item = $schema->resultset('invoice_templates')->find({ reseller_id => $form->values->{reseller_id}, @@ -152,7 +156,7 @@ sub create :Chained('template_list_restricted') :PathPart('create') :Args() { } my $tmpl_params = $form->values; - $tmpl_params->{data} //= NGCP::Panel::Utils::InvoiceTemplate::svg_content($c, $tmpl_params->{data}); + $tmpl_params->{data} //= NGCP::Panel::Utils::InvoiceTemplate::svg_content($c, $tmpl_params->{category}, $tmpl_params->{data}); my $tmpl = $c->stash->{tmpl_rs}->create($tmpl_params); delete $c->session->{created_objects}->{reseller}; @@ -287,7 +291,7 @@ sub get_content_ajax :Chained('base') :PathPart('editcontent/get/ajax') :Args(0) my ($self, $c) = @_; my $tmpl = $c->stash->{tmpl}; - my $content = NGCP::Panel::Utils::InvoiceTemplate::svg_content($c, $tmpl->data); + my $content = NGCP::Panel::Utils::InvoiceTemplate::svg_content($c, $tmpl->category, $tmpl->data); $c->response->content_type('text/html'); $c->response->body($content); diff --git a/lib/NGCP/Panel/Field/AllContracts.pm b/lib/NGCP/Panel/Field/AllContracts.pm new file mode 100644 index 0000000000..76c25078b6 --- /dev/null +++ b/lib/NGCP/Panel/Field/AllContracts.pm @@ -0,0 +1,20 @@ +package NGCP::Panel::Field::AllContracts; +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler::Field::Compound'; + +has_field 'id' => ( + type => '+NGCP::Panel::Field::DataTable', + label => 'Contract', + do_label => 0, + do_wrapper => 0, + required => 1, + template => 'helpers/datatables_field.tt', + ajax_src => '/contract/all_contracts/ajax', + table_titles => ['#', 'Status', 'Contact Email', 'Product'], + table_fields => ['id', 'status', 'contact_email', 'product_name'], +); + +no Moose; +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Field/InvoiceTemplate.pm b/lib/NGCP/Panel/Field/InvoiceTemplate.pm index 10c6d62674..d2166d062a 100644 --- a/lib/NGCP/Panel/Field/InvoiceTemplate.pm +++ b/lib/NGCP/Panel/Field/InvoiceTemplate.pm @@ -10,8 +10,8 @@ has_field 'id' => ( required => 1, ajax_src => '/invoicetemplate/ajax', template => 'helpers/datatables_field.tt', - table_titles => ['#', 'Reseller', 'Name'], - table_fields => ['id', 'reseller_name', 'name'], + table_titles => ['#', 'Reseller', 'Name', 'Category'], + table_fields => ['id', 'reseller_name', 'name', 'category'], ); no Moose; diff --git a/lib/NGCP/Panel/Form/Invoice/Invoice.pm b/lib/NGCP/Panel/Form/Invoice/Invoice.pm index 6c6a5fc839..2dfa5b4859 100644 --- a/lib/NGCP/Panel/Form/Invoice/Invoice.pm +++ b/lib/NGCP/Panel/Form/Invoice/Invoice.pm @@ -21,8 +21,8 @@ has_field 'template' => ( ); has_field 'contract' => ( - type => '+NGCP::Panel::Field::CustomerContract', - label => 'Customer', + type => '+NGCP::Panel::Field::AllContracts', + label => 'Contract', validate_when_empty => 1, element_attr => { rel => ['tooltip'], diff --git a/lib/NGCP/Panel/Form/Invoice/TemplateAdmin.pm b/lib/NGCP/Panel/Form/Invoice/TemplateAdmin.pm index 110751a6d1..e250b4b973 100644 --- a/lib/NGCP/Panel/Form/Invoice/TemplateAdmin.pm +++ b/lib/NGCP/Panel/Form/Invoice/TemplateAdmin.pm @@ -6,7 +6,6 @@ extends 'NGCP::Panel::Form::Invoice::TemplateReseller'; has_field 'reseller' => ( type => '+NGCP::Panel::Field::Reseller', label => 'Reseller', - validate_when_empty => 1, element_attr => { rel => ['tooltip'], title => ['The reseller id to assign this invoice template to.'] @@ -16,9 +15,31 @@ has_field 'reseller' => ( has_block 'fields' => ( tag => 'div', class => [qw/modal-body/], - render_list => [qw/reseller name type call_direction/], + render_list => [qw/reseller name type call_direction category/], ); +sub validate { + my ($self) = @_; + + my $c = $self->ctx; + return unless $c; + + my $category = $self->field('category')->value; + my $reseller_id = $self->field('reseller')->value; + $reseller_id = $reseller_id->{id} if $reseller_id; + + if (($category eq 'customer' + or $category eq 'did') + and not $reseller_id) { + $self->field('reseller')->fields->[0]->add_error($c->loc("Reseller is required for category 'customer' or 'did'")); + } elsif (($category eq 'peer' + or $category eq 'reseller') + and $reseller_id) { + $self->field('reseller')->fields->[0]->add_error($c->loc("Reseller is must be empty for category 'peer' or 'reseller'")); + } + +} + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Invoice/TemplateReseller.pm b/lib/NGCP/Panel/Form/Invoice/TemplateReseller.pm index d761a03e87..3eefc958b5 100644 --- a/lib/NGCP/Panel/Form/Invoice/TemplateReseller.pm +++ b/lib/NGCP/Panel/Form/Invoice/TemplateReseller.pm @@ -49,6 +49,23 @@ has_field 'call_direction' => ( }, ); +has_field 'category' => ( + type => 'Select', + label => 'Category', + required => 1, + options => [ + { label => 'customer', value => 'customer' }, + { label => 'peer', value => 'peer' }, + { label => 'reseller', value => 'reseller' }, + { label => 'did', value => 'did' }, + ], + default => 'customer', + element_attr => { + rel => ['tooltip'], + title => ['The category of the invoice.'], + }, +); + has_field 'save' => ( type => 'Submit', value => 'Save', @@ -59,7 +76,7 @@ has_field 'save' => ( has_block 'fields' => ( tag => 'div', class => [qw/modal-body/], - render_list => [qw/name type call_direction/], + render_list => [qw/name type call_direction category/], ); has_block 'actions' => ( diff --git a/lib/NGCP/Panel/Utils/Contract.pm b/lib/NGCP/Panel/Utils/Contract.pm index b1e395afa2..0455e166e4 100644 --- a/lib/NGCP/Panel/Utils/Contract.pm +++ b/lib/NGCP/Panel/Utils/Contract.pm @@ -174,69 +174,112 @@ sub get_contract_zonesfees_rs { my $contract_id = $params{contract_id}; my $subscriber_uuid = $params{subscriber_uuid}; my $group_detail = $params{group_by_detail}; - - my $zonecalls_rs_out = $c->model('DB')->resultset('cdr')->search( { - 'call_status' => 'ok', - 'source_user_id' => ($subscriber_uuid || { '!=' => '0' }), + my $category = $params{category}; + + my $q = { + call_status => 'ok', start_time => [ -and => { '>=' => $stime->epoch}, { '<=' => $etime->epoch}, ], - source_account_id => $contract_id, - },{ + }; + + my $q_out = { %$q }; + my $q_in = { %$q }; + #my $q_intra = { %$q }; + + my $contract = $c->model('DB')->resultset('contracts')->find({ id => $contract_id }); + my $class; + $class = $contract->product()->class() if $contract; + if ($class) { + if ($class eq 'sippeering' or $class eq 'pstnpeering') { + $category = 'carrier' unless $category; + $q_out->{source_provider_id} = $contract_id; + $q_out->{destination_provider_id} = { '!=' => $contract_id }; + #$q_out->{source_user_id} = { '=' => '0' }; + $q_in->{destination_provider_id} = $contract_id; + $q_in->{source_provider_id} = { '!=' => $contract_id }; + #$q_in->{destination_user_id} = { '=' => '0' }; + #$q_intra->{source_provider_id} = $contract_id; + #$q_intra->{destination_provider_id} = $contract_id; + } elsif ($class eq 'reseller') { + $category = 'reseller' unless $category; + $q_out->{source_provider_id} = $contract_id; + $q_out->{destination_provider_id} = { '!=' => $contract_id }; #no intra reseller calls + #$q_out->{source_user_id} = { '!=' => '0' }; + $q_in->{destination_provider_id} = $contract_id; + $q_in->{source_provider_id} = { '!=' => $contract_id }; #no intra reseller calls + #$q_in->{destination_user_id} = { '!=' => '0' }; + #$q_intra->{source_provider_id} = $contract_id; + #$q_intra->{destination_provider_id} = $contract_id; + } elsif ($class eq 'sipaccount' or $class eq 'pbxaccount') { + $category = 'customer' unless $category; + if ($subscriber_uuid) { + $q_out->{source_user_id} = $subscriber_uuid; + $q_out->{destination_account_id} = { '!=' => $contract_id }; + + $q_in->{destination_user_id} = $subscriber_uuid; + $q_in->{source_account_id} = { '!=' => $contract_id }; + } else { + $q_out->{source_account_id} = $contract_id; + $q_out->{destination_account_id} = { '!=' => $contract_id }; #no intra contract calls + #$q_out->{source_user_id} = { '!=' => '0' }; + $q_in->{destination_account_id} = $contract_id; + $q_in->{source_account_id} = { '!=' => $contract_id }; #no intra contract calls + #$q_in->{destination_user_id} = { '!=' => '0' }; + #$q_intra->{source_account_id} = $contract_id; + #$q_intra->{destination_account_id} = $contract_id; + } + } + } + $category = 'carrier' if $category eq 'peer'; + $category = 'customer' if $category eq 'did'; + + my $zonecalls_rs_out = $c->model('DB')->resultset('cdr')->search($q_out,{ 'select' => [ { sum => 'me.source_customer_cost', -as => 'customercost' }, { sum => 'me.source_carrier_cost', -as => 'carriercost' }, { sum => 'me.source_reseller_cost', -as => 'resellercost' }, - { sum => 'me.source_customer_free_time', -as => 'free_time' }, + { sum => "me.source_" . $category . "_free_time", -as => 'free_time' }, { sum => 'me.duration', -as => 'duration' }, { count => '*', -as => 'number' }, - 'source_customer_billing_zones_history.zone', - $group_detail ? 'source_customer_billing_zones_history.detail' : (), + "source_" . $category . "_billing_zones_history.zone", + $group_detail ? "source_" . $category . "_billing_zones_history.detail" : (), ], 'as' => [ qw/customercost carriercost resellercost free_time duration number zone/, $group_detail ? 'zone_detail' : (), ], - join => 'source_customer_billing_zones_history', + join => "source_" . $category . "_billing_zones_history", group_by => [ - 'source_customer_billing_zones_history.zone', - $group_detail ? 'source_customer_billing_zones_history.detail' : (), + "source_" . $category . "_billing_zones_history.zone", + $group_detail ? "source_" . $category . "_billing_zones_history.detail" : (), ], - order_by => 'source_customer_billing_zones_history.zone', + order_by => "source_" . $category . "_billing_zones_history.zone", } ); - my $zonecalls_rs_in = $c->model('DB')->resultset('cdr')->search( { - 'call_status' => 'ok', - 'destination_user_id' => ($subscriber_uuid || { '!=' => '0' }), - start_time => - [ -and => - { '>=' => $stime->epoch}, - { '<=' => $etime->epoch}, - ], - destination_account_id => $contract_id, - },{ + my $zonecalls_rs_in = $c->model('DB')->resultset('cdr')->search($q_in,{ 'select' => [ { sum => 'me.destination_customer_cost', -as => 'customercost' }, { sum => 'me.destination_carrier_cost', -as => 'carriercost' }, { sum => 'me.destination_reseller_cost', -as => 'resellercost' }, - { sum => 'me.destination_customer_free_time', -as => 'free_time' }, + { sum => "me.destination_" . $category . "_free_time", -as => 'free_time' }, { sum => 'me.duration', -as => 'duration' }, { count => '*', -as => 'number' }, - 'destination_customer_billing_zones_history.zone', - $group_detail ? 'destination_customer_billing_zones_history.detail' : (), + "destination_" . $category . "_billing_zones_history.zone", + $group_detail ? "destination_" . $category . "_billing_zones_history.detail" : (), ], 'as' => [ qw/customercost carriercost resellercost free_time duration number zone/, $group_detail ? 'zone_detail' : (), ], - join => 'destination_customer_billing_zones_history', + join => "destination_" . $category . "_billing_zones_history", group_by => [ - 'destination_customer_billing_zones_history.zone', - $group_detail ? 'destination_customer_billing_zones_history.detail' : (), + "destination_" . $category . "_billing_zones_history.zone", + $group_detail ? "destination_" . $category . "_billing_zones_history.detail" : (), ], - order_by => 'destination_customer_billing_zones_history.zone', + order_by => "destination_" . $category . "_billing_zones_history.zone", } ); return ($zonecalls_rs_in, $zonecalls_rs_out); @@ -285,32 +328,60 @@ sub get_contract_zonesfees { return \%allzones; } -sub get_contract_calls_rs{ +sub get_contract_calls_rs { my %params = @_; - (my($c,$customer_contract_id,$stime,$etime,$call_direction)) = @params{qw/c customer_contract_id stime etime call_direction/}; + my($c,$contract_id,$stime,$etime,$call_direction,$category) = @params{qw/c contract_id stime etime call_direction category/}; $stime ||= NGCP::Panel::Utils::DateTime::current_local()->truncate( to => 'month' ); $etime ||= $stime->clone->add( months => 1 ); - - my @cols = (); - push(@cols,qw/source_user source_domain source_cli destination_user_in/); - #push(@cols,NGCP::Panel::Utils::CallList::get_suppression_id_colnames()); - push(@cols,qw/start_time duration call_type source_customer_cost/); - my @colnames = @cols; - push(@cols,qw/source_customer_billing_zones_history.zone source_customer_billing_zones_history.detail/); - push(@colnames,qw/zone zone_detail/); - my $calls_rs = $c->model('DB')->resultset('cdr')->search({ + my $q = { 'call_status' => 'ok', 'start_time' => [ -and => { '>=' => $stime->epoch}, { '<=' => $etime->epoch}, ], - },{ + }; + + my $contract = $c->model('DB')->resultset('contracts')->find({ id => $contract_id }); + my $class; + $class = $contract->product()->class() if $contract; + my $contract_type; + if ($class) { + if ($class eq 'sippeering' or $class eq 'pstnpeering') { + $category = 'carrier' unless $category; + $contract_type = 'provider'; + #if ($call_direction eq 'in') { + # $call_direction = 'out'; + #} elsif ($call_direction eq 'out') { + # $call_direction = 'in'; + #} + } elsif ($class eq 'reseller') { + $category = 'reseller' unless $category; + $contract_type = 'provider'; + } elsif ($class eq 'sipaccount' or $class eq 'pbxaccount') { + $category = 'customer' unless $category; + $contract_type = 'account'; + } + } + $category = 'carrier' if $category eq 'peer'; + $category = 'customer' if $category eq 'did'; + + my @cols = (); + push(@cols,qw/source_user source_domain source_cli destination_user_in/); + #push(@cols,NGCP::Panel::Utils::CallList::get_suppression_id_colnames()); + push(@cols,qw/start_time duration call_type/); + push(@cols,'source_' . $category . '_cost'); + my @colnames = @cols; + push(@cols,'source_' . $category . '_billing_zones_history.zone'); + push(@cols,'source_' . $category . '_billing_zones_history.detail'); + push(@colnames,qw/zone zone_detail/); + + my $calls_rs = $c->model('DB')->resultset('cdr')->search($q,{ select => \@cols, as => \@colnames, - 'join' => 'source_customer_billing_zones_history', + 'join' => 'source_' . $category . '_billing_zones_history', 'order_by' => 'start_time', } ); @@ -318,21 +389,21 @@ sub get_contract_calls_rs{ if ($call_direction) { if ($call_direction eq "in") { $calls_rs = $calls_rs->search({ - destination_account_id => $customer_contract_id, + 'destination_' . $contract_type . '_id' => $contract_id, }); #suppression rs decoration at last, after any "select =>" return NGCP::Panel::Utils::CallList::call_list_suppressions_rs($c,$calls_rs,NGCP::Panel::Utils::CallList::SUPPRESS_IN); } elsif ($call_direction eq "out") { $calls_rs = $calls_rs->search({ - source_account_id => $customer_contract_id, + 'source_' . $contract_type . '_id' => $contract_id, }); #suppression rs decoration at last, after any "select =>" return NGCP::Panel::Utils::CallList::call_list_suppressions_rs($c,$calls_rs,NGCP::Panel::Utils::CallList::SUPPRESS_OUT); } elsif ($call_direction eq "in_out") { $calls_rs = $calls_rs->search({ -or => [ - { source_account_id => $customer_contract_id }, - { destination_account_id => $customer_contract_id }, + { 'source_' . $contract_type . '_id' => $contract_id }, + { 'destination_' . $contract_type . '_id' => $contract_id }, ], }); #suppression rs decoration at last, after any "select =>" diff --git a/lib/NGCP/Panel/Utils/Invoice.pm b/lib/NGCP/Panel/Utils/Invoice.pm index 2cd67cbf22..07547bc267 100644 --- a/lib/NGCP/Panel/Utils/Invoice.pm +++ b/lib/NGCP/Panel/Utils/Invoice.pm @@ -11,15 +11,23 @@ use HTML::Entities; use Geography::Countries qw/country/; use HTTP::Status qw(:constants); -sub get_invoice_amounts{ +sub get_invoice_amounts { my(%params) = @_; - my($customer_contract,$billing_profile,$contract_balance,$zonecalls) = @params{qw/customer_contract billing_profile contract_balance zonecalls/}; + my($customer_contract,$billing_profile,$contract_balance,$zonecalls,$category) = @params{qw/customer_contract billing_profile contract_balance zonecalls category/}; my $invoice = {}; $billing_profile->{interval_charge} //= 0.0; $customer_contract->{vat_rate} //= 0.0; if ($zonecalls) { $invoice->{amount_net} = 0.0; - map { $invoice->{amount_net} += $_->{customercost}; } values %$zonecalls; + map { + if ($category eq 'customer' or $category eq 'did') { + $invoice->{amount_net} += $_->{customercost}; + } elsif ($category eq 'reseller') { + $invoice->{amount_net} += $_->{resellercost}; + } elsif ($category eq 'peer') { + $invoice->{amount_net} += $_->{carriercost}; + } + } values %$zonecalls; } else { $contract_balance->{cash_balance_interval} //= 0.0; #use Data::Dumper; @@ -51,9 +59,9 @@ sub prepare_contact_data{ #return $contact; } -sub create_invoice{ +sub create_invoice { my($c,$params) = @_; - my($contract_id,$customer,$stime,$etime,$tmpl,$invoice_data) = @$params{qw/contract_id customer stime etime tmpl invoice_data/}; + my($contract,$stime,$etime,$tmpl,$invoice_data) = @$params{qw/contract stime etime tmpl invoice_data/}; my $invoice; @@ -62,64 +70,87 @@ sub create_invoice{ #this has to be refactored - select a contract balance instead of a "period" my $balance = NGCP::Panel::Utils::ProfilePackages::get_contract_balance( c => $c, - contract => $customer, + contract => $contract, stime => $stime, etime => $etime,); $stime = $balance->start; $etime = $balance->end; my $bm_actual = NGCP::Panel::Utils::BillingMappings::get_actual_billing_mapping( c => $c, - contract => $customer, + contract => $contract, now => $balance->start); my $billing_profile = $bm_actual->billing_profile; - my $zonecalls = NGCP::Panel::Utils::Contract::get_contract_zonesfees( + my $zonecalls = {}; + my $did_zonecalls = []; + if ($tmpl->category eq 'did') { + foreach my $subs ($schema->resultset('voip_subscribers')->search({ + contract_id => $contract->id, + #status => { '!=' => 'terminated' }, + #'provisioning_voip_subscriber.is_pbx_group' => 0, + }, #{ + #join => 'provisioning_voip_subscriber', + #} + )->all) { + my $zc = NGCP::Panel::Utils::Contract::get_contract_zonesfees( + c => $c, + contract_id => $contract->id, + stime => $stime, + etime => $etime, + call_direction => $tmpl->call_direction, + group_by_detail => 1, + category => $tmpl->category, + subscriber_uuid => $subs->uuid, + ); + my $s = { $subs->get_inflated_columns }; + $s->{primary_number} = { $subs->primary_number->get_inflated_columns } if $subs->primary_number; + $s->{prov_subscriber} = { $subs->provisioning_voip_subscriber->get_inflated_columns } if $subs->provisioning_voip_subscriber; + push(@$did_zonecalls,{ + subscriber => $s, + zonecalls => $zc, + totalcost => 0.0, + totalduration => 0.0, + }) if scalar keys %$zc; + } + } + $zonecalls = NGCP::Panel::Utils::Contract::get_contract_zonesfees( c => $c, - contract_id => $contract_id, + contract_id => $contract->id, stime => $stime, etime => $etime, call_direction => $tmpl->call_direction, group_by_detail => 1, - ); - my $calllist_rs = NGCP::Panel::Utils::Contract::get_contract_calls_rs( - c => $c, - customer_contract_id => $contract_id, - stime => $stime, - etime => $etime, - call_direction => $tmpl->call_direction, - ); - my $calllist = [ map { - my $call = {$_->get_inflated_columns}; - $call->{start_time} = $call->{start_time}->epoch; - $call->{destination_user_in} =~s/%23/#/g; - #$call->{destination_user_in} = encode_entities($call->{destination_user_in}, '<>&"#'); - $call->{source_customer_cost} += 0.0; # make sure it's a number - NGCP::Panel::Utils::CallList::suppress_cdr_fields($c,$call,$_); - } $calllist_rs->all ]; - - #my $billing_mapping = $customer->billing_mappings->find($customer->get_column('bmid')); - #my $billing_profile = $billing_mapping->billing_profile; - #try { - # $balance = NGCP::Panel::Utils::Contract::get_contract_balance( - # c => $c, - # profile => $billing_profile, - # contract => $customer, - # stime => $stime, - # etime => $etime - # ); - #} catch($e) { - # NGCP::Panel::Utils::Message::error( - # c => $c, - # error => $e, - # desc => $c->loc('Failed to get contract balance.'), - # ); - # die; - #} + category => $tmpl->category, + ); + + my $calllist = []; + if ($tmpl->category eq 'customer') { + my $calllist_rs = NGCP::Panel::Utils::Contract::get_contract_calls_rs( + c => $c, + contract_id => $contract->id, + stime => $stime, + etime => $etime, + call_direction => $tmpl->call_direction, + category => $tmpl->category, + ); + $calllist = [ map { + my $call = {$_->get_inflated_columns}; + $call->{start_time} = $call->{start_time}->epoch; + $call->{destination_user_in} =~s/%23/#/g; + #$call->{destination_user_in} = encode_entities($call->{destination_user_in}, '<>&"#'); + $call->{source_customer_cost} += 0.0; # make sure it's a number + $call->{source_reseller_cost} += 0.0 if exists $call->{source_reseller_cost}; + $call->{source_carrier_cost} += 0.0 if exists $call->{source_carrier_cost}; + NGCP::Panel::Utils::CallList::suppress_cdr_fields($c,$call,$_); + } $calllist_rs->all ]; + } my $invoice_amounts = get_invoice_amounts( - customer_contract => {$customer->get_inflated_columns}, + customer_contract => {$contract->get_inflated_columns}, #support legacy + contract => {$contract->get_inflated_columns}, billing_profile => {$billing_profile->get_inflated_columns}, contract_balance => {$balance->get_inflated_columns}, zonecalls => $zonecalls, + category => $tmpl->category, ); @{$invoice_data}{qw/amount_net amount_vat amount_total/} = @$invoice_amounts{qw/amount_net amount_vat amount_total/}; @@ -156,13 +187,16 @@ sub create_invoice{ # TODO: index 170 seems the upper limit here, then the calllist breaks - $vars->{rescontact} = { $customer->contact->reseller->contract->contact->get_inflated_columns }; - $vars->{customer} = { $customer->get_inflated_columns }; - $vars->{custcontact} = { $customer->contact->get_inflated_columns }; + $vars->{rescontact} = { $contract->contact->reseller->contract->contact->get_inflated_columns } if $contract->contact->reseller; + $vars->{customer} = { $contract->get_inflated_columns }; + $vars->{contract} = { $contract->get_inflated_columns }; + $vars->{custcontact} = { $contract->contact->get_inflated_columns }; + $vars->{contact} = { $contract->contact->get_inflated_columns }; $vars->{billprof} = { $billing_profile->get_inflated_columns }; prepare_contact_data($vars->{billprof}); prepare_contact_data($vars->{custcontact}); + prepare_contact_data($vars->{contact}); prepare_contact_data($vars->{rescontact}); $vars->{invoice} = { @@ -173,6 +207,7 @@ sub create_invoice{ amount_vat => $invoice_data->{amount_vat}, amount_total => $invoice_data->{amount_total}, contract_balance => { $balance->get_inflated_columns }, + call_direction => ($tmpl->call_direction eq 'in' ? 'from' : ($tmpl->call_direction eq 'out' ? 'to' : ($tmpl->call_direction eq 'in_out' ? 'from/to' : ''))), }; $vars->{calls} = $calllist; $vars->{zones} = { @@ -181,9 +216,35 @@ sub create_invoice{ data => [ values(%{ $zonecalls }) ], }; map { - $vars->{zones}->{totalcost} += $_->{customercost}; + if ($tmpl->category eq 'customer' or $tmpl->category eq 'did') { + $vars->{zones}->{totalcost} += $_->{customercost}; + } elsif ($tmpl->category eq 'reseller') { + $vars->{zones}->{totalcost} += $_->{resellercost}; + } elsif ($tmpl->category eq 'peer') { + $vars->{zones}->{totalcost} += $_->{carriercost}; + } $vars->{zones}->{totalduration} += $_->{duration}; } values %$zonecalls; + + map { + my $did_zc = $_; + map { + if ($tmpl->category eq 'customer' or $tmpl->category eq 'did') { + $did_zc->{totalcost} += $_->{customercost}; + } elsif ($tmpl->category eq 'reseller') { + $did_zc->{totalcost} += $_->{resellercost}; + } elsif ($tmpl->category eq 'peer') { + $did_zc->{totalcost} += $_->{carriercost}; + } + $did_zc->{totalduration} += $_->{duration}; + $did_zc->{data} = [ values(%{ delete $did_zc->{zonecalls} }) ]; + } values %{$did_zc->{zonecalls}}; + } @$did_zonecalls; + $vars->{did_zones} = $did_zonecalls; + + #use Data::Dumper; + #$c->log->debug(Dumper($did_zonecalls)); + $t->process(\$svg, $vars, \$out) || do { my $error = $t->error(); my $error_msg = "error processing template, type=".$error->type.", info='".$error->info."'"; @@ -213,56 +274,80 @@ sub create_invoice{ } sub check_invoice_data{ - my($c,$params) = @_; - my($contract_id,$tmpl_id,$period,$period_start,$period_end) = @$params{qw/contract_id tmpl_id period period_start period_end/}; + my ($c,$params) = @_; + my ($contract_id,$tmpl_id,$period,$period_start,$period_end) = @$params{qw/contract_id tmpl_id period period_start period_end/}; my $invoice_data = {}; my $schema = $c->model('DB'); - my $customer = NGCP::Panel::Utils::Contract::get_customer_rs(c => $c)->find({ 'me.id' => $contract_id }); - unless($customer) { - die { - showdetails => $c->loc('Customer not found'), - error => "invalid contract_id $contract_id", - httpcode => HTTP_UNPROCESSABLE_ENTITY, - }; - } - $invoice_data->{contract_id} = $contract_id; - + my $tmpl = $schema->resultset('invoice_templates')->search({ id => $tmpl_id, }); - if($c->user->roles eq "admin") { - } elsif($c->user->roles eq "reseller") { + if ($c->user->roles eq "admin") { + } elsif ($c->user->roles eq "reseller") { $tmpl = $tmpl->search({ reseller_id => $c->user->reseller_id, }); } $tmpl = $tmpl->first; - unless($tmpl) { + unless ($tmpl) { die { showdetails => $c->loc('Invoice template not found'), error => "invalid template id $tmpl_id", httpcode => HTTP_UNPROCESSABLE_ENTITY, }; } - unless($tmpl->data) { + unless ($tmpl->data) { die { showdetails => $c->loc('Invoice template does not have an SVG stored yet'), error => "invalid template id $tmpl_id, data is empty", httpcode => HTTP_UNPROCESSABLE_ENTITY, }; } + + my $contract; + if ('customer' eq $tmpl->category or 'did' eq $tmpl->category) { + $contract = NGCP::Panel::Utils::Contract::get_customer_rs(c => $c)->find({ 'me.id' => $contract_id }); + unless($contract) { + die { + showdetails => $c->loc('Customer not found'), + error => "invalid contract_id $contract_id", + httpcode => HTTP_UNPROCESSABLE_ENTITY, + }; + } + + unless($contract->contact->reseller_id == $tmpl->reseller_id) { + die { + showdetails => $c->loc('Template and customer must belong to same reseller'), + error => "template id ".$tmpl->id." has different reseller than contract id $contract_id", + httpcode => HTTP_UNPROCESSABLE_ENTITY, + }; + } + } else { + + my @product_ids = map { $_->id; } $schema->resultset('products')->search_rs({ 'class' => ['pstnpeering','sippeering','reseller'] })->all; + $contract = NGCP::Panel::Utils::Contract::get_contract_rs(c => $c)->search_rs({ + 'me.id' => $contract_id, + 'product_id' => { -in => [ @product_ids ] }, + },{ + join => 'contact', + })->first; + + unless($contract) { + die { + showdetails => $c->loc('Contract not found'), + error => "invalid contract_id $contract_id", + httpcode => HTTP_UNPROCESSABLE_ENTITY, + }; + } - unless($customer->contact->reseller_id == $tmpl->reseller_id) { - die { - showdetails => $c->loc('Template and customer must belong to same reseller'), - error => "template id ".$tmpl->id." has different reseller than contract id $contract_id", - httpcode => HTTP_UNPROCESSABLE_ENTITY, - }; } + + $invoice_data->{contract_id} = $contract_id; + #$invoice_data->{category} = $tmpl->category; my $stime = $period_start ? NGCP::Panel::Utils::DateTime::from_string( $period_start @@ -273,7 +358,8 @@ sub check_invoice_data{ $period_end ) : $stime->clone->add(months => 1)->subtract(seconds => 1); - return($contract_id,$customer,$tmpl,$stime,$etime,$invoice_data); + return ($contract,$tmpl,$stime,$etime,$invoice_data); + } -1; -# vim: set tabstop=4 expandtab: + +1; \ No newline at end of file diff --git a/lib/NGCP/Panel/Utils/InvoiceTemplate.pm b/lib/NGCP/Panel/Utils/InvoiceTemplate.pm index 41262f2faa..8cd0d3333f 100644 --- a/lib/NGCP/Panel/Utils/InvoiceTemplate.pm +++ b/lib/NGCP/Panel/Utils/InvoiceTemplate.pm @@ -159,18 +159,20 @@ sub get_tt { } sub svg_content{ - my ($c, $content) = @_; + my ($c, $category, $content) = @_; unless ($content) { #default is the same for all - I would like to move it as something constant to itils - my $default = 'invoice/default/invoice_template_svg.tt'; + my $default = 'invoice/default/' . $category . '_invoice_template_svg.tt'; my $t = NGCP::Panel::Utils::InvoiceTemplate::get_tt(); try { $content = $t->context->insert($default); } catch($e) { # TODO: handle error! - $c and $c->log->error("failed to load default invoice template: $e"); + my $msg = "failed to load default $category invoice template: $e"; + $c and $c->log->error($msg); + die($msg); return; } } diff --git a/share/templates/invoice/default/invoice_template_svg.tt b/share/templates/invoice/default/customer_invoice_template_svg.tt similarity index 97% rename from share/templates/invoice/default/invoice_template_svg.tt rename to share/templates/invoice/default/customer_invoice_template_svg.tt index 3997545e97..93950b65fe 100644 --- a/share/templates/invoice/default/invoice_template_svg.tt +++ b/share/templates/invoice/default/customer_invoice_template_svg.tt @@ -6,11 +6,11 @@ # money_signs = 3; PROCESS "invoice/default/invoice_template_aux.tt"; - money_format(amount=(billprof.interval_charge * 100), comma='.'); fixfee = aux.val; + money_format(amount=(billprof.interval_charge), comma='.'); fixfee = aux.val; money_format(amount=(zones.totalcost), comma='.'); zonefee = aux.val; - money_format(amount=(invoice.amount_net * 100), comma='.'); netfee = aux.val; - money_format(amount=(invoice.amount_vat * 100), comma='.'); vatfee = aux.val; - money_format(amount=(invoice.amount_total * 100), comma='.'); allfee = aux.val; + money_format(amount=(invoice.amount_net), comma='.'); netfee = aux.val; + money_format(amount=(invoice.amount_vat), comma='.'); vatfee = aux.val; + money_format(amount=(invoice.amount_total), comma='.'); allfee = aux.val; cur = billprof.currency; p_start = date_format(thedate=invoice.period_start, format='%Y-%m-%d'); p_end = date_format(thedate=invoice.period_end, format='%Y-%m-%d'); @@ -25,7 +25,7 @@ [% rescontact.company %] [% rescontact.street %] - [% rescontact.postcode %] [% custcontact.city %] + [% rescontact.postcode %] [% rescontact.city %] [% rescontact.country %] Company Reg.Nr.: [% rescontact.comregnum %] @@ -82,7 +82,7 @@ - Call Summary + Calls [% invoice.call_direction %] other Customers and Subscribers Zone Quantity diff --git a/share/templates/invoice/default/did_invoice_template_svg.tt b/share/templates/invoice/default/did_invoice_template_svg.tt new file mode 100644 index 0000000000..19fc45061b --- /dev/null +++ b/share/templates/invoice/default/did_invoice_template_svg.tt @@ -0,0 +1,159 @@ + + + + + + Background + + + [% rescontact.company %] + [% rescontact.street %] + [% rescontact.postcode %] [% rescontact.city %] + [% rescontact.country %] + + Company Reg.Nr.: [% rescontact.comregnum %] + VAT.Nr.: [% rescontact.vatnum %] + IBAN: [% rescontact.iban %] + BIC: [% rescontact.bic %] + Page [% aux.page %] + + + + + + + + + Summary + + + [% custcontact.firstname %] [% custcontact.lastname %] + [% custcontact.street %] + [% custcontact.postcode %] [% custcontact.city %] + [% custcontact.country %] + + Invoice Nr. + [% invoice.serial %] + Customer Nr. + [% customer.external_id %] + Invoice Period + [% p_start %] - [% p_end %] + Date + [% date_now(format='%Y-%m-%d') %] + + Your Monthly Statement + + Dear Customer, + For our services provided in the period of [% p_start %] to [% p_end %], we invoice the following items: + + + Recurring Fees + + Name + Quantity + Unit Price + Total Price in [% cur %] + + [% billprof.name %] + 1 + [% fixfee %] + [% fixfee %] + + Total + [% fixfee %] + + + + + Calls [% invoice.call_direction %] other Customers and Subscribers + + Zone + Quantity + Usage + Total Price in [% cur %] + + + + + + + + Total + [% zonefee %] + + + + + + + + + Summary + in [% cur %] + + Total Summary + [% netfee %] + VAT ([% customer.vat_rate %]%) + [% vatfee %] + + Amount Due + [% allfee %] + + + The amount is automatically charged via SEPA within 30 days using Mandate ID MID12345 and Creditor ID CID12345 + from your account with IBAN [% rescontact.iban %] and BIC [% rescontact.bic %]. + With best regards, + Your [% rescontact.company %] Service Team + + + + + + + + + DID ZoneCalls + + + [% did.subscriber.username %]: Calls [% invoice.call_direction %] other Customers and Subscribers + + Zone + Quantity + Usage + Total Price in [% cur %] + + + + + + + + Total + [% zonefee %] + + + + + + + + + + diff --git a/share/templates/invoice/default/invoice_template_aux.tt b/share/templates/invoice/default/invoice_template_aux.tt index ddcab3ba13..70739b2268 100644 --- a/share/templates/invoice/default/invoice_template_aux.tt +++ b/share/templates/invoice/default/invoice_template_aux.tt @@ -67,6 +67,7 @@ END; aux.lasty = y; END; + MACRO apply_units(item) BLOCK; IF server_process_units == 'none'; ''; diff --git a/share/templates/invoice/default/peer_invoice_template_svg.tt b/share/templates/invoice/default/peer_invoice_template_svg.tt new file mode 100644 index 0000000000..b4369ebf83 --- /dev/null +++ b/share/templates/invoice/default/peer_invoice_template_svg.tt @@ -0,0 +1,129 @@ + + + + + + Background + + + OPERATOR COMPANY + OPERATOR STREET + OPERATOR POSTCODE OPERATOR CITY + OPERATOR COUNTRY + + Company Reg.Nr.: OPERATOR COMPREGNUM + VAT.Nr.: OPERATOR VATNUM + IBAN: OPERATOR IBAN + BIC: OPERATOR BIC + Page [% aux.page %] + + + + + + + + + Summary + + + [% contact.company %] + [% contact.street %] + [% contact.postcode %] [% contact.city %] + [% contact.country %] + + Invoice Nr. + [% invoice.serial %] + Peering Contract Nr. + [% contract.external_id %] + Invoice Period + [% p_start %] - [% p_end %] + Date + [% date_now(format='%Y-%m-%d') %] + + Your Monthly Statement + + Dear Peering Partner, + For our services provided in the period of [% p_start %] to [% p_end %], we invoice the following items: + + + Recurring Fees + + Name + Quantity + Unit Price + Total Price in [% cur %] + + [% billprof.name %] + 1 + [% fixfee %] + [% fixfee %] + + Total + [% fixfee %] + + + + + Peer Calls [% invoice.call_direction %] Platform Resellers and other Peers + + Zone + Quantity + Usage + Total Price in [% cur %] + + + + + + + + Total + [% zonefee %] + + + + + + + + + Summary + in [% cur %] + + Total Summary + [% netfee %] + VAT ([% contract.vat_rate %]%) + [% vatfee %] + + Amount Due + [% allfee %] + + + The amount is automatically charged via SEPA within 30 days using Mandate ID MID12345 and Creditor ID CID12345 + from your account with IBAN [% rescontact.iban %] and BIC [% rescontact.bic %]. + With best regards, + Your [% rescontact.company %] Service Team + + + + + + diff --git a/share/templates/invoice/default/reseller_invoice_template_svg.tt b/share/templates/invoice/default/reseller_invoice_template_svg.tt new file mode 100644 index 0000000000..186f9b798a --- /dev/null +++ b/share/templates/invoice/default/reseller_invoice_template_svg.tt @@ -0,0 +1,129 @@ + + + + + + Background + + + OPERATOR COMPANY + OPERATOR STREET + OPERATOR POSTCODE OPERATOR CITY + OPERATOR COUNTRY + + Company Reg.Nr.: OPERATOR COMPREGNUM + VAT.Nr.: OPERATOR VATNUM + IBAN: OPERATOR IBAN + BIC: OPERATOR BIC + Page [% aux.page %] + + + + + + + + + Summary + + + [% contact.company %] + [% contact.street %] + [% contact.postcode %] [% contact.city %] + [% contact.country %] + + Invoice Nr. + [% invoice.serial %] + Reseller Contract Nr. + [% contract.external_id %] + Invoice Period + [% p_start %] - [% p_end %] + Date + [% date_now(format='%Y-%m-%d') %] + + Your Monthly Statement + + Dear Reseller, + For our services provided in the period of [% p_start %] to [% p_end %], we invoice the following items: + + + Recurring Fees + + Name + Quantity + Unit Price + Total Price in [% cur %] + + [% billprof.name %] + 1 + [% fixfee %] + [% fixfee %] + + Total + [% fixfee %] + + + + + Calls [% invoice.call_direction %] other Platform Resellers and Peers + + Zone + Quantity + Usage + Total Price in [% cur %] + + + + + + + + Total + [% zonefee %] + + + + + + + + + Summary + in [% cur %] + + Total Summary + [% netfee %] + VAT ([% contract.vat_rate %]%) + [% vatfee %] + + Amount Due + [% allfee %] + + + The amount is automatically charged via SEPA within 30 days using Mandate ID MID12345 and Creditor ID CID12345 + from your account with IBAN [% rescontact.iban %] and BIC [% rescontact.bic %]. + With best regards, + Your [% rescontact.company %] Service Team + + + + + +