diff --git a/lib/NGCP/Panel/Controller/Customer.pm b/lib/NGCP/Panel/Controller/Customer.pm index 0df2e1b0dc..5aa84f3e43 100644 --- a/lib/NGCP/Panel/Controller/Customer.pm +++ b/lib/NGCP/Panel/Controller/Customer.pm @@ -827,10 +827,76 @@ sub invoice_data :Chained('base') :PathPart('invoice') :CaptureArgs(0) { etime => $etime, ); #TODO: FAKE FAKE FAKE FAKE - $invoice_details = [$invoice_details->all()]; + my $invoice_details_raw = $invoice_details; + $invoice_details = [$invoice_details_raw->all()]; my $i = 1; $invoice_details = [map{[$i++,$_]} (@$invoice_details) x 21]; - $c->stash(invoice_details => $invoice_details ); + $c->stash( invoice_details => $invoice_details ); + $c->stash( invoice_details_raw => $invoice_details_raw ); +} +sub invoice_details_ajax :Chained('base') :PathPart('invoice/details/ajax') :Args(0) { + my ($self, $c) = @_; + my $dt_columns_json = $c->request->parameters->{dt_columns}; + use JSON; + #use irka; + #use Data::Dumper; + #irka::loglong(Dumper($dt_columns)); + $c->forward( 'invoice_data' ); + my $dt_columns = from_json($dt_columns_json); + NGCP::Panel::Utils::Datatables::process($c, $c->stash->{invoice_details_raw}, $dt_columns ); + $c->detach( $c->view("JSON") ); +} + +sub invoice_template_activate :Chained('base') :PathPart('invoice_template/activate') :Args(1) { + my ($self, $c) = @_; + $c->log->debug('invoice_template_activate'); + 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->{contract_id} = $c->stash->{contract}->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::Customer::InvoiceTemplate->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->activateCustomerInvoiceTemplate(%$in); + $c->forward( 'invoice_template_list' ); } sub invoice_template_delete :Chained('base') :PathPart('invoice_template/delete') :Args(1) { my ($self, $c) = @_; @@ -883,6 +949,7 @@ sub invoice_template_delete :Chained('base') :PathPart('invoice_template/delete' $backend->deleteCustomerInvoiceTemplate(%$in); $c->forward( 'invoice_template_list' ); } + sub invoice_template_list_data :Chained('invoice_data') :PathPart('') :CaptureArgs(0) { my ($self, $c) = @_; $c->log->debug('invoice_template_list_data'); diff --git a/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm b/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm index 2823e8a031..a36f1b16b3 100644 --- a/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm +++ b/lib/NGCP/Panel/Model/DB/InvoiceTemplate.pm @@ -96,13 +96,7 @@ sub storeCustomerInvoiceTemplate{ }); } if($is_active && $tt_id){ - $self->schema->resultset('invoice_template')->search({ - reseller_id => $contract_id, - type => $tt_type, - id => {'!=' => $tt_id }, - })->update_all({ - is_active => 0, - }); + $self->deactivateOtherTemplates($contract_id,$tt_id); } }); return { tt_id => $tt_id }; @@ -112,6 +106,10 @@ sub getCustomerInvoiceTemplateList{ my (%params) = @_; my ($contract_id,$tt_sourcestate,$tt_type, $tt_string, $tt_id) = @params{qw/contract_id tt_sourcestate tt_type tt_string_sanitized tt_id/}; + #return [ + #$self->schema->resultset('invoice_template_fake')->find(\'select * from invoice_template')->all + #$self->schema->resultset('invoice_template')->name(\'(select * from invoice_template)')->all + #]; return [ $self->schema->resultset('invoice_template')->search({ reseller_id => $contract_id, })->all ]; @@ -125,6 +123,31 @@ sub deleteCustomerInvoiceTemplate{ id => $tt_id, })->delete_all; } +sub activateCustomerInvoiceTemplate{ + my $self = shift; + my (%params) = @_; + my ($contract_id,$tt_id) = @params{qw/contract_id tt_id/}; + $self->schema->txn_do(sub { + $self->schema->resultset('invoice_template')->search({ + reseller_id => $contract_id, + id => $tt_id, + })->update({ + is_active => 1, + }); + $self->deactivateOtherTemplates($contract_id,$tt_id); + }); +} +sub deactivateOtherTemplates{ + my $self = shift; + my ($contract_id,$tt_id) = @_; + $self->schema->resultset('invoice_template')->search({ + reseller_id => $contract_id, + id => {'!=' => $tt_id }, + is_active => 1, + })->update_all({ + is_active => 0, + }); +} sub checkCustomerInvoiceTemplateContract{ my $self = shift; my (%params) = @_; diff --git a/lib/NGCP/Panel/Utils/Datatables.pm b/lib/NGCP/Panel/Utils/Datatables.pm index c6ce33f66a..697f063916 100644 --- a/lib/NGCP/Panel/Utils/Datatables.pm +++ b/lib/NGCP/Panel/Utils/Datatables.pm @@ -18,35 +18,34 @@ sub process { # check if we need to join more tables # TODO: can we nest it deeper than once level? - unless ($use_rs_cb) { - for my $c(@{ $cols }) { - my @parts = split /\./, $c->{name}; - if($c->{literal_sql}) { - $rs = $rs->search_rs(undef, { - '+select' => [ \[$c->{literal_sql}] ], - '+as' => [ $c->{accessor} ], - }); - } elsif(@parts == 2) { - $rs = $rs->search_rs(undef, { - join => $parts[0], - '+select' => [ $c->{name} ], - '+as' => [ $c->{accessor} ], - }); - } elsif(@parts == 3) { - $rs = $rs->search_rs(undef, { - join => { $parts[0] => $parts[1] }, - '+select' => [ $parts[1].'.'.$parts[2] ], - '+as' => [ $c->{accessor} ], - }); - } elsif(@parts == 4) { - $rs = $rs->search_rs(undef, { - join => { $parts[0] => { $parts[1] => $parts[2] } }, - '+select' => [ $parts[2].'.'.$parts[3] ], - '+as' => [ $c->{accessor} ], - }); - } elsif(@parts > 4) { - # TODO throw an error for now as we only support up to 3 levels - } + set_columns($c, $cols); + for my $c(@{ $cols }) { + my @parts = split /\./, $c->{name}; + if($c->{literal_sql}) { + $rs = $rs->search_rs(undef, { + '+select' => [ \[$c->{literal_sql}] ], + '+as' => [ $c->{accessor} ], + }); + } elsif(@parts == 2) { + $rs = $rs->search_rs(undef, { + join => $parts[0], + '+select' => [ $c->{name} ], + '+as' => [ $c->{accessor} ], + }); + } elsif(@parts == 3) { + $rs = $rs->search_rs(undef, { + join => { $parts[0] => $parts[1] }, + '+select' => [ $parts[1].'.'.$parts[2] ], + '+as' => [ $c->{accessor} ], + }); + } elsif(@parts == 4) { + $rs = $rs->search_rs(undef, { + join => { $parts[0] => { $parts[1] => $parts[2] } }, + '+select' => [ $parts[2].'.'.$parts[3] ], + '+as' => [ $c->{accessor} ], + }); + } elsif(@parts > 4) { + # TODO throw an error for now as we only support up to 3 levels } } diff --git a/share/static/js/invoice_template.js b/share/static/js/invoice_template.js new file mode 100644 index 0000000000..0a5f1ecd49 --- /dev/null +++ b/share/static/js/invoice_template.js @@ -0,0 +1,124 @@ +//constructor +var svgCanvasEmbed = null; +function init_embed() { + var svgEditFrameName = 'svgedit'; + var frame = document.getElementById(svgEditFrameName); + svgCanvasEmbed = new EmbeddedSVGEdit(frame); + // Hide main button, as we will be controlling new/load/save etc from the host document + var doc = frame.contentDocument; + if (!doc) + { + doc = frame.contentWindow.document; + } + //var mainButton = doc.getElementById('main_button'); + //mainButton.style.display = 'none'; +} +//private +function getSvgString(){ + return svgCanvasEmbed.frame.contentWindow.svgCanvas.getSvgString(); +} +function setSvgStringToEditor( svgParsedString ){ + //alert('setSvgStringToEditor: '+svgParsedString); + svgCanvasEmbed.setSvgString( svgParsedString )( + function(data,error){ + if(error){ + }else{ + svgCanvasEmbed.zoomChanged('', 'canvas'); + } + } + ); +} +function setSvgStringToPreview( svgParsedString, q, data ) { + var previewIframe = document.getElementById('svgpreview'); + //alert('setSvgStringToPreview: svgParsedString='+svgParsedString+';'); + if ($.browser.msie) { + //we need to repeat query to server for msie if we don't want send template string via GET method + if(!q){ + var dataPreview = data; + dataPreview.tt_viewmode = 'parsed'; + dataPreview.tt_type = 'svg'; + dataPreview.tt_sourcestate = dataPreview.tt_sourcestate || 'saved'; + q = uriForAction( dataPreview, 'invoice_template' ); + } + previewIframe.src = q; + }else{ + previewIframe.src = "data:text/html," + encodeURIComponent(svgParsedString); + } +} +function fetchSvgToEditor( data ) { + var q = uriForAction( data, 'invoice_template' ); + //alert('fetchSvgToEditor: q='+q+';'); + $.ajax({ + url: q, + }).done( function ( httpResponse ){ + setSvgStringToEditor( httpResponse ); + }); +} +function refreshTemplateList ( contract_id ){ + fetch_into( + 'collapse_invoice_template_list', + uriForAction( {'contract_id': contract_id}, 'invoice_template_list' ), + '', + function(){ mainWrapperInit(); } + ); +} +//public +function fetchInvoiceTemplateData( data ){ + //params spec: tt_type=[svg|html]/tt_viewmode[parsed|raw]/tt_sourcestate[saved|previewed|default]/tt_output_type[svg|pdf|html|json|svgzip|pdfzip|htmlzip]/tt_id + //tt_output_type=svg really outputs text/html mimetype. But it will be couple of tags ( per page). + data.tt_output_type = 'json'; + var q = uriForAction( data, 'invoice_template' ); + //alert('fetchInvoiceTemplateData: q='+q+';'); + $.ajax({ + url: q, + datatype: "json", + //}).done( function( jsonres ){ + }).done( function( templatedata ){ + //alert(templatedata); + //alert(templatedata.aaData); + if(templatedata.aaData){ + if( templatedata.aaData.template ){ + setSvgStringToEditor( templatedata.aaData.template.raw ); + setSvgStringToPreview( templatedata.aaData.template.parsed ); + } + if( templatedata.aaData.form ){ + $('form[name=invoice_template]').loadJSON(templatedata.aaData.form); + } + } + }); +} +function savePreviewedAndShowParsed( data ){ + var svgString = getSvgString(); + var q = uriForAction( data, 'invoice_template_previewed' ); + //alert('savePreviewedAndShowParsed: svgString='+svgString+'; q='+q+';'); + //alert('savePreviewedAndShowParsed: q='+q+';'); + //save + q=formToUri(q); + $.post( q, { template: svgString } ) + .done( function( httpResponse ){ + // & show template + //alert('savePreviewedAndShowParsed: httpResponse='+httpResponse+';'); + setSvgStringToPreview( httpResponse, q ) + //refresh list after saving + refreshTemplateList( data.contract_id ); + } ); +} +function saveTemplate( data ) { + var svgString = getSvgString(); + data.tt_sourcestate='saved'; + data.tt_output_type = 'json'; + var q = uriForAction( data, 'invoice_template_saved' ); + q=formToUri(q); + alert('saveTemplate: q='+q+';'); + $.ajax( { + url: q, + type: "POST", + datatype: 'json', + data: { template: svgString }, + } ).done( function( jsonResponse ) { + if(jsonResponse.aaData && jsonResponse.aaData.form){ + $('form[name=invoice_template]').loadJSON(jsonResponse.aaData.form); + } + refreshTemplateList( data.contract_id ); + }); +} diff --git a/share/templates/customer/invoice.tt b/share/templates/customer/invoice.tt index 88c94ab212..bbd74d40a4 100644 --- a/share/templates/customer/invoice.tt +++ b/share/templates/customer/invoice.tt @@ -20,25 +20,6 @@ +
@@ -198,15 +78,22 @@ function saveTemplate( data ) { [% -#while we don't due to local clearHelper(); helper.name = c.loc('Invoice Details'); + helper.dt_columns = [ + { name => 'zone', title => c.loc('Zone'), search=> 1 }, + { name => 'number', title => c.loc('Calls amount') }, + { name => 'duration', title => c.loc('Duration') }, + { name => 'free_time', title => c.loc('Free time') }, + { name => 'cost', title => c.loc('Cash') }, + ]; helper.name_single = c.loc('Invoice Record'); - helper.identifier = 'invoice_details'; + helper.identifier = 'invoice_details_raw'; + helper.ajax_uri = c.uri_for_action( '/customer/invoice_details_ajax', [ contract.id ] ) ; initHelperAuto(); PROCESS 'helpers/datatables.tt'; -%] - + [%Dumper.dump_html(helper.ajax_uri)%] @@ -261,62 +148,8 @@ function saveTemplate( data ) { -[%#It can be separated later to form template and loaded with ajax and fillinform template::toolkit plugin. %] -[%#It is reliable and easy to implement solution, but requires additional modules. For now populate small form with jquery.%] -[%# USE FillInForm %]
- -
- -
-
- -
- -
-
-
-
- -
- -
-
- - [% c.loc('Refresh Preview')%] - - - [% c.loc('Save template')%] - -
- -[%initial = 'default'%] - +[%PROCESS 'customer/invoice_template_form.tt' %]
- \ No newline at end of file diff --git a/share/templates/customer/invoice_template_form.tt b/share/templates/customer/invoice_template_form.tt new file mode 100644 index 0000000000..7779c49827 --- /dev/null +++ b/share/templates/customer/invoice_template_form.tt @@ -0,0 +1,69 @@ +[%#It can be separated later to form template and loaded with ajax and fillinform template::toolkit plugin. %] +[%#It is reliable and easy to implement solution, but requires additional modules. For now populate small form with jquery.%] +[%# USE FillInForm %] + + + +
+[% IF write_access -%] + + [% c.loc('Create from scratch')%] + +
+[% END -%] +
+ +
+ +
+
+
+
+ +
+ +
+
+ + [% c.loc('Refresh Preview')%] + + + [% c.loc('Save template')%] + +
+ +[%initial = 'default'%] + + + diff --git a/share/templates/customer/invoice_template_list.tt b/share/templates/customer/invoice_template_list.tt index e785f7cab4..b0c138f430 100644 --- a/share/templates/customer/invoice_template_list.tt +++ b/share/templates/customer/invoice_template_list.tt @@ -6,15 +6,6 @@ [%END%]
- [% IF write_access -%] - - [% c.loc('Create invoice template')%] - -
- [% END -%] [%IF invoice_template_list.size %]
@@ -30,7 +21,7 @@ [%# Dumper.dump_html(invoice_template_list.as_query)%] [%# Dumper.dump_html(template)%] - + @@ -59,24 +50,24 @@ [%END%] [%IF !template.is_active%] - [% c.loc('Make active') %] - + [%END%] [% c.loc('Delete') %] [%END%] - [% c.loc('Download source') %] [% c.loc('Download invoice') %] - + diff --git a/share/templates/helpers/datatables.tt b/share/templates/helpers/datatables.tt index ecae33cdaa..43a153e44f 100644 --- a/share/templates/helpers/datatables.tt +++ b/share/templates/helpers/datatables.tt @@ -3,6 +3,10 @@ helper.column_titles = []; helper.column_fields = []; FOR col IN helper.dt_columns; + IF !col.accessor; + col.accessor = col.name; + col.accessor.replace('\.','_'); + END; NEXT UNLESS col.title; helper.column_titles.push(col.title); helper.column_fields.push(col.accessor); @@ -80,7 +84,7 @@ $(document).ready(function() { "mRender": function ( data, type, full ) { if(data == null) return ''; - return String(data).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + return String(data).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');//" } }, [% END -%] diff --git a/share/templates/helpers/datatables_vars.tt b/share/templates/helpers/datatables_vars.tt index 89b8762fb7..f0355241c2 100644 --- a/share/templates/helpers/datatables_vars.tt +++ b/share/templates/helpers/datatables_vars.tt @@ -1,3 +1,4 @@ +[% USE JSON.Escape( pretty => 0 ) %]; [%- #USE Dumper; @@ -12,12 +13,25 @@ MACRO initHelper(var,value) BLOCK; identifier = "${helper.identifier}" _ "_" _ var; UNLESS helper.${var}.defined; helper.${var} = helper.${var} || value || ${"${identifier}"} || ${var}; END; END; +MACRO initDtColumns BLOCK; + FOR col in helper.dt_columns; + IF ! col.accessor; + col.accessor = col.name.replace('\.','_'); + END; + END; +END; MACRO initHelperAuto BLOCK; FOREACH identifier in [ 'messages', 'length_change', 'dt_columns', 'close_target', 'create_flag' ]; initHelper(identifier); END; + #; initHelper('form_object',form); initHelper('ajax_uri', c.uri_for( c.controller.action_for('ajax') )); + IF ! helper.ajax_uri.search('[\?&]dt_columns='); + initDtColumns(); + helper.dt_columns_json = helper.dt_columns.json() | url; + helper.ajax_uri = helper.ajax_uri _ '?dt_columns=' _ helper.dt_columns_json; + END; END; IF !no_auto_helper;
[% template.get_column('is_active') %] [% template.get_column('type') %] [% template.get_column('name') %]