diff --git a/lib/NGCP/Panel/Controller/Invoice.pm b/lib/NGCP/Panel/Controller/Invoice.pm index 7e08061f7f..d235558606 100644 --- a/lib/NGCP/Panel/Controller/Invoice.pm +++ b/lib/NGCP/Panel/Controller/Invoice.pm @@ -16,7 +16,8 @@ use NGCP::Panel::Utils::Message; use NGCP::Panel::Form::Invoice::Template; use NGCP::Panel::Form::Invoice::Generate; -use NGCP::Panel::Form::Invoice::Basic; +use NGCP::Panel::Form::Invoice::Send; +#use NGCP::Panel::Form::Invoice::Basic; use NGCP::Panel::Model::DB::InvoiceTemplate; use NGCP::Panel::Utils::InvoiceTemplate; @@ -323,11 +324,19 @@ sub invoice_generate :Chained('base') :PathPart('generate') :Args(0) { #backend => $backend, #client_contract_ajax_src => $c->uri_for_action( '/invoice/ajax_datatables_data', [ $self->provider_id, 'invoice_list_data' ], ); - $validator->field('client_contract_id')->ajax_src( - $c->uri_for_action( - '/invoice/ajax_datatables_data', - [ $in->{provider_id}, 'provider_client_list' ] , - )->as_string()); + + #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}]) ); @@ -460,6 +469,74 @@ sub parse_invoice_period :Private{ $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 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_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) = @_; diff --git a/lib/NGCP/Panel/Form/Invoice/Generate.pm b/lib/NGCP/Panel/Form/Invoice/Generate.pm index 5f8f2fa026..40b42b384b 100644 --- a/lib/NGCP/Panel/Form/Invoice/Generate.pm +++ b/lib/NGCP/Panel/Form/Invoice/Generate.pm @@ -1,12 +1,7 @@ package NGCP::Panel::Form::Invoice::Generate; -#use Sipwise::Base; use HTML::FormHandler::Moose; -#extends qw/HTML::FormHandler NGCP::Panel::Form::ValidatorBase/; extends 'NGCP::Panel::Form::ValidatorBase'; -#extends qw/NGCP::Panel::Form::ValidatorBase HTML::FormHandler::Field::Compound/; -#extends 'HTML::FormHandler::Field::Compound'; -#use Moose::Util::TypeConstraints; use DateTime; use DateTime::Format::Strptime; @@ -21,6 +16,7 @@ has_field 'client_contract_id' => ( is => 'rw', type => '+NGCP::Panel::Field::DataTable', label => 'Contract', + name => 'client_contract_id', do_label => 0, do_wrapper => 0, required => 1, @@ -28,28 +24,10 @@ has_field 'client_contract_id' => ( #we will set it in controller #ajax_src => $c->uri_for_action( '/invoice/ajax_datatables_data', [ $self->provider_id, 'invoice_list_data' ], ajax_src => '', - -# { name => 'contracts.id', title => c.loc('Client Contract Id'), search => 1}, -# { name => 'contracts.external_id', title => c.loc('External #'), search => 1 }, -# { name => 'email', title => c.loc('Contact Email'), search => 1 }, -# { name => 'contracts.billing_mappings.billing_profile.name', title => c.loc('Billing Profile'), search => 1 }, -# { name => 'contracts.billing_mappings.product.name', title => c.loc('Product'), search => 1 }, -# { name => 'contracts.status', title => c.loc('Status'), search => 1 }, table_titles => ['Contract Id', 'First Name', 'Last Name', 'Email'], table_fields => ['contracts.id', 'firstname', 'lastname', 'email'], ); -#has_field 'contract.id' => ( -# type => '+NGCP::Panel::Field::DataTable', -# label => 'Client', -# do_label => 0, -# do_wrapper => 0, -# required => 1, -# template => 'helpers/datatables_field.tt', -# ajax_src => '/contact/ajax_noreseller', -# table_titles => ['#', 'First Name', 'Last Name', 'Email'], -# table_fields => ['id', 'firstname', 'lastname', 'email'], -#); has_field 'start' => ( type => '+NGCP::Panel::Field::DatePicker', label => 'Start Date', @@ -71,10 +49,10 @@ has_field 'save' => ( do_label => 0, ); -#has_field 'client_contract_id' => ( -# type => 'Hidden', -# required => 1, -#); +has_field 'client_contract_id_hidden' => ( + type => 'Hidden', + required => 1, +); has_block 'fields' => ( tag => 'div', diff --git a/lib/NGCP/Panel/Form/Invoice/Send.pm b/lib/NGCP/Panel/Form/Invoice/Send.pm new file mode 100644 index 0000000000..53abd77a43 --- /dev/null +++ b/lib/NGCP/Panel/Form/Invoice/Send.pm @@ -0,0 +1,59 @@ +package NGCP::Panel::Form::Invoice::Send; + +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::ValidatorBase'; + +has_field 'submitid' => ( type => 'Hidden' ); +has '+widget_wrapper' => ( default => 'Bootstrap' ); +sub build_render_list {[qw/submitid fields actions/]} +sub build_form_element_class { [qw/form-horizontal/] } + +has_field 'email' => ( + type => 'Text', + label => 'Emails', + required => 1, +); + +has_field 'save' => ( + type => 'Button', + value => 'Send', + element_class => [qw/btn btn-primary/], + do_label => 0, +); + +has_block 'fields' => ( + tag => 'div', + class => [qw/modal-body/], + render_list => [qw/email/], +); + +has_block 'actions' => ( + tag => 'div', + class => [qw/modal-footer/], + render_list => [qw/save/], +); + +1; + +=head1 NAME + +NGCP::Panel::Form::InvoiceTemplate + +=head1 DESCRIPTION + +Form to modify an invoice template. + +=head1 METHODS + +=head1 AUTHOR + +Irina Peshinskaya + +=head1 LICENSE + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm b/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm index 2c750639b7..48ae9dcedf 100644 --- a/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm +++ b/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm @@ -367,9 +367,21 @@ sub getInvoice{ my (%params) = @_; my ($invoice_id) = @params{qw/invoice_id/}; return $self->schema->resultset('invoices')->search({ - 'id' => $invoice_id, + 'me.id' => $invoice_id, }, undef )->first; } +sub getInvoiceContact{ + my $self = shift; + my (%params) = @_; + my ($invoice_id) = @params{qw/invoice_id/}; + return $self->schema->resultset('invoices')->search({ + 'me.id' => $invoice_id, + }, { + '+select' => ['contact.email'], + '+as' => ['email'], + 'join' => [ {'contract_balances' => { 'contract' => 'contact' } } ], + } )->first; +} sub deleteInvoice{ my $self = shift; my (%params) = @_; @@ -546,7 +558,7 @@ sub checkResellerClientContact{ if($in->{client_contact_id} && $in->{provider_id}){ if(my $contact = $self->schema->resultset('contacts')->search({ 'reseller_id' => $in->{provider_id}, - 'id' => $in->{client_contact_id}, + 'me.id' => $in->{client_contact_id}, })->first){ $res = $contact->get_column('id'); } @@ -561,7 +573,7 @@ sub checkResellerInvoice{ if($in->{invoice_id} && $in->{provider_id}){ if(my $invoice = $self->schema->resultset('invoices')->search({ 'contact.reseller_id' => $in->{provider_id}, - 'id' => $in->{client_contact_id}, + 'me.id' => $in->{invoice_id}, },{ 'join' => { 'contract_balances' => { 'contract' => 'contact' }}, })->first){ @@ -581,7 +593,7 @@ sub checkResellerInvoiceTemplate{ #$in->{c}->log->debug("checkResellerInvoiceTemplate: tt_id=".$in->{tt_id}.";provider_id=".$in->{provider_id}.";"); if(my $tt = $self->schema->resultset('invoice_templates')->search({ 'reseller_id' => $in->{provider_id}, - 'id' => $in->{tt_id}, + 'me.id' => $in->{tt_id}, })->first){ $res = $tt->get_column('id'); } diff --git a/lib/NGCP/Panel/Utils/TTEmailer.pm b/lib/NGCP/Panel/Utils/TTEmailer.pm new file mode 100644 index 0000000000..56bce1306b --- /dev/null +++ b/lib/NGCP/Panel/Utils/TTEmailer.pm @@ -0,0 +1,128 @@ +package NGCP::Panel::Utils::TTEmailer; + +sub send_email{ + my $self = shift; + my ($email,$hr_tmpl,$hr_vars) = @_; + my ($email,$subject,$text,$from,$type,$headers,$images,$binatt) = @$hr_mail{qw/email subject text from type headers images binatt/}; + my $cfg = Knetrix::Config->get; + + # If we don't specify the email body then fetch it from t_$view_${action}email.tt2 + if(!$text){ + $text = ''; + $hr_tmpl->{$_} ||= ${$self->{OUT_DATA}{request}}{$_} foreach qw/module sub_module view action/; + if(!$hr_tmpl->{tmplnamestrict}){ + $hr_tmpl->{action} .= 'email'; + } + $hr_tmpl->{error} = 0; + + my $stc = {}; + my $tt2 = Knetrix::Template->instance() || throw Knetrix::Error -text=>"Missing tt2 obj"; + + if(ref $hr_vars eq 'HASH'){ + use Hash::Merge (); + $hr_vars = Hash::Merge::merge $self->{OUT_DATA} , $hr_vars; + } else { + $hr_vars = $self->{OUT_DATA}; + } + (@{$hr_vars->{config}}{qw/http httpdata data/},@{$hr_vars->{tmpl_hash}}{qw/module sub_module/}) + = ($cfg->dir_config('http'),$cfg->dir_config('httpdata'),$cfg->dir_config('systemdata'),@$hr_tmpl{qw/module sub_module/}); + $hr_vars->{tmpl_req} = $hr_tmpl; + # Some funky magic to access variables inside the template + $hr_vars->{import} ||= $stc; + my $importold = $Template::Stash::HASH_OPS->{ import } if defined $Template::Stash::HASH_OPS->{ import }; + $Template::Stash::HASH_OPS->{ import } = sub { $stc = $_[0] }; + $log->error(Dumper $hr_tmpl); + $log->error(Dumper $self->{OUT_DATA}{request}); + $hr_tmpl = Knetrix::Template::Magic->wand($hr_tmpl); + my $ok = $tt2->process($hr_tmpl,$hr_vars,\$text) || $log->error($tt2->error()); + $Template::Stash::HASH_OPS->{ import } = $importold if defined $importold; + $subject ||= $stc->get('subject'); + $type ||= $stc->get('mimetype'); + $from ||= $stc->get('from'); + } + + # If from isn't specified fetch it from knetrix.xml or use support@envisionext.com + if(!$from){ + $from = $cfg->dir_config('from','email') || 'support@envisionext.com'; + } + + # Extract the Images from the template/mail body + $images ||= {}; + sub collect_images{ + my ($q1,$image,$q2,$images) = @_; + $log->error(Dumper \@_); + my($path,$filename) = $image =~/(.*?)([^\/]+\/?)$/; + $log->error("path is $path filename is $filename"); + $images->{$filename} = $path; + return $q1.$filename.$q2; + } + $text =~s/(['"]cid:)([^'"]+)(['"])/collect_images($1,$2,$3,$images)/ge; + + my $imagescnt = scalar keys(%$images); + if($binatt){ + ref $binatt eq 'HASH' and $binatt = [$binatt]; + ref $binatt eq 'ARRAY' and $imagescnt += scalar @$binatt; + } + $log->debug("imagescnt=",$imagescnt,"; text=",$text,"images=",Dumper $images); + + # Set the mail type based on the existence of any images + my %msgadd; + if ($imagescnt) { + %msgadd = ( Type => 'multipart/related' ); + } else { + %msgadd = ( Type => $type?$type:'text/html', + Data => $text, + ); + } + # Setup the mail object + my $msg = MIME::Lite->new( To => $email, + From => $from, + Subject => $subject, + %msgadd, + ); + # Setup a debug email for the postmaster + my $debugmsg = MIME::Lite->new( To => $cfg->dir_config('postmaster','email') || 'ric@envisionext.com', + From => $from, + Subject => $subject, + %msgadd, + ); + # Attach the body and any images + if($imagescnt){ + $msg->attach( Data => $text, + Type => 'text/html', + ); + $debugmsg->attach( Data => $text."
Util::send_email\n\n\nTo:$email;".(Dumper([$hr_mail,$hr_vars]))."\n", + Type => 'text/html', + ); + # Attach the images + my $datadir = $cfg->dir_config('staticdata'); + while( my ($img,$path) = each(%$images) ){ + my ($ext) = $img=~/[^\.]+\.(.+)$/; + $msg->attach( Type => $mimetypes{$ext}, + Id => $img, + Path => join '/', $datadir,$path,$img, + ); + $debugmsg->attach( Type => $mimetypes{$ext}, + Id => $img, + Path => join '/', $datadir,$path,$img, + ); + } + foreach (_afy $binatt){ + $msg->attach( %$_); + $debugmsg->attach( %$_); + } + } + my $res; + print STDERR Dumper $msg; + if(!$hr_mail->{dontsend}){ + my @params = split(':',$cfg->dir_config('server','email')); + $res = $msg->send(@params) unless $cfg->dir_config('safemode','email');; + $debugmsg->send(@params) if $cfg->dir_config('debug','email'); + }else{ + $res = 1; + } + return ($res,$msg,$debugmsg) +} + + +1; diff --git a/share/templates/helpers/modal.tt b/share/templates/helpers/modal.tt index 1bb03add71..536a910c88 100644 --- a/share/templates/helpers/modal.tt +++ b/share/templates/helpers/modal.tt @@ -35,6 +35,7 @@ [%m.close_target_direct%] [%ELSIF m.ajax_load%] $('#mod_edit').modal('hide'); + $('#mod_edit').parent('div').html(''); [%ELSE%] console.log("redirecting to [% m.close_target ? m.close_target : c.uri_for() %]"); window.location.href="[% m.close_target ? m.close_target : c.uri_for() %]"; @@ -63,6 +64,7 @@ if( status != 'error' ){ //form object is necessary to compose uri for refresh refreshAjaxList( '[%m.ajax_list_refresh%]', form.serializeObject() ); + $('#mod_edit').parent('div').html(''); } [%END%] [%m.ajax_callback%] diff --git a/share/templates/invoice/invoice_send_form.tt b/share/templates/invoice/invoice_send_form.tt new file mode 100644 index 0000000000..eac3a1634e --- /dev/null +++ b/share/templates/invoice/invoice_send_form.tt @@ -0,0 +1,2 @@ +[%m.name = "Send Invoice"%] +[%PROCESS 'helpers/ajax_form_modal.tt'-%] diff --git a/share/templates/invoice/list.tt b/share/templates/invoice/list.tt index a0d361e637..146092ba09 100644 --- a/share/templates/invoice/list.tt +++ b/share/templates/invoice/list.tt @@ -30,7 +30,7 @@ [%messages.unshift( c.loc('Reseller is [_1]', reseller.first.status) ); %] [% END -%]