package NGCP::Panel::Controller::Invoice; use Sipwise::Base; use namespace::sweep; BEGIN { extends 'Catalyst::Controller'; } use NGCP::Panel::Utils::Message; use NGCP::Panel::Utils::DateTime; use NGCP::Panel::Utils::Contract; use NGCP::Panel::Utils::InvoiceTemplate; use NGCP::Panel::Utils::Invoice; use NGCP::Panel::Form::Invoice::Invoice; sub auto :Private { my ($self, $c) = @_; $c->log->debug(__PACKAGE__ . '::auto'); NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c); return 1; } sub inv_list :Chained('/') :PathPart('invoice') :CaptureArgs(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(subscriberadmin) { my ( $self, $c ) = @_; $c->stash->{inv_rs} = $c->model('DB')->resultset('invoices'); if($c->user->roles eq "admin") { } elsif($c->user->roles eq "reseller") { $c->stash->{inv_rs} = $c->stash->{inv_rs}->search({ 'contact.reseller_id' => $c->user->reseller_id, },{ join => { contract => 'contact' }, }); } elsif($c->user->roles eq "subscriberadmin") { $c->stash->{inv_rs} = $c->stash->{inv_rs}->search({ contract_id => $c->user->account_id, }); }; $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 => 'serial', search => 1, title => $c->loc('Serial') }, ]); $c->stash(template => 'invoice/invoice_list.tt'); } sub customer_inv_list :Chained('/') :PathPart('invoice/customer') :CaptureArgs(1) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(subscriberadmin) { my ( $self, $c, $contract_id ) = @_; $c->stash->{inv_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ { name => "id", search => 1, title => $c->loc("#") }, { name => "serial", search => 1, title => $c->loc("Serial #") }, { name => "period_start", search => 1, title => $c->loc("Start") }, { name => "period_end", search => 1, title => $c->loc("End") }, { name => "amount_net", search => 1, title => $c->loc("Net Amount") }, { name => "amount_vat", search => 1, title => $c->loc("VAT Amount") }, { name => "amount_total", search => 1, title => $c->loc("Total Amount") }, ]); unless($contract_id && $contract_id->is_int) { NGCP::Panel::Utils::Message->error( c => $c, error => "Invalid contract id $contract_id found", desc => $c->loc('Invalid contract id found'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/sound')); } if($c->user->roles eq "subscriberadmin" && $c->user->account_id != $contract_id) { NGCP::Panel::Utils::Message->error( c => $c, error => "access violation, subscriberadmin ".$c->user->uuid." with contract id ".$c->user->account_id." tries to access foreign contract id $contract_id", desc => $c->loc('Invalid contract id found'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/sound')); } my $contract = $c->model('DB')->resultset('contracts')->find($contract_id); unless($contract) { NGCP::Panel::Utils::Message->error( c => $c, error => "Contract id $contract_id not found", desc => $c->loc('Invalid contract id detected'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/sound')); } $c->stash(inv_rs => $c->model('DB')->resultset('invoices')->search({ contract_id => $contract->id, })); $c->stash(template => 'sound/list.tt'); return; } sub root :Chained('inv_list') :PathPart('') :Args(0) { my ($self, $c) = @_; } sub ajax :Chained('inv_list') :PathPart('ajax') :Args(0) { my ($self, $c) = @_; my $rs = $c->stash->{inv_rs}; NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{inv_dt_columns}); $c->detach( $c->view("JSON") ); } sub customer_ajax :Chained('customer_inv_list') :PathPart('ajax') :Args(0) { my ($self, $c) = @_; my $rs = $c->stash->{inv_rs}; NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{inv_dt_columns}); $c->detach( $c->view("JSON") ); } sub base :Chained('inv_list') :PathPart('') :CaptureArgs(1) { my ($self, $c, $inv_id) = @_; unless($inv_id && $inv_id->is_integer) { NGCP::Panel::Utils::Message->error( c => $c, log => 'Invalid invoice id detected', desc => $c->loc('Invalid invoice id detected'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoice')); } my $res = $c->stash->{inv_rs}->find($inv_id); unless(defined($res)) { NGCP::Panel::Utils::Message->error( c => $c, log => 'Invoice does not exist', desc => $c->loc('Invoice does not exist'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoice')); } $c->stash(inv => $res); } sub create :Chained('inv_list') :PathPart('create') :Args() :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) { my ($self, $c) = @_; my $posted = ($c->request->method eq 'POST'); my $params = {}; $params = $params->merge($c->session->{created_objects}); my $form; $form = NGCP::Panel::Form::Invoice::Invoice->new(ctx => $c); $form->process( posted => $posted, params => $c->request->params, item => $params, ); NGCP::Panel::Utils::Navigation::check_form_buttons( c => $c, form => $form, fields => { }, back_uri => $c->req->uri, ); if($posted && $form->validated) { try { my $schema = $c->model('DB'); $schema->txn_do(sub { my $contract_id = $form->params->{contract}{id}; my $customer_rs = NGCP::Panel::Utils::Contract::get_customer_rs(c => $c); my $customer = $customer_rs->find({ 'me.id' => $contract_id }); unless($customer) { NGCP::Panel::Utils::Message->error( c => $c, error => "invalid contract_id $contract_id", desc => $c->loc('Customer not found'), ); die; } delete $form->params->{contract}; $form->params->{contract_id} = $contract_id; my $tmpl_id = $form->params->{template}{id}; delete $form->params->{template}; my $tmpl = $schema->resultset('invoice_templates')->search({ id => $tmpl_id, }); 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) { NGCP::Panel::Utils::Message->error( c => $c, error => "invalid template id $tmpl_id", desc => $c->loc('Invoice template not found'), ); die; } unless($tmpl->data) { NGCP::Panel::Utils::Message->error( c => $c, error => "invalid template id $tmpl_id, data is empty", desc => $c->loc('Invoice template does not have an SVG stored yet'), ); die; } unless($customer->contact->reseller_id == $tmpl->reseller_id) { NGCP::Panel::Utils::Message->error( c => $c, error => "template id ".$tmpl->id." has different reseller than contract id $contract_id", desc => $c->loc('Template and customer must belong to same reseller'), ); die; } my $stime = NGCP::Panel::Utils::DateTime::from_string( delete $form->params->{period} )->truncate(to => 'month'); my $etime = $stime->clone->add(months => 1)->subtract(seconds => 1); my $zonecalls = NGCP::Panel::Utils::Contract::get_contract_zonesfees( c => $c, contract_id => $contract_id, stime => $stime, etime => $etime, in => 0, out => 1, 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, ); my $calllist = [ map { my $call = {$_->get_inflated_columns}; $call->{start_time} = $call->{start_time}->epoch; $call->{source_customer_cost} += 0.0; # make sure it's a number $call; } $calllist_rs->all ]; my $billing_mapping = $customer->billing_mappings->find($customer->get_column('bmid')); my $billing_profile = $billing_mapping->billing_profile; my $balance; 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; } my $invoice_amounts = NGCP::Panel::Utils::Invoice::get_invoice_amounts( customer_contract => {$customer->get_inflated_columns}, billing_profile => {$billing_profile->get_inflated_columns}, contract_balance => {$balance->get_inflated_columns}, ); @{$form->params}{qw/amount_net amount_vat amount_total/} = @$invoice_amounts{qw/amount_net amount_vat amount_total/}; # generate tmp serial here, derive one from after insert $form->params->{serial} = "tmp".time.int(rand(99999)); $form->params->{data} = undef; #maybe inflation should be applied? Generation failed here, although the latest schema applied. $form->params->{period_start} = $stime->ymd.' '. $stime->hms; $form->params->{period_end} = $etime->ymd.' '. $etime->hms; my $invoice; try { $invoice = $schema->resultset('invoices')->create($form->params); } catch($e) { NGCP::Panel::Utils::Message->error( c => $c, error => $e, desc => $c->loc('Failed to save invoice meta data.'), ); die; } #sprintf("INV%04d%02d%07d", $stime->year, $stime->month, $invoice->id); #to make it unified for web and cron script my $serial = NGCP::Panel::Utils::Invoice::get_invoice_serial($c,{ invoice=>{ period_start => $stime, period_end => $etime, id => $invoice->id, }}); my $svg = $tmpl->data; utf8::decode($svg); my $t = NGCP::Panel::Utils::InvoiceTemplate::get_tt(); my $out = ''; my $pdf = ''; my $vars = {}; # 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 }; NGCP::Panel::Utils::Invoice::prepare_contact_data($vars->{custcontact}); NGCP::Panel::Utils::Invoice::prepare_contact_data($vars->{rescontact}); $vars->{billprof} = { $billing_profile->get_inflated_columns }; $vars->{invoice} = { period_start => $stime, period_end => $etime, serial => $serial, amount_net => $form->params->{amount_net}, amount_vat => $form->params->{amount_vat}, amount_total => $form->params->{amount_total}, }; $vars->{calls} = $calllist, $vars->{zones} = { totalcost => $balance->cash_balance_interval, data => [ values(%{ $zonecalls }) ], }; try { $t->process(\$svg, $vars, \$out) || do { my $error = $t->error(); my $msg = "error processing template, type=".$error->type.", info='".$error->info."'"; NGCP::Panel::Utils::Message->error( c => $c, log => $msg, desc => $c->loc('Failed to render template. Type is [_1], info is [_2].', $error->type, $error->info), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoice')); return; }; NGCP::Panel::Utils::InvoiceTemplate::preprocess_svg(\$out); NGCP::Panel::Utils::InvoiceTemplate::svg_pdf($c, \$out, \$pdf); } catch($e) { NGCP::Panel::Utils::Message->error( c => $c, log => $e, desc => $c->loc('Failed to render invoice'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoice')); return; } $invoice->update({ serial => $serial, data => $pdf, }); NGCP::Panel::Utils::Message->info( c => $c, cname => 'create', log => $vars->{invoice}, desc => $c->loc('Invoice #[_1] successfully created', $invoice->id), ); }); } catch($e) { NGCP::Panel::Utils::Message->error( c => $c, error => $e, desc => $c->loc('Failed to create invoice.'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoice')); } $c->stash(form => $form); $c->stash(create_flag => 1); } sub delete :Chained('base') :PathPart('delete') { my ($self, $c) = @_; try { my $schema = $c->model('DB'); $schema->txn_do(sub{ $c->stash->{inv}->delete; }); NGCP::Panel::Utils::Message->info( c => $c, data => { $c->stash->{inv}->get_inflated_columns }, desc => $c->loc('Invoice successfully deleted'), ); } catch($e) { NGCP::Panel::Utils::Message->error( c => $c, error => $e, desc => $c->loc('Failed to delete invoice .'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoice')); } sub download :Chained('base') :PathPart('download') { my ($self, $c) = @_; try { $c->response->content_type('application/pdf'); $c->response->body($c->stash->{inv}->data); return; } catch($e) { NGCP::Panel::Utils::Message->error( c => $c, error => $e, desc => $c->loc('Failed to delete invoice .'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoice')); } __PACKAGE__->meta->make_immutable; 1; # vim: set tabstop=4 expandtab: