From b6533b1a1f850fd7e25a1aa600a5df871b719c59 Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Mon, 2 Jun 2014 16:25:08 +0200 Subject: [PATCH] MT#5879 Redesign invoice template meta handling. Properly handle create/update/delete of template meta info. --- lib/NGCP/Panel/Controller/Invoice.pm | 1058 ----------------- lib/NGCP/Panel/Controller/InvoiceTemplate.pm | 259 ++++ lib/NGCP/Panel/Controller/NumberBlock.pm | 2 +- lib/NGCP/Panel/Controller/Reseller.pm | 11 +- lib/NGCP/Panel/Form/Invoice/TemplateAdmin.pm | 25 + .../Panel/Form/Invoice/TemplateReseller.pm | 68 ++ .../invoice/invoice_template_lorem.tt | 51 +- .../templates/invoice/invoice_template_svg.tt | 10 +- share/templates/invoice/template_list.tt | 89 +- share/templates/reseller/details.tt | 67 +- 10 files changed, 455 insertions(+), 1185 deletions(-) delete mode 100644 lib/NGCP/Panel/Controller/Invoice.pm create mode 100644 lib/NGCP/Panel/Controller/InvoiceTemplate.pm create mode 100644 lib/NGCP/Panel/Form/Invoice/TemplateAdmin.pm create mode 100644 lib/NGCP/Panel/Form/Invoice/TemplateReseller.pm diff --git a/lib/NGCP/Panel/Controller/Invoice.pm b/lib/NGCP/Panel/Controller/Invoice.pm deleted file mode 100644 index 54a13b5f6d..0000000000 --- a/lib/NGCP/Panel/Controller/Invoice.pm +++ /dev/null @@ -1,1058 +0,0 @@ -package NGCP::Panel::Controller::Invoice; -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 JSON; -use Number::Phone; -use URI::Encode; - -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::Form::Invoice::Send; -use NGCP::Panel::Form::Invoice::Basic; -use NGCP::Panel::Model::DB::InvoiceTemplate; -use NGCP::Panel::Utils::InvoiceTemplate; - -sub auto :Private { - my ($self, $c) = @_; - $c->log->debug(__PACKAGE__ . '::auto'); - NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c); - return 1; -} - -sub invoice :Chained('/') :PathPart('invoice') :CaptureArgs(0) { - my ($self, $c) = @_; -} - -sub root :Chained('invoice') :PathPart('') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) -{ - my ($self, $c) = @_; -} - -sub messages :Chained('invoice') :PathPart('messages') :Args(0) { - my ($self, $c) = @_; - $c->log->debug('messages'); - $c->stash( - messages => $c->flash->{messages} , - template => 'helpers/ajax_messages.tt' ); - $c->detach( $c->view('SVG') );#no wrapper view -} - -sub ajax_datatables_data :Chained('base') :PathPart('ajax') :Args(1) { - my ($self, $c, $item ) = @_; - my $dt_columns_json = $c->request->parameters->{dt_columns}; - $c->forward( $item ); - my $dt_columns = from_json($dt_columns_json); - NGCP::Panel::Utils::Datatables::process($c, $c->stash->{$item.'_ajax'}, $dt_columns ); - $c->detach( $c->view("JSON") ); -} - -sub base :Chained('invoice') :PathPart('') :CaptureArgs(1) { - my ($self, $c, $reseller_id) = @_; - $c->log->debug("base: reseller_id=$reseller_id; uri_for=".$c->uri_for().";"); - - my $error_exit_sub = sub { - my($log, $desc) = @_; - $desc //= $log; - NGCP::Panel::Utils::Message->error( - c => $c, - log => $log, - desc => $c->loc($desc), - ); - NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/reseller')); - #$c->response->redirect($c->uri_for('/reseller')); - $c->detach(); - }; - unless($reseller_id && $reseller_id->is_int) { - $error_exit_sub->('Invalid reseller id detected'); - return; - } - - $c->detach('/denied_page') - if($c->user->roles eq "reseller" && $c->user->reseller_id != $reseller_id); - - - my $reseller = $c->model('DB')->resultset('resellers')->search({ - status => { '!=' => 'terminated' }, - id => $reseller_id - }); - unless($reseller->first) { - NGCP::Panel::Utils::Message->error( - c => $c, - log => 'Reseller not found', - desc => $c->loc('Reseller not found'), - ); - NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/reseller')); - } - - my $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - my $in = $c->request->parameters; - $in->{provider_id} = $reseller_id; - $in->{c} = $c; - - foreach(qw/provider_id client_contract_id client_contact_id invoice_id tt_id/){ $in->{$_} //= ''; $c->log->debug("base: $_=".$in->{$_}.";");} - - - if($in->{client_contact_id}){ - if(!$backend->checkResellerClientContact($in)){ - $error_exit_sub->('Invalid contact id detected'); - return; - } - } - if($in->{client_contract_id}){ - if(!$backend->checkResellerClientContract($in)){ - $error_exit_sub->('Invalid contract id detected'); - return; - } - } - if($in->{invoice_id}){ - if(!$backend->checkResellerInvoice($in)){ - $error_exit_sub->('Invalid invoice id detected'); - return; - } - } - if($in->{tt_id}){ - if(!$backend->checkResellerInvoiceTemplate($in)){ - $error_exit_sub->('Invalid invoice template id detected'); - return; - } - } - $c->stash( - provider => $reseller->first, - provider_rs => $reseller, - contract => $reseller->search_rs({ id => $reseller_id })->first->contract - ); -} - - -sub invoice_details_zones :Chained('base') :PathPart('') :CaptureArgs(0) { - my ($self, $c, $in) = @_; - $c->log->debug('invoice_details_zones'); - my $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - my $provider_id = $c->stash->{provider}->id; - my $stime = $in->{stime} || NGCP::Panel::Utils::DateTime::current_local()->truncate(to => 'month'); - my $etime = $in->{etime} || $stime->clone->add(months => 1); - - #look, NGCP::Panel::Utils::Contract - it is kind of backend separation here - #my $form = NGCP::Panel::Form::Invoice::Template->new( ); - my $invoice_details_zones = $backend->get_contract_zonesfees_rs( - c => $c, - provider_id => $provider_id, - %$in, - stime => $stime, - etime => $etime, - ); - #TODO: FAKE FAKE FAKE FAKE - my $invoice_details_zones_ajax = $invoice_details_zones; - $invoice_details_zones = [$invoice_details_zones_ajax->all()]; - my $i = 1; - $invoice_details_zones = [map{[$i++,$_]} (@$invoice_details_zones) x 1]; - $c->stash( invoice_details_zones => $invoice_details_zones ); - $c->stash( invoice_details_zones_ajax => $invoice_details_zones_ajax ); -} - -sub invoice_details_calls :Chained('invoice_details_zones') :PathPart('') :CaptureArgs(0) { - my ($self, $c, $in) = @_; - $c->log->debug('invoice_details_calls'); - my $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - my $provider_id = $c->stash->{provider}->id; - my $stime = $in->{stime} || NGCP::Panel::Utils::DateTime::current_local()->truncate(to => 'month'); - my $etime = $in->{etime} || $stime->clone->add(months => 1); - - #look, NGCP::Panel::Utils::Contract - it is kind of backend separation here - #my $form = NGCP::Panel::Form::Invoice::Template->new( ); - my $invoice_details_calls = $backend->get_contract_calls_rs( - c => $c, - provider_id => $provider_id, - %$in, - stime => $stime, - etime => $etime, - ); - #$invoice_details_calls - #TODO: FAKE FAKE FAKE FAKE - my $invoice_details_calls_ajax = $invoice_details_calls; - #foreach my $call(@$invoice_details_calls_ajax) { - # next unless($call->source_cli && $call->source_cli =~ /^\d{5,}$/ && - # $call->destination_user_in && $call->destination_user_in =~ /^\d{5,}$/); - # my $s = Number::Phone->new($call->source_cli); - # my $d = Number::Phone->new($call->destination_user_in); - # next unless($s && $d); - #} - - $invoice_details_calls = [$invoice_details_calls_ajax->all()]; - my $i = 1; - $invoice_details_calls = [map{[$i++,$_]} (@$invoice_details_calls) x 1]; - $c->stash( invoice_details_calls => $invoice_details_calls ); - $c->stash( invoice_details_calls_ajax => $invoice_details_calls_ajax ); -} - -sub invoice_list :Chained('base') :PathPart('list') :Args(0) { - my ($self, $c) = @_; - my $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - $c->log->debug('invoice_list'); - #$c->forward( 'template_list_data' ); - my $provider_id = $c->stash->{provider}->id; - my $invoice_list = $backend->getProviderInvoiceList( - provider_id => $provider_id, - ); - $c->view('SVG')->getTemplateVars($c); - $c->stash( - client_contacts_list => $backend->getInvoiceProviderClients( provider_id => $provider_id ), - invoice_list => [$invoice_list->all], - #invoice_list_ajax => $invoice_list, - template => 'invoice/list.tt', - ); - #$c->detach( $c->view() ); -} -sub invoice_list_data :Chained('base') :PathPart('list') :Args(0) { - my ($self, $c) = @_; - my $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - $c->log->debug('invoice_list_data'); - my $provider_id = $c->stash->{provider}->id; - my $client_contact_id = $c->request->parameters->{client_contact_id}; - my $invoice_list_ajax = $backend->getProviderInvoiceListAjax( - provider_id => $provider_id, - $client_contact_id ? ( client_contact_id => $client_contact_id):(), - ); - $c->stash( - invoice_list_data_ajax => $invoice_list_ajax, - ); - #$c->detach( $c->view() ); -} - -sub provider_client_list :Chained('base') :PathPart('clients/list') :Args(0) { - my ($self, $c) = @_; - my $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - $c->log->debug('provider_client_list'); - my $provider_id = $c->stash->{provider}->id; - my $provider_client_list_ajax = $backend->getInvoiceProviderClients( - provider_id => $provider_id, - ); - $c->stash( - provider_client_list_ajax => $provider_client_list_ajax, - ); - #$c->detach( $c->view() ); -} - -sub invoice_data :Chained('base') :PathPart('data') :Args(1) { - my ($self, $c) = @_; - my ($invoice_id) = pop; - $c->log->debug('invoice_data'); - my $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - my $invoice = $backend->getInvoice(invoice_id => $invoice_id); - $c->response->content_type('application/pdf'); - $c->response->body( $invoice->get_column('data') ); - return; -} - -sub invoice_delete :Chained('base') :PathPart('delete') :Args(0) { - my ($self, $c) = @_; - $c->log->debug('invoice_delete'); - my($validator,$backend,$in,$out); - - $in = $c->request->parameters; - #check that this id really belongs to specified contract? or just add contract condition to delete query? - #checking is more universal - #this is just copy-paste from method above - #of course we are chained and we can put in and out to stash - #input - $in->{provider_id} = $c->stash->{provider}->id; - - #output - $out={}; - - #storage - #pass scheme here is ugly, and should be moved somehow to DB::Base - $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - - #input checking & simple preprocessing - $validator = NGCP::Panel::Form::Invoice::Basic->new( backend => $backend ); -# $form->schema( $c->model('DB::InvoiceTemplate')->schema ); - #to common form package ? removing is necessary due to FormHandler param presence evaluation - it is based on key presence, not on defined/not defined value - #in future this method should be called by ControllerBase - $validator->remove_undef_in($in); - - #really, we don't need a form here at all - #just use as already implemented fields checking and defaults applying - #$validator->setup_form( - $validator->process( - posted => 1, - params => $in, - ); - #$validator->validate_form(); - - #multi return... - $c->log->debug("validated=".$validator->validated.";\n"); - if(!$validator->validated){ - #return; - } - my $in_validated = $validator->fif; - - #dirty hack 1 - #really model logic should receive validated input, but raw input also should be saved somewhere - - #---------------> $in = $in_validated; - - #think about it more - - $backend->deleteInvoice(%$in); - #$c->flash(messages => [{type => 'success', text => $c->loc( - $c->stash(messages => [{type => 'success', text => $c->loc( - 'Invoice deleted' - ) }]); - $c->stash( template => 'helpers/ajax_messages.tt' ); - $c->detach( $c->view("SVG") ); -} -sub invoice_generate :Chained('base') :PathPart('generate') :Args(0) { - my ($self, $c) = @_; - $c->log->debug($c->action); - my($validator,$backend,$in,$out); - $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - - #from parameters - $in = $c->request->parameters; - $in->{provider_id} = $c->stash->{provider}->id; - - - $validator = NGCP::Panel::Form::Invoice::Generate->new( - #backend => $backend, - #client_contract_ajax_src => $c->uri_for_action( '/invoice/ajax_datatables_data', [ $self->provider_id, 'invoice_list_data' ], - ); - - #todo: try to move it tp frp, template. really this is view responsibility - #cpan is out again - #if(!$in->{client_contract_id}){ - $validator->field('client_contract_id')->ajax_src( - $c->uri_for_action( - '/invoice/ajax_datatables_data', - [ $in->{provider_id}, 'provider_client_list' ] , - )->as_string()); - #}else{ - # $validator->field('client_contract_id',$validator->field('client_contract_id_hidden')); - # $validator->field('client_contract_id')->name('client_contract_id'); - #} - $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? - $validator->action( $c->uri_for_action('invoice/invoice_generate',[$in->{provider_id}]) ); - $validator->name( 'invoice_generate' );#from parameters - 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, - #no edit supposed for invoice - just creation and deletion, so item from DB is not used - item => $in, - ); - my $in_validated = $validator->fif; - if($posted){ - $c->forward('parse_invoice_period',[$in]); - #$c->log->debug("stime=".$in->{stime}.";etime=".$in->{etime}.";"); - $c->log->debug("validated=".$validator->validated.";"); - if($validator->validated) { - $c->forward('generate_invoice',[$in, $out]); - - try { - $c->flash(messages => [{type => 'success', text => $c->loc( - 'Invoice generated' - ) }]); - } catch($e) { - NGCP::Panel::Utils::Message->error( - c => $c, - error => $e, - desc => $c->loc( - 'Failed to generate invoice.' - ), - ); - } - $c->stash( messages => $c->flash->{messages} ); - $c->stash( template => 'helpers/ajax_messages.tt' ); - }else{ - $c->response->headers->header( 'X-Form-Status' => 'error' ); - } - } - if(!$validator->validated){ - $c->stash( m => {create_flag => !$in->{invoice_id}} ); - $c->stash( form => $validator ); - $c->stash( template => 'invoice/invoice_generate_form.tt' ); - } - $c->detach( $c->view("SVG") );#to the sake of nowrapper -} - -#absolutely Model method -sub generate_invoice :Private{ - my ($self, $c, $in, $out) = @_; - $out ||={}; - my $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - $c->log->debug('generate_invoice;'); - - my $provider = $c->stash->{provider}; - #$Data::Dumper::Maxdepth = 1; - - my $client_contract = $backend->getContractInfo('contract_id' => $in->{client_contract_id}); - my $client_contact = $backend->getContactInfo('contact_id' => $client_contract->get_column('contact_id') ); - my $provider_contract = $backend->getContractInfo('contract_id' => $provider->get_column('contract_id') ); - my $provider_contact = $backend->getContactInfo('contact_id' => $provider_contract->get_column('id')); - my $contract_balance = $backend->getContractBalance($in); - #$c->log->debug("customer->id="..";"); - my $billing_profile = $backend->getBillingProfile($in); - if(!$contract_balance){ - NGCP::Panel::Utils::Contract::create_contract_balance( - c => $c, - profile => $billing_profile, - contract => $client_contract, - stime => $in->{stime}, - etime => $in->{etime}, - ); - $contract_balance = $backend->getContractBalance($in); - } - my $invoice; - if($contract_balance->get_column('invoice_id')){ - $invoice = $backend->getInvoice('invoice_id' => $contract_balance->get_column('invoice_id') ); - #$c->log->debug('getInvoice:invoice.id='.$invoice->get_column('id').';'); - }else{ - $invoice = $backend->createInvoice( - 'contract_balance' => $contract_balance, - stime => $in->{stime}, - etime => $in->{etime}, - ); - $c->log->debug('createInvoice:invoice.id='.$invoice->get_column('id').';'); - } - $c->forward('invoice_details_calls',[$in]); - $c->forward('invoice_details_zones',[$in]); - #additions for generations - $in = { - %$in, - no_fake_data => 1, - tt_type => 'svg', - tt_sourcestate => 'saved', - tt_id => $in->{provider_id}, - }; - $out = { - %$out, - tt_id => $in->{provider_id}, - }; - my $stash = { - provider => $provider_contact, - client => $client_contact, - invoice => $invoice, - bp => $billing_profile, - invoice_details_zones => $c->stash->{invoice_details_zones}, - invoice_details_calls => $c->stash->{invoice_details_calls}, - }; - my $svg = ''; - $backend->getInvoiceTemplate( %$in, result => \$svg );#provider_id in $in is enough - if(!$svg){ - NGCP::Panel::Utils::InvoiceTemplate::getDefaultInvoiceTemplate( c => $c, type => 'svg', result => \$svg ); - NGCP::Panel::Utils::InvoiceTemplate::preprocessInvoiceTemplateSvg( {no_fake_data => 1}, \$svg); - } - $svg = $c->view('SVG')->getTemplateProcessed($c,\$svg, $stash ); - NGCP::Panel::Utils::InvoiceTemplate::convertSvg2Pdf($c,\$svg,$in,$out); - $backend->storeInvoiceData('invoice'=>$invoice,'data'=>\$out->{tt_string_pdf}); -} -sub parse_invoice_period :Private{ - my ($self, $c, $in) = @_; - - my $parser = DateTime::Format::Strptime->new( - #pattern => '%Y-%m-%d %H:%M:%S', - pattern => '%Y-%m-%d', - ); - if($in->{start}) { - $in->{stime} = $parser->parse_datetime($in->{start})->truncate(to => 'day'); - } - if($in->{end}) { - $in->{etime} = $parser->parse_datetime($in->{end})->truncate(to => 'day')->add(days => 1)->subtract(seconds => 1); - } -} -sub invoice_send :Chained('base') :PathPart('send') :Args(0) { - my ($self, $c) = @_; - $c->log->debug($c->action); - my($validator,$backend,$in,$out); - $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - - #from parameters - $in = $c->request->parameters; - $in->{provider_id} = $c->stash->{provider}->id; - - - $validator = NGCP::Panel::Form::Invoice::Send->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? - $validator->action( $c->uri_for_action('invoice/invoice_send',[$in->{provider_id}]) ); - $validator->name( 'invoice_send' );#from parameters - - my $posted = exists $in->{submitid}; - $c->log->debug("posted=$posted;"); - - if($in->{invoice_id} && !$in->{email}){ - if(my $invoice_contact = $backend->getInvoiceContact(%$in)){ - $in->{email} = $invoice_contact->get_column('email'); - } - } - - #todo: validate that customer is not terminated and is sip/pbx account - $validator->process( - posted => $posted, - params => $in, - #no edit supposed for invoice - just creation and deletion, so item from DB is not used - item => $in, - ); - my $in_validated = $validator->fif; - if($posted){ - $c->log->debug("validated=".$validator->validated.";"); - if($validator->validated) { - - #logic here - - try { - $c->flash(messages => [{type => 'success', text => $c->loc( - 'Invoice sent' - ) }]); - } catch($e) { - NGCP::Panel::Utils::Message->error( - c => $c, - error => $e, - desc => $c->loc( - 'Failed to send invoice.' - ), - ); - } - $c->stash( messages => $c->flash->{messages} ); - $c->stash( template => 'helpers/ajax_messages.tt' ); - }else{ - $c->response->headers->header( 'X-Form-Status' => 'error' ); - } - } - if(!$validator->validated){ - $c->stash( m => {create_flag => !$in->{invoice_id}} ); - $c->stash( form => $validator ); - $c->stash( template => 'invoice/invoice_send_form.tt' ); - } - $c->detach( $c->view("SVG") );#to the sake of nowrapper -} - -sub template_base :Chained('base') :PathPart('template') :CaptureArgs(0) { - my ($self, $c) = @_; - my($validator,$backend,$in,$out); - $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - $c->log->debug('template_base'); - #my $client_id = $c->stash->{client} ? $c->stash->{client}->id : undef ; - #my $client; - #if($client_id){ - # $client = $backend->getClient($client_id); - #}else{ - # #$c->stash->{provider}->id; - #} - #$c->stash( provider => $c->stash->{reseller}->first ); -} - -sub template_info :Chained('template_base') :PathPart('info') :Args(0) { - my ($self, $c) = @_; - $c->log->debug($c->action); - my($validator,$backend,$in,$out); - $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - - #from parameters - $in = $c->request->parameters; - $in->{provider_id} = $c->stash->{provider}->id; - #(undef,undef,@$in{qw/tt_id/}) = @_; - - if($in->{tt_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($_);} - } - if(!$out->{tt_data}){ - $out->{tt_data} = $in; - } - $validator = NGCP::Panel::Form::Invoice::Template->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/template_info',[$in->{provider_id}]) ); - $validator->name( 'template_info' );#from parameters - #my $posted = 0; - my $posted = exists $in->{submitid}; - $c->log->debug("posted=$posted;"); - $validator->process( - posted => $posted, - params => $in, - #item => $in, - item => $out->{tt_data}, - #item => $out->{tt_data}, - ); - my $in_validated = $validator->fif; - if($posted){ - if($validator->validated) { - try { - $backend->storeInvoiceTemplateInfo(%$in_validated); - $c->flash(messages => [{type => 'success', text => $c->loc( - $in->{tt_id} - ?'Invoice template updated' - :'Invoice template created' - ) }]); - } catch($e) { - NGCP::Panel::Utils::Message->error( - c => $c, - error => $e, - desc => $c->loc( - $in->{tt_id} - ?'Failed to update invoice template.' - :'Failed to create invoice template.' - ), - ); - } - $c->stash( messages => $c->flash->{messages} ); - $c->stash( template => 'helpers/ajax_messages.tt' ); - }else{ - $c->stash( m => {create_flag => !$in->{tt_id}} ); - $c->stash( form => $validator ); - #$c->stash( template => 'helpers/ajax_form_modal.tt' ); - $c->stash( template => 'invoice/template_info_form.tt' ); - $c->response->headers->header( 'X-Form-Status' => 'error' ); - } - }else{ - #$c->stash( in => $in ); - #$c->stash( out => $out ); - $c->stash( m => {create_flag => !$in->{tt_id}} ); - $c->stash( form => $validator ); - #$c->stash( template => 'helpers/ajax_form_modal.tt' ); - $c->stash( template => 'invoice/template_info_form.tt' ); - } - $c->detach( $c->view("SVG") );#to the sake of nowrapper -} - -sub template_activate :Chained('template_base') :PathPart('activate') :Args(2) { - my ($self, $c) = @_; - $c->log->debug('template_activate'); - my($validator,$backend,$in,$out); - - (undef,undef,@$in{qw/tt_id is_active/}) = @_; - #check that this id really belongs to specified contract? or just add contract condition to delete query? - #checking is more universal - #this is just copy-paste from method above - #of course we are chained and we can put in and out to stash - #input - $in->{provider_id} = $c->stash->{provider}->id; - - #output - $out={}; - - #storage - #pass scheme here is ugly, and should be moved somehow to DB::Base - $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - - #input checking & simple preprocessing - $validator = NGCP::Panel::Form::Invoice::Template->new( backend => $backend ); -# $form->schema( $c->model('DB::InvoiceTemplate')->schema ); - #to common form package ? removing is necessary due to FormHandler param presence evaluation - it is based on key presence, not on defined/not defined value - #in future this method should be called by ControllerBase - $validator->remove_undef_in($in); - - #really, we don't need a form here at all - #just use as already implemented fields checking and defaults applying - #$validator->setup_form( - $validator->process( - posted => 1, - params => $in, - ); - #$validator->validate_form(); - - #multi return... - $c->log->debug("validated=".$validator->validated.";\n"); - if(!$validator->validated){ - #return; - } - my $in_validated = $validator->fif; - - #dirty hack 1 - #really model logic should recieve validated input, but raw input also should be saved somewhere - $in = $in_validated; - #think about it more - if( ! $in->{is_active} ){ - $backend->activateInvoiceTemplate(%$in); - }else{ - $backend->deactivateInvoiceTemplate(%$in); - } - $c->flash(messages => [{type => 'success', text => $c->loc( - $in->{is_active} - ? 'Invoice template deactivated' - :'Invoice template activated' - ) }]); - $c->forward( 'template_list' ); -} -sub template_delete :Chained('template_base') :PathPart('delete') :Args(1) { - my ($self, $c) = @_; - $c->log->debug('template_delete'); - my($validator,$backend,$in,$out); - - (undef,undef,@$in{qw/tt_id/}) = @_; - #check that this id really belongs to specified contract? or just add contract condition to delete query? - #checking is more universal - #this is just copy-paste from method above - #of course we are chained and we can put in and out to stash - #input - $in->{provider_id} = $c->stash->{provider}->id; - - #output - $out={}; - - #storage - #pass scheme here is ugly, and should be moved somehow to DB::Base - $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - - #input checking & simple preprocessing - $validator = NGCP::Panel::Form::Invoice::Template->new( backend => $backend ); -# $form->schema( $c->model('DB::InvoiceTemplate')->schema ); - #to common form package ? removing is necessary due to FormHandler param presence evaluation - it is based on key presence, not on defined/not defined value - #in future this method should be called by ControllerBase - $validator->remove_undef_in($in); - - #really, we don't need a form here at all - #just use as already implemented fields checking and defaults applying - #$validator->setup_form( - $validator->process( - posted => 1, - params => $in, - ); - #$validator->validate_form(); - - #multi return... - $c->log->debug("validated=".$validator->validated.";\n"); - if(!$validator->validated){ - #return; - } - my $in_validated = $validator->fif; - - #dirty hack 1 - #really model logic should recieve validated input, but raw input also should be saved somewhere - $in = $in_validated; - #think about it more - - $backend->deleteInvoiceTemplate(%$in); - $c->flash(messages => [{type => 'success', text => $c->loc( - 'Invoice template deleted' - ) }]); - $c->forward( 'template_list' ); -} - -sub template_list_data :Chained('base') :PathPart('') :CaptureArgs(0) { - my ($self, $c) = @_; - $c->log->debug('template_list_data'); - my($validator,$backend,$in,$out); - $in->{provider_id} = $c->stash->{provider}->id; - $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - my $records = [$backend->getInvoiceTemplateList( %$in )->all]; - $c->stash( template_list => $records ); -} -sub template_list :Chained('template_base') :PathPart('list') :Args(0) { - my ($self, $c) = @_; - $c->log->debug('template_list'); - $c->forward( 'template_list_data' ); - $c->stash( template => 'invoice/template_list_alt.tt' ); - $c->detach($c->view('SVG'));#just no wrapper - maybe there is some other way? -} - -sub template :Chained('template_base') :PathPart('') :Args(0) { - my ($self, $c) = @_; - $c->forward('template_list_data'); - $c->stash(template => 'invoice/template.tt'); -} -sub template_alt :Chained('template_base') :PathPart('') :Args(1) { - my ($self, $c) = @_; - my $in={}; - (undef,undef,$in->{tt_id}) = @_; - #for form hidden too. And for template_view. - $c->stash(tt_id => $in->{tt_id}); - $c->stash(template => 'invoice/template_alt.tt'); -} - -sub template_view :Chained('template_base') :PathPart('view') :Args { - my ($self, $c) = @_; - $c->log->debug('template_view'); - no warnings 'uninitialized'; - - my($validator,$backend,$in,$out); - - - #input - (undef,undef,@$in{qw/tt_type tt_viewmode tt_sourcestate tt_output_type tt_id/}) = @_ ; - $in->{provider_id} = $c->stash->{provider}->id; - $in->{tt_id} //= $c->request->parameters->{tt_id}; - $in->{tt_id} //= $c->stash->{tt_id}; - #$in->{client_id} = ; - $in->{tt_string} = $c->request->body_parameters->{template} || ''; - foreach(qw/name is_active/){$in->{$_} = $c->request->parameters->{$_};} - - #output - $out={}; - - #storage - #pass scheme here is ugly, and should be moved somehow to DB::Base - $backend = NGCP::Panel::Model::DB::InvoiceTemplate->new( schema => $c->model('DB') ); - - #input checking & simple preprocessing - $validator = NGCP::Panel::Form::Invoice::Template->new; -# $form->schema( $c->model('DB::InvoiceTemplate')->schema ); - #to common form package ? removing is necessary due to FormHandler param presence evaluation - it is based on key presence, not on defined/not defined value - $validator->remove_undef_in($in); - - #really, we don't need a form here at all - #just use as already implemented fields checking and defaults applying - #$validator->setup_form( - $validator->process( - posted => 1, - params => $in, - ); - #$validator->validate_form(); - - #multi return... - $c->log->debug("validated=".$validator->validated.";\n"); - if(!$validator->validated){ - #return; - } - my $in_validated = $validator->fif; - - #dirty hack 1 - #really model logic should recieve validated input, but raw input also should be saved somewhere - $in = $in_validated; - - #model logic - my $tt_string_default = ''; - my $tt_string_customer = ''; - my $tt_string_force_default = ( $in->{tt_sourcestate} eq 'default' ); - $c->log->debug("force_default=$tt_string_force_default;"); - if(!$in->{tt_string} && !$tt_string_force_default){ - #here we also may be better should contact model, not DB directly. Will return to this separation later - #at the end - we can figure out rather basic controller behaviour - ($out->{tt_id},undef,$out->{tt_data}) = $backend->getInvoiceTemplate( %$in, result => \$tt_string_customer ); - - if($out->{tt_data}){ - $out->{json} = { - tt_data => { - tt_id => $out->{tt_data}->get_column('id'), - base64_previewed => ( $out->{tt_data}->get_column('base64_previewed') ? 1 : 0), - }, - }; - foreach(qw/name is_active/){ - $out->{json}->{tt_data}->{$_} = $out->{tt_data}->get_column($_); - } - } - } - - #we need to get default to 1) sanitize (if in->tt_string) or 2)if not in->tt_string and no customer->tt_string - if($in->{tt_string} || !$tt_string_customer || $tt_string_force_default ){ - try{ - #Utils... mmm - if it were model - there would be no necessity in utils using - NGCP::Panel::Utils::InvoiceTemplate::getDefaultInvoiceTemplate( c => $c, type => $in->{tt_type}, result => \$tt_string_default ); - } catch($e) { - NGCP::Panel::Utils::Message->error( - c => $c, - error => 'default invoice template error', - desc => $c->loc('There is no one invoice template in the system.'), - ); - } - if($in->{tt_string} && !$tt_string_force_default){ - - #sanitize - my $tt_string_sanitized = $in->{tt_string}; - $tt_string_sanitized =~s///gs; - $tt_string_sanitized =~s/'/'/gs; - my $tokens_re = qr/\[%(.*?)%\]/; - my $token_shape_re = qr/\s*/; - my %tokens_valid = map{$_=~s/$token_shape_re//sg; $_ => 1;} ($tt_string_default=~/$tokens_re/sg); - #use irka; - #use Data::Dumper; - #irka::loglong(Dumper(\%tokens_valid)); - foreach( $tt_string_sanitized=~/$tokens_re/sg ){ - my $token_shape=$_; - $token_shape=~s/$token_shape_re//sg; - if(! exists $tokens_valid{$token_shape}){ - $c->log->debug('Not allowed token in invoice template:'.$_.";\n"); - $tt_string_sanitized=~s/(?:\[%)+\s*\Q$_\E\s*(?:%\])+//g; - } - } - #/sanitize - to sub, later - - my($tt_stored); - try { - $tt_stored = $backend->storeInvoiceTemplateContent( - %$in, - tt_string_sanitized => \$tt_string_sanitized, - ); - $c->flash(messages => [{type => 'success', text => $c->loc( - $in->{tt_id} - ?'Invoice template updated' - :'Invoice template created' - ) }]); - } catch($e) { - NGCP::Panel::Utils::Message->error( - c => $c, - error => $e, - desc => $c->loc( - $in->{tt_id} - ?'Failed to update invoice template.' - :'Failed to create invoice template.' - ), - ); - } - - $out->{json} = { - tt_data => { - tt_id => $tt_stored->{tt_id}, - }, - }; - - $out->{tt_string} = $tt_string_sanitized; - }elsif(!$tt_string_customer || $tt_string_force_default){ - $out->{tt_string} = $tt_string_default; - $c->log->debug("apply default;"); - } - }else{#we have customer template, we don't have dynamic template string, we weren't requested to show default - $out->{tt_string} = $tt_string_customer; - } - #/model logic - - #prepare response - #mess,mess,mess here - if($in->{tt_output_type} eq 'svg'){ - $c->response->content_type('text/html'); - #multi-svg document (as well as one-svg documet) is shown ok with text/html -# $c->response->content_type('image/svg+xml'); - }elsif($in->{tt_output_type} eq 'pdf'){ - $c->response->content_type('application/pdf'); - }elsif($in->{tt_output_type} eq 'html'){ - $c->response->content_type('text/html'); - }elsif($in->{tt_output_type} eq 'json'){ - #$c->response->content_type('application/json'); - #IE prompts to save json file. - $c->response->content_type('text/html'); - #$c->response->content_type('text/javascript'); - }elsif($in->{tt_output_type}=~m'zip'){ - $c->response->content_type('application/zip'); - } - - #$out->{tt_string}=~s/()($2.*?>))/$1$3/gs; - - if($in->{tt_viewmode} eq 'raw'){ - #$c->stash->{VIEW_NO_TT_PROCESS} = 1; - $c->response->body($out->{tt_string}); - return; - }else{#parsed - - my $contacts = $c->model('DB')->resultset('contacts')->search({ id => $in->{provider_id} }); - $c->stash( provider => $contacts->first ); - - #some preprocessing should be done only before showing. So, there will be: - #preShowCustomTemplate prerpocessing - { - #preShowInvoice - #even better - to template filters - #also to model - $out->{tt_string_prepared}=$out->{tt_string_stored}=$out->{tt_string}; - NGCP::Panel::Utils::InvoiceTemplate::preprocessInvoiceTemplateSvg($in,\$out->{tt_string_prepared}); - } - - if( ($in->{tt_output_type} eq 'svg') || ( $in->{tt_output_type} eq 'html') ){ - #$c->response->content_type('image/svg+xml'); - $c->stash( template => \$out->{tt_string_prepared} ); - $c->detach( $c->view('SVG') ); - }elsif($in->{tt_output_type} eq 'json'){ - #method - $c->log->debug('prepare json'); - - my $aaData = { - template =>{ - raw => $out->{tt_string_stored}, - parsed => $c->view('SVG')->getTemplateProcessed($c, \$out->{tt_string_prepared}, $c->stash ), - }, - }; - #can be empty if we just load default - if($out->{json} && $out->{json}->{tt_data}){ - $aaData->{form} = $out->{json}->{tt_data}; - }else{ - #if we didn't have tt_data - then we have empty form fields with applied defaults - $aaData->{form} = $in; - } - #$c->stash( to_json( { aaData => $aaData} ) ); - #$c->detach( $c->view('SVG') );#ie doesn't serve correctly json - $c->response->body( to_json( { aaData => $aaData} ) ); - #$c->detach( $c->view('SVG') );#ie doesn't serve correctly json - }elsif($in->{tt_output_type} eq 'pdf'){ - #method - $c->response->content_type('application/pdf'); - my $svg = $c->view('SVG')->getTemplateProcessed($c,\$out->{tt_string_prepared}, $c->stash ); - NGCP::Panel::Utils::InvoiceTemplate::convertSvg2Pdf($c,\$svg,$in,$out); - $c->response->body($out->{tt_string_pdf}); - return; - #$out->{tt_string} = `cat $filename `; - } - - } -} - -sub template_aux_embedImage :Chained('invoice') :PathPart('auxembedimage') :Args(0) { - my ($self, $c) = @_; - - #I know somewhere is logging of all visited methods - $c->log->debug('template_aux_embedImage'); - my($validator,$backend,$in,$out); - - #todo - #mime-type and type checking in form - - $in = $c->request->parameters; - $in->{svg_file} = $c->request->upload('svg_file'); - if($in->{svg_file}) { - my $ft = File::Type->new(); - $out->{image_content} = $in->{svg_file}->slurp; - $out->{image_content_mimetype} = $ft->mime_type($out->{image_content}); - $out->{image_content_base64} = encode_base64($out->{image_content}, ''); - } - $c->log->debug('mime-type '.$out->{image_content_mimetype}); - $c->stash(out => $out); - $c->stash(in => $in); - $c->stash(template => 'invoice/template_editor_aux_embedimage.tt'); - $c->detach( $c->view('SVG') ); - -} - -__PACKAGE__->meta->make_immutable; - -__END__ - -=encoding UTF-8 - -=head1 NAME - -NGCP::Panel::Controller::Invoice - Catalyst Controller - -=head1 DESCRIPTION - -Catalyst Controller. - -=head1 METHODS - -=head1 AUTHOR - -I. Peshinskaya,,, - -=head1 LICENSE - -This library is free software. You can redistribute it and/or modify -it under the same terms as Perl itself. - -# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/InvoiceTemplate.pm b/lib/NGCP/Panel/Controller/InvoiceTemplate.pm new file mode 100644 index 0000000000..5dd16d78ae --- /dev/null +++ b/lib/NGCP/Panel/Controller/InvoiceTemplate.pm @@ -0,0 +1,259 @@ +package NGCP::Panel::Controller::InvoiceTemplate; +use Sipwise::Base; +use namespace::sweep; +BEGIN { extends 'Catalyst::Controller'; } + +use NGCP::Panel::Form::Invoice::TemplateAdmin; +use NGCP::Panel::Form::Invoice::TemplateReseller; + +sub auto :Private { + my ($self, $c) = @_; + $c->log->debug(__PACKAGE__ . '::auto'); + NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c); + return 1; +} + +sub template_list :Chained('/') :PathPart('invoicetemplate') :CaptureArgs(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) { + my ( $self, $c ) = @_; + + $c->stash->{tmpl_rs} = $c->model('DB')->resultset('invoice_templates'); + if($c->user->roles eq "admin") { + } elsif($c->user->roles eq "reseller") { + $c->stash->{tmpl_rs} = $c->stash->{tmpl_rs}->search({ + 'reseller_id' => $c->user->reseller_id + }); + }; + + $c->stash->{tmpl_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ + { name => 'id', search => 1, title => $c->loc('#') }, + { 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 => 'is_active', search => 1, title => $c->loc('Active') }, + ]); + + $c->stash->{template} = 'invoice/template_list.tt'; +} + +sub root :Chained('template_list') :PathPart('') :Args(0) { + my ($self, $c) = @_; +} + +sub ajax :Chained('template_list') :PathPart('ajax') :Args(0) { + my ($self, $c) = @_; + my $rs = $c->stash->{tmpl_rs}; + NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{tmpl_dt_columns}); + $c->detach( $c->view("JSON") ); +} + +sub reseller_ajax :Chained('template_list') :PathPart('ajax/reseller') :Args(1) { + my ($self, $c, $reseller_id) = @_; + my $rs = $c->stash->{tmpl_rs}->search({ + reseller_id => $reseller_id, + }); + NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{tmpl_dt_columns}); + $c->detach( $c->view("JSON") ); +} + +sub base :Chained('template_list') :PathPart('') :CaptureArgs(1) { + my ($self, $c, $tmpl_id) = @_; + + unless($tmpl_id && $tmpl_id->is_integer) { + NGCP::Panel::Utils::Message->error( + c => $c, + log => 'Invalid invoice template id detected', + desc => $c->loc('Invalid invoice template id detected'), + ); + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoicetemplate')); + } + + my $res = $c->stash->{tmpl_rs}->find($tmpl_id); + unless(defined($res)) { + NGCP::Panel::Utils::Message->error( + c => $c, + log => 'Invoice template does not exist', + desc => $c->loc('Invoice template does not exist'), + ); + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoicetemplate')); + } + $c->stash(tmpl => $res); +} + +sub create :Chained('template_list') :PathPart('create') :Args() { + my ($self, $c, $reseller_id) = @_; + + if(defined $reseller_id && !$reseller_id->is_integer) { + NGCP::Panel::Utils::Message->error( + c => $c, + log => 'Invalid reseller id detected', + desc => $c->loc('Invalid reseller id detected'), + ); + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/reseller')); + } + + my $posted = ($c->request->method eq 'POST'); + my $params = {}; + $params = $params->merge($c->session->{created_objects}); + + my $form; + if($c->user->roles eq "admin" && !$reseller_id) { + $form = NGCP::Panel::Form::Invoice::TemplateAdmin->new(ctx => $c); + } else { + $form = NGCP::Panel::Form::Invoice::TemplateReseller->new(ctx => $c); + if($c->user->roles eq "admin") { + my $reseller = $c->model('DB')->resultset('resellers')->find($reseller_id); + unless($reseller) { + NGCP::Panel::Utils::Message->error( + c => $c, + log => 'Invalid reseller id detected', + desc => $c->loc('Invalid reseller id detected'), + ); + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/reseller')); + } + } + } + $form->process( + posted => $posted, + params => $c->request->params, + item => $params, + ); + NGCP::Panel::Utils::Navigation::check_form_buttons( + c => $c, + form => $form, + fields => { + 'reseller.create' => $c->uri_for('/reseller/create'), + }, + back_uri => $c->req->uri, + ); + if($posted && $form->validated) { + try { + my $schema = $c->model('DB'); + $schema->txn_do(sub { + if($c->user->roles eq "admin") { + $form->params->{reseller_id} = $reseller_id ? $reseller_id : $form->params->{reseller}{id}; + } elsif($c->user->roles eq "reseller") { + $form->params->{reseller_id} = $c->user->reseller_id; + } + delete $form->params->{reseller}; + + my $tmpl = $c->stash->{tmpl_rs}->create($form->params); + + # deactivate other templates if this one got active + if($tmpl->is_active) { + $c->model('DB')->resultset('invoice_templates')->search({ + reseller_id => $tmpl->reseller_id, + is_active => 1, + id => { '!=' => $tmpl->id }, + })->update({ is_active => 0 }); + } + + delete $c->session->{created_objects}->{reseller}; + }); + $c->flash(messages => [{type => 'success', text => $c->loc('Invoice template successfully created')}]); + } catch($e) { + NGCP::Panel::Utils::Message->error( + c => $c, + error => $e, + desc => $c->loc('Failed to create invoice template.'), + ); + } + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoicetemplate')); + } + + $c->stash(form => $form); + $c->stash(create_flag => 1); +} + +sub edit_info :Chained('base') :PathPart('editinfo') { + my ($self, $c) = @_; + + my $tmpl = $c->stash->{tmpl}; + my $posted = ($c->request->method eq 'POST'); + my $params = { $tmpl->get_inflated_columns }; + $params->{reseller}{id} = delete $params->{reseller_id}; + $params = $params->merge($c->session->{created_objects}); + + my $form; + if($c->user->roles eq "admin") { + $form = NGCP::Panel::Form::Invoice::TemplateAdmin->new(ctx => $c); + } else { + $form = NGCP::Panel::Form::Invoice::TemplateReseller->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 => { + 'reseller.create' => $c->uri_for('/reseller/create'), + }, + back_uri => $c->req->uri, + ); + if($posted && $form->validated) { + try { + my $schema = $c->model('DB'); + $schema->txn_do(sub { + if($c->user->roles eq "admin") { + $form->params->{reseller_id} = $form->params->{reseller}{id}; + } elsif($c->user->roles eq "reseller") { + $form->params->{reseller_id} = $c->user->reseller_id; + } + delete $form->params->{reseller}; + + my $old_active = $tmpl->is_active; + $tmpl->update($form->params); + + # deactivate other templates if this one got active + if($tmpl->is_active && !$old_active) { + $c->model('DB')->resultset('invoice_templates')->search({ + reseller_id => $tmpl->reseller_id, + is_active => 1, + id => { '!=' => $tmpl->id }, + })->update({ is_active => 0 }); + } + # we don't promote another one as active, as we don't know + # for sure which one + + delete $c->session->{created_objects}->{reseller}; + }); + $c->flash(messages => [{type => 'success', text => $c->loc('Invoice template successfully updated')}]); + } catch($e) { + NGCP::Panel::Utils::Message->error( + c => $c, + error => $e, + desc => $c->loc('Failed to update invoice template.'), + ); + } + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoicetemplate')); + } + + $c->stash(form => $form); + $c->stash(edit_flag => 1); +} + +sub delete :Chained('base') :PathPart('delete') { + my ($self, $c) = @_; + + try { + my $schema = $c->model('DB'); + $schema->txn_do(sub{ + $c->stash->{tmpl}->delete; + }); + $c->flash(messages => [{type => 'success', text => $c->loc('Invoice template successfully deleted')}]); + } catch($e) { + NGCP::Panel::Utils::Message->error( + c => $c, + error => $e, + desc => $c->loc('Failed to delete invoice template.'), + ); + } + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/invoicetemplate')); +} + +__PACKAGE__->meta->make_immutable; +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/NumberBlock.pm b/lib/NGCP/Panel/Controller/NumberBlock.pm index 83642774c1..c820ef59f5 100644 --- a/lib/NGCP/Panel/Controller/NumberBlock.pm +++ b/lib/NGCP/Panel/Controller/NumberBlock.pm @@ -9,7 +9,7 @@ use NGCP::Panel::Form::NumberBlock::BlockReseller; use NGCP::Panel::Utils::Message; use NGCP::Panel::Utils::Navigation; -sub auto :Private{ +sub auto :Private { my ($self, $c) = @_; $c->log->debug(__PACKAGE__ . '::auto'); NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c); diff --git a/lib/NGCP/Panel/Controller/Reseller.pm b/lib/NGCP/Panel/Controller/Reseller.pm index e7402d6c2b..867fdea386 100644 --- a/lib/NGCP/Panel/Controller/Reseller.pm +++ b/lib/NGCP/Panel/Controller/Reseller.pm @@ -154,6 +154,13 @@ sub base :Chained('list_reseller') :PathPart('') :CaptureArgs(1) { { name => "domain", search => 1, title => $c->loc('Domain') }, { name => "domain_resellers.reseller.name", search => 1, title => $c->loc('Reseller') }, ]); + $c->stash->{tmpl_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ + { name => 'id', search => 1, title => $c->loc('#') }, + { name => 'name', search => 1, title => $c->loc('Name') }, + { name => 'type', search => 1, title => $c->loc('Type') }, + { name => 'is_active', search => 1, title => $c->loc('Active') }, + ]); + $c->stash(reseller => $c->stash->{resellers}->search_rs({ id => $reseller_id })); unless($c->stash->{reseller}->first) { @@ -314,10 +321,6 @@ sub _handle_reseller_status_change { sub details :Chained('base') :PathPart('details') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) { my ($self, $c) = @_; - $c->stash(provider => $c->stash->{reseller}->first); - #didn't find a way to make it correct with chain - $c->forward('/invoice/template_list_data'); - $c->stash(template => 'reseller/details.tt'); return; } diff --git a/lib/NGCP/Panel/Form/Invoice/TemplateAdmin.pm b/lib/NGCP/Panel/Form/Invoice/TemplateAdmin.pm new file mode 100644 index 0000000000..a0c0ac062b --- /dev/null +++ b/lib/NGCP/Panel/Form/Invoice/TemplateAdmin.pm @@ -0,0 +1,25 @@ +package NGCP::Panel::Form::Invoice::TemplateAdmin; + +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::Invoice::TemplateReseller'; +use Moose::Util::TypeConstraints; + +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.'] + }, +); + +has_block 'fields' => ( + tag => 'div', + class => [qw/modal-body/], + render_list => [qw/reseller name type is_active/], +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Invoice/TemplateReseller.pm b/lib/NGCP/Panel/Form/Invoice/TemplateReseller.pm new file mode 100644 index 0000000000..1017861a46 --- /dev/null +++ b/lib/NGCP/Panel/Form/Invoice/TemplateReseller.pm @@ -0,0 +1,68 @@ +package NGCP::Panel::Form::Invoice::TemplateReseller; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; +use Moose::Util::TypeConstraints; + +use HTML::FormHandler::Widget::Block::Bootstrap; + +has '+widget_wrapper' => ( default => 'Bootstrap' ); +has_field 'submitid' => ( type => 'Hidden' ); +sub build_render_list {[qw/submitid fields actions/]} +sub build_form_element_class {[qw(form-horizontal)]} + +has_field 'name' => ( + type => 'Text', + label => 'Name', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The name of the invoice template.'], + }, +); + +has_field 'type' => ( + type => 'Select', + label => 'Type', + required => 1, + options => [ + { label => 'SVG', value => 'svg' }, + ], + element_attr => { + rel => ['tooltip'], + title => ['The invoice template type (only svg for now).'], + }, +); + +has_field 'is_active' => ( + type => 'Boolean', + label => 'Active', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Whether this template is used to generate invoices for this reseller.'], + }, +); + +has_field 'save' => ( + type => 'Submit', + value => 'Save', + element_class => [qw/btn btn-primary/], + label => '', +); + +has_block 'fields' => ( + tag => 'div', + class => [qw/modal-body/], + render_list => [qw/name type is_active/], +); + +has_block 'actions' => ( + tag => 'div', + class => [qw/modal-footer/], + render_list => [qw/save/], +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/share/templates/invoice/invoice_template_lorem.tt b/share/templates/invoice/invoice_template_lorem.tt index b24a11f01a..d884368b14 100644 --- a/share/templates/invoice/invoice_template_lorem.tt +++ b/share/templates/invoice/invoice_template_lorem.tt @@ -38,20 +38,20 @@ template_variables.description.import({ }); IF 1 || !provider; - DEFAULT provider.bic='ABCDEFG1234'; - DEFAULT provider.city='Provider City'; - DEFAULT provider.company='Provider Gmbh.'; - DEFAULT provider.country='Provider-Country'; - DEFAULT provider.email='office@provider.com'; - DEFAULT provider.fax='+1 650 1234566'; - DEFAULT provider.fn='305595'; + DEFAULT provider.company='Providercompany Inc'; + DEFAULT provider.street='Providerstreet 99'; + DEFAULT provider.city='Providercity'; + DEFAULT provider.postcode='12345'; + DEFAULT provider.country='Providercountry'; + DEFAULT provider.comregnum='12345'; DEFAULT provider.iban='1234567890ABC'; - DEFAULT provider.mobile='+1 650 1234568'; - DEFAULT provider.phone='+1 650 1234567'; - DEFAULT provider.postcode='65104'; - DEFAULT provider.street='Provider Street'; + DEFAULT provider.bic='ABCDEFG1234'; + DEFAULT provider.vat='XY1234567'; + DEFAULT provider.email='provider@example.org'; + DEFAULT provider.phone='+1 234 567890'; + DEFAULT provider.mobile='+1 234 567890'; + DEFAULT provider.faxnumber='+1 234 567890'; DEFAULT provider.url='http://www.provider.com/'; - DEFAULT provider.vat='UATAA1234AA1234'; ELSE; FOREACH i in ['bic','city','company','country','email','fax','fn','iban','mobile','phone','postcode','street','url','vat']; TRY; @@ -73,16 +73,15 @@ template_variables.description.import({ }); IF !client.id; -DEFAULT client.bic='ABCDEFG1234'; -DEFAULT client.city='Client City'; -DEFAULT client.country='Client-Country'; -DEFAULT client.title='Herr Dipl. Ing (FH)'; -DEFAULT client.firstname='Client Firstname'; +DEFAULT client.firstname='Firstname'; +DEFAULT client.lastname='Lastname'; +DEFAULT client.street='Clientstreet 12/3/45'; +DEFAULT client.city='Clientcity'; +DEFAULT client.postcode='98765'; +DEFAULT client.country='Clientcountry'; DEFAULT client.id=Math.int(Math.rand(999999))|format('%06d'); -DEFAULT client.lastname='Client-Lastname Sr.'; -DEFAULT client.postcode='65104'; -DEFAULT client.sepa='AT1234567890'; -DEFAULT client.street='Client Street'; +DEFAULT client.iban='AT1234567890'; +DEFAULT client.bic='ABCDEFG1234'; DEFAULT client.vatid='AA1234'; END; @@ -90,7 +89,7 @@ END; template_variables.description.import({ bp => { 'name' => 'Billing Profile name', - 'interval_charge' => 'Constant fee for invoice period.', + 'interval_charge' => 'Recurring fee for invoice period', } }); @@ -110,9 +109,9 @@ DEFAULT bp.fraud_interval_notify = ''; DEFAULT bp.fraud_daily_limit = ''; DEFAULT bp.fraud_daily_lock = ''; DEFAULT bp.fraud_daily_notify = ''; -DEFAULT bp.currency = ''; -DEFAULT bp.vat_rate = ''; -DEFAULT bp.vat_included = ''; +DEFAULT bp.currency = 'USD'; +DEFAULT bp.vat_rate = '20'; +DEFAULT bp.vat_included = 0; END; invoice_details_zones = []; @@ -159,4 +158,4 @@ IF !invoice_details_zones.size(); i = i + 1; END; END; -%] \ No newline at end of file +%] diff --git a/share/templates/invoice/invoice_template_svg.tt b/share/templates/invoice/invoice_template_svg.tt index 8cf3b87748..bb105f1101 100644 --- a/share/templates/invoice/invoice_template_svg.tt +++ b/share/templates/invoice/invoice_template_svg.tt @@ -34,13 +34,17 @@ TitlePage_1 -[%provider.company%][%if(', ', provider.postcode _ ' ' _ provider.city ) %][%if(', ', provider.street )%] -[%client.title%] +[%provider.company%], [%provider.street%], [%provider.postcode%] [% provider.city%], [%provider.country%] + +[% IF client.company -%] +[%client.company%] +[% ELSE -%] [%client.firstname%] [%client.lastname%] +[% END -%] [%client.street%] [%client.postcode%] [%client.city%] [%client.country%] -Anschlussinhaber: [%client.firstname%] [%client.lastname%] +Contract Owner: [% client.company ? client.company : client.firstname _ ' ' _ client.lastname%] Rechnung Rechnungsnummer diff --git a/share/templates/invoice/template_list.tt b/share/templates/invoice/template_list.tt index 36bf6f6c61..a55777dfd7 100644 --- a/share/templates/invoice/template_list.tt +++ b/share/templates/invoice/template_list.tt @@ -1,63 +1,28 @@ -[% USE Dumper %] -[% USE format %] -[% money_format = format('%.2f') %] -[% IF !c.user.read_only && (c.user.roles == 'admin' || c.user.roles == 'reseller') -%] -[% write_access = 1 %] -[%END%] - -[%IF template_list.size %] - - - - - - - - - - - - [% FOR template IN template_list -%] - [%# Dumper.dump_html(template_list.as_query)%] - [%# Dumper.dump_html(template)%] - - - - - - - - [%END%] - -
[% c.loc('Active') %][% c.loc('Id') %][% c.loc('Name') %][% c.loc('Type') %]
[%IF template.get_column('is_active') > 0; '✓'; END%][% template.get_column('id') %][% template.get_column('name') %][% template.get_column('type') %] - -
-[%END%] +[% site_config.title = c.loc('Invoice Templates') -%] +[% + helper.name = c.loc('Invoice Template'); + helper.identifier = "InvoiceTemplate"; + helper.messages = messages; + helper.dt_columns = tmpl_dt_columns; + helper.paginate = 'true'; + helper.filter = 'true'; + helper.close_target = close_target; + helper.create_flag = create_flag; + helper.edit_flag = edit_flag; + helper.form_object = form; + helper.ajax_uri = c.uri_for_action('/invoicetemplate/ajax'); - \ No newline at end of file + UNLESS c.user.read_only; + helper.dt_buttons = [ + { name = c.loc('Edit Meta'), uri = "/invoicetemplate/'+full.id+'/editinfo", class = 'btn-small btn-primary', icon = 'icon-edit' }, + { name = c.loc('Edit Content'), uri = "/invoicetemplate/'+full.id+'/editcontent", class = 'btn-small btn-tertiary', icon = 'icon-edit' }, + { name = c.loc('Delete'), uri = "/invoicetemplate/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-remove' }, + ]; + helper.top_buttons = [ + { name = c.loc('Create Invoice Template'), uri = c.uri_for_action('/invoicetemplate/create'), class = 'btn-small btn-primary', icon = 'icon-star' }, + ]; + END; + + PROCESS 'helpers/datatables.tt'; +-%] +[% # vim: set tabstop=4 syntax=html expandtab: -%] diff --git a/share/templates/reseller/details.tt b/share/templates/reseller/details.tt index 1a3a8b57a5..e68ff66b37 100644 --- a/share/templates/reseller/details.tt +++ b/share/templates/reseller/details.tt @@ -4,9 +4,6 @@ [% c.loc('Back') %] - - [% c.loc('Invoices')%] - [% back_created = 1 -%] @@ -269,40 +266,48 @@ +
+ +
+
+[% + helper.name = c.loc('Invoice Template'); + helper.identifier = "InvoiceTemplate"; + helper.messages = messages; + helper.dt_columns = tmpl_dt_columns; + helper.paginate = 'true'; + helper.filter = 'true'; + helper.close_target = close_target; + helper.create_flag = create_flag; + helper.edit_flag = edit_flag; + helper.form_object = form; + helper.ajax_uri = c.uri_for_action('/invoicetemplate/reseller_ajax', [c.req.captures.0] ); -[% IF branding_edit_flag != 1 -%] + UNLESS c.user.read_only; + helper.dt_buttons = [ + { name = c.loc('Edit Meta'), uri = "/invoicetemplate/'+full.id+'/editinfo", class = 'btn-small btn-primary', icon = 'icon-edit' }, + { name = c.loc('Edit Content'), uri = "/invoicetemplate/'+full.id+'/editcontent", class = 'btn-small btn-tertiary', icon = 'icon-edit' }, + { name = c.loc('Delete'), uri = "/invoicetemplate/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-remove' }, + ]; + helper.top_buttons = [ + { name = c.loc('Create Invoice Template'), uri = c.uri_for('/invoicetemplate/create', c.req.captures.0), class = 'btn-small btn-primary', icon = 'icon-star' }, + ]; + END; -[%PROCESS 'helpers/modal.tt' -%] -[%mf_helper = { - ajax_load => 1, - ajax_list_refresh => 'template', - }%] -[%modal_script( m = mf_helper )%] -[%END%] - - - -[%PROCESS "invoice/uri_wrapper_js.tt"%] -
- -
-
- [% c.loc('Create invoices template')%] -
-
-[%PROCESS 'invoice/template_list_alt.tt' %] -
-
-
+ PROCESS 'helpers/datatables.tt'; +-%] - -
-
+
+
+
+[% IF edit_flag || create_flag -%] +[%PROCESS 'helpers/modal.tt' -%] +[% END -%] [% IF branding_edit_flag == 1 -%] [% IF form.has_for_js;