From 02e58b0360c5015ca7d80bc7db25ff4aef2c4b3e Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Wed, 4 Jun 2014 21:38:49 +0200 Subject: [PATCH] MT#5879 Completely redo invoice template handling. Use a more clean default template. Properly render page numbers. Cleanup cleanup cleanup. --- lib/NGCP/Panel/Controller/InvoiceTemplate.pm | 28 ++ lib/NGCP/Panel/Utils/InvoiceTemplate.pm | 96 +++- .../extensions/ext-server_opensave.js | 2 +- .../invoice/default/invoice_template_aux.tt | 366 ++++---------- .../invoice/default/invoice_template_svg.tt | 468 ++++++------------ .../invoice/template_editor_aux_embedimage.tt | 9 + .../templates/invoice/template_editor_form.tt | 2 +- 7 files changed, 372 insertions(+), 599 deletions(-) create mode 100644 share/templates/invoice/template_editor_aux_embedimage.tt diff --git a/lib/NGCP/Panel/Controller/InvoiceTemplate.pm b/lib/NGCP/Panel/Controller/InvoiceTemplate.pm index 7767b6eef9..9a4f94f139 100644 --- a/lib/NGCP/Panel/Controller/InvoiceTemplate.pm +++ b/lib/NGCP/Panel/Controller/InvoiceTemplate.pm @@ -3,6 +3,9 @@ use Sipwise::Base; use namespace::sweep; BEGIN { extends 'Catalyst::Controller'; } +use File::Type; +use MIME::Base64 qw(encode_base64); + use NGCP::Panel::Utils::InvoiceTemplate; use NGCP::Panel::Form::Invoice::TemplateAdmin; use NGCP::Panel::Form::Invoice::TemplateReseller; @@ -313,13 +316,18 @@ sub set_content_ajax :Chained('base') :PathPart('editcontent/set/ajax') :Args(0) ); return; } + + print ">>>>>>>>>>>>>>>>> sanitize\n"; NGCP::Panel::Utils::InvoiceTemplate::sanitize_svg(\$content); try { + print ">>>>>>>>>>>>>>>>> update content\n"; $tmpl->update({ base64_saved => $content, base64_previewed => undef, }); + + print ">>>>>>>>>>>>>>>>> saved content\n"; } catch($e) { NGCP::Panel::Utils::Message->error( c => $c, @@ -370,6 +378,26 @@ sub preview_content :Chained('base') :PathPart('editcontent/preview') :Args(0) { return; } +sub embed_image :Chained('/') :PathPart('invoicetemplate/embedimage') :Args(0) { + my ($self, $c) = @_; + + my ($in, $out); + $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('TT') ); + +} + diff --git a/lib/NGCP/Panel/Utils/InvoiceTemplate.pm b/lib/NGCP/Panel/Utils/InvoiceTemplate.pm index 3354fc69ea..9b5a3414b9 100644 --- a/lib/NGCP/Panel/Utils/InvoiceTemplate.pm +++ b/lib/NGCP/Panel/Utils/InvoiceTemplate.pm @@ -10,8 +10,7 @@ sub svg_pdf { my ($c,$svg_ref,$pdf_ref) = @_; my $svg = $$svg_ref; - #my $dir = File::Temp->newdir(); # cleans up automatically leaving scope - my $dir = File::Temp->newdir(undef, CLEANUP => 1); + my $dir = File::Temp->newdir(undef, CLEANUP => 0); my $tempdir = $dir->dirname; my $pagenum = 1; my @pagefiles; @@ -25,6 +24,26 @@ sub svg_pdf { my $pagefile = "$tempdir/$pagenum.svg"; push @pagefiles, $pagefile; + + print ">>>>>>>>>>>>>>>>>> processing $pagefile\n"; + + my $xp = XML::XPath->new($page); + my $g = $xp->find('//g[contains(@class,"firsty-") and contains(@class,"lasty")]'); + foreach my $node($g->get_nodelist) { + my $class = $node->getAttribute('class'); + print ">>>>>>>>>>>>>>>>>> got class $class\n"; + my $firsty = $class; my $lasty = $class; + + $firsty =~ s/^.+firsty\-(\d+).*$/$1/; + $lasty =~ s/^.+lasty\-(\d+).*$/$1/; + if(length($firsty) && length($lasty)) { + print ">>>>>>>>>>>> we got firsty=$firsty and lasty=$lasty\n"; + process_child_nodes($node, $firsty, $lasty); + } + } + $page = ($xp->findnodes('/'))[0]->toString(); + + open($fh, ">", $pagefile); binmode($fh, ":utf8"); print $fh $page; @@ -33,7 +52,12 @@ sub svg_pdf { $pagenum++; } - my @cmd_args = (qw/-h 849 -w 600 -a -f pdf/, @pagefiles); + # For whatever reason, the pdf looks ok with zoom of 1.0 when + # generated via rsvg-convert, but the print result is too big, + # so we need to scale it down by 0.8 to get a mediabox of 595,842 + # when using 90dpi. + # (it doesn't happen with inkscape, no idea what rsvg does) + my @cmd_args = (qw/-a -f pdf -z 0.8/, @pagefiles); $$pdf_ref = capturex([0], "/usr/bin/rsvg-convert", @cmd_args); return 1; @@ -44,18 +68,13 @@ sub preprocess_svg { my $xp = XML::XPath->new($$svg_ref); - my $g = $xp->find('//g[@id[contains(.,"page")]]'); + my $g = $xp->find('//g[@class="page"]'); foreach my $node($g->get_nodelist) { if($node->getAttribute('display')) { $node->removeAttribute('display'); } } - my $comment = $xp->find('//comment()[normalize-space(.) = "{}" or normalize-space(.) = "{ }"]'); - foreach my $node($comment->get_nodelist) { - $node->getParentNode->removeChild($node); - } - $$svg_ref = ($xp->findnodes('/'))[0]->toString(); $$svg_ref=~s/(?:{\s*)?(?:\s*})?//gs; @@ -96,6 +115,26 @@ sub get_tt { return $tt; } +sub process_child_nodes { + my ($node, $firsty, $y) = @_; + for my $attr (qw/y y1 y2/) { + my $a = $node->getAttribute($attr); + if($a) { + $a =~ s/^(\d+)\w*$/$1/; + my $delta = $a - $firsty; + my $newy = $y + $delta; + + print ">>>>>>>>>>>>>> attr=$attr, firsty=$firsty, a=$a, delta=$delta, new=$newy\n"; + $node->removeAttribute($attr); + $node->appendAttribute(XML::XPath::Node::Attribute->new($attr, $newy."mm")); + } + } + my @children = $node->getChildNodes(); + foreach my $node(@children) { + process_child_nodes($node, $firsty, $y); + } +} + sub get_dummy_data { return { rescontact => { @@ -113,9 +152,10 @@ sub get_dummy_data { faxnumber => '+3234567890', iban => 'RESIBAN1234567890', bic => 'RESBIC1234567890', + vatnum => 'RESVAT1234567890', }, customer => { - id => rand(10000)+10000, + id => int(rand(10000))+10000, external_id => 'Resext1234567890', }, custcontact => { @@ -133,6 +173,7 @@ sub get_dummy_data { faxnumber => '+6234567890', iban => 'CUSTIBAN1234567890', bic => 'CUSTBIC1234567890', + vatnum => 'CUSTVAT1234567890', }, billprof => { handle => 'BILPROF12345', @@ -151,29 +192,36 @@ sub get_dummy_data { year => '2014', month => '01', serial => '1234567', + total_net => 12345, + vat => 12345*0.2, + total => 12345+(12345*0.2), }, calls => [ map {{ start_time => time, - source_customer_cost => rand(1000), - duration => rand(7200) + 10, + source_customer_cost => int(rand(100000)), + duration => int(rand(7200)) + 10, destination_user_in => "1".$_."1234567890", - call_type => (qw/cfu cfb cft cfna/)[rand 4], - zone => "Zone $_", - zone_detail => "Detail $_", - }}(1 .. 100) - ], - zones => [ - map {{ - number => rand(200), - cost => rand(10000), - duration => rand(10000), - free_time => 0, + call_type => (qw/cfu cfb cft cfna/)[int(rand 4)], zone => "Zone $_", zone_detail => "Detail $_", - }}(1 .. 15) + }}(1 .. 50) ], + zones => { + totalcost => int(rand(10000))+10000, + data => [ + map {{ + number => int(rand(200)), + cost => int(rand(100000)), + duration => int(rand(10000)), + free_time => 0, + zone => "Zone $_", + zone_detail => "Detail $_", + }}(1 .. 5) + ], + }, }; + } 1; diff --git a/share/static/js/libs/svg-edit/extensions/ext-server_opensave.js b/share/static/js/libs/svg-edit/extensions/ext-server_opensave.js index 13aed26098..8e1849c881 100644 --- a/share/static/js/libs/svg-edit/extensions/ext-server_opensave.js +++ b/share/static/js/libs/svg-edit/extensions/ext-server_opensave.js @@ -113,7 +113,7 @@ svgEditor.addExtension("server_opensave", { open_svg_action = svgEditor.curConfig.extPath + 'fileopen.php?type=load_svg'; import_svg_action = svgEditor.curConfig.extPath + 'fileopen.php?type=import_svg'; //import_img_action = svgEditor.curConfig.extPath + 'fileopen.php?type=import_img'; - import_img_action = '/invoice/auxembedimage?type=import_img'; + import_img_action = '/invoicetemplate/embedimage?type=import_img'; // Set up function for PHP uploader to use svgEditor.processFile = function(str64, type) { diff --git a/share/templates/invoice/default/invoice_template_aux.tt b/share/templates/invoice/default/invoice_template_aux.tt index 72ddebc2c0..0b96d62e54 100644 --- a/share/templates/invoice/default/invoice_template_aux.tt +++ b/share/templates/invoice/default/invoice_template_aux.tt @@ -1,283 +1,135 @@ [% -USE Dumper; -USE Math; -USE date; -USE format; + USE date; + USE Math; + USE String; -datenow.value = date.now(); -datenow.month_end = date_format(month_end (datenow.value), noyear = 1 ); -datenow.month_start = date_format(month_start(datenow.value), noyear = 1 ); -datenow.year=date.format(datenow.value,'%Y'); -IF invoice.serial; - invoice.serial=Math.int(ifz(invoice.serial))|format('%06d'); - #invoice.date_start = '00:00:00 01-' _ invoice.month _ invoice.year; - invoice.date_start = '00:00:00 01-05-2014'; - invoice.date_end = month_end(invoice.date_start); -END; + aux.page = 1; - -MACRO date_format(dateval,noyear) BLOCK; -date.format(dateval,'%B'); ordinate(date.format(dateval,'%e')); IF !noyear; date.format(dateval,'%Y'); END; -END; -MACRO month_start(dateval) BLOCK; -'00:00:00 01-' _ date.format(dateval,'%m-%Y'); -END; -MACRO month_end(dateval) BLOCK; -'00:00:00 00-' _ (Math.int(ifz(date.format(dateval,'%m'))) + 1) _ date.format(dateval,'-%Y'); -END; - -MACRO ordinate(num) BLOCK; - IF num.search('(?'; + draw_background; + IF open_g; + ''; + END; END; -END; - -MACRO get_page(pagetype) BLOCK ; - #use this macro until no symbolic references in tt ; - #data can be empty, if we just need y - it doesn't depend on data ; - IF pagetype == 'titlepage' ; - page = titlepage(); - ELSIF pagetype == 'zonepage' ; - page = zonepage(); - ELSIF pagetype == 'callpage' ; - page = callpage(); + + MACRO svgclose BLOCK; + IF close_g; + ''; + END; + ''; END; - page; -END; -MACRO adjustrow(data, page, pagelocal, pageslocalnum, rowtype, tt_type, rows_interval_in, rownumber) BLOCK ; - rows_interval = ( ifz(rows_interval_in) > 0 ) ? rows_interval_in : row_vertical_interval; - IF rowtype == 'datarow' ; - row = datarow(data); - ELSIF rowtype == 'totalrow' ; - row = totalrow( (pagelocal == pageslocalnum) ? data.global : data.perpage.${pagelocal} ); + MACRO newpage BLOCK; + svgclose(close_g=0); + svgopen(open_g=0); END; - IF tt_type == 'svg' ; - y_re = '(?s)(<(?:text)[^>]*\s+y\s*=.*?)([-\d\.,]+)(.*)' ; + + MACRO check_pagebreak BLOCK; + IF maxy <= (aux.lasty + following_height); + svgclose(close_g=1); + svgopen(open_g=1); + aux.lasty = miny; + END; END; - matches = row.match( y_re ) ; - IF matches.size > 0 ; - y_old = Math.int(ifz(matches.1)) ; - y = ifz(y_old) + ( ifz(rows_interval) * ( ifz(rownumber) - 1 ) ) ; - row = matches.0 _ y _ matches.2 ; - matches_other = row.match('(<.*?\s+y\s*=.*?)([-\d\.,]+)', 1) ; - i = 0 ; - #matches_otherDumper.dump_html(matches_other); - WHILE i*2 < matches_other.size() ; - IF !matches_other.item(i*2).search(''; + val; + ''; END; - total.global.cost = ifz(total.global.cost) + ( Math.int(calldata.cost) / 100 ) ; - total.global.number = ifz(total.global.number) + calldata.number ; - total.global.duration = ifz(total.global.duration) + calldata.duration ; - total.global.free_time = ifz(total.global.free_time) + calldata.free_time ; - total.perpage.${pagelocal}.cost = ifz(total.perpage.${pagelocal}.cost) + ( Math.int(calldata.cost) / 100) ; - total.perpage.${pagelocal}.number = ifz(total.perpage.${pagelocal}.number) + calldata.number ; - total.perpage.${pagelocal}.duration = ifz(total.perpage.${pagelocal}.duration) + calldata.duration ; - total.perpage.${pagelocal}.free_time = ifz(total.perpage.${pagelocal}.free_time) + calldata.free_time ; - adjustrow(call, page, pagelocal, pageslocalnum, rowtype, tt_type, rows_interval, loop.count) ; - END ; -END ; - -MACRO list_calls(callsdata, page, pagelocal, pageslocalnum, rowtype, total, tt_type, rows_interval) BLOCK; - FOR call IN callsdata ;#invoice_details; - IF call.1; calldata = call.1; ELSE; calldata = call; END; - FOREACH colname in ['source_customer_cost','duration']; - calldata.${colname} = ifz(calldata.get_column(colname)); + y = y + offsety; + IF y >= maxy; + svgclose(close_g=1); + svgopen(open_g=1); + y = miny; END; - total.global.duration = ifz(total.global.duration) + calldata.duration ; - total.global.cost = ifz(total.global.cost) + ( Math.int(calldata.source_customer_cost) / 100 ) ; - total.perpage.${pagelocal}.duration = ifz(total.perpage.${pagelocal}.duration) + calldata.duration ; - total.perpage.${pagelocal}.cost = ifz(total.perpage.${pagelocal}.cost) + ( Math.int(calldata.source_customer_cost) / 100 ) ; - adjustrow(call, page, pagelocal, pageslocalnum, rowtype, tt_type, rows_interval, loop.count) ; - END ; -END ; - -MACRO get_page_rows_number(pagetype, tt_type, rows_type) BLOCK; - IF tt_type == 'svg' ; - rows_type = rows_type ? rows_type _ '-' : '' ; - page_rows_re = rows_type _ 'rows="([0-9]+)"' ; + END; + aux.lasty = y; END; - page = get_page(pagetype) ; - matches = page.match( page_rows_re ) ; - - rows = matches.0 || matches.1 ; - rows = Math.int(ifz(rows)); - rows; -END ; - - -MACRO get_page_interval(pagetype, tt_type, interval_type) BLOCK; - IF tt_type == 'svg' ; - interval_type = interval_type ? interval_type _ '-' : '' ; - page_interval_re = interval_type _ 'rows-interval="([0-9]+)"' ; + MACRO calllist BLOCK; + fontfamily = fontfamily || 'Arial'; + fontsize = fontsize || 8; + y = starty; + anc = anchor || 'start'; + FOR call IN calls; + x = startx; + FOR f IN fields; + anc = f.anchor || 'start'; + x = x + f.dx; + format_field(field=f, val=call.${f.name}); + val = aux.val; + ''; + val; + ''; + END; + y = y + offsety; + IF y >= maxy; + svgclose(close_g=1); + svgopen(open_g=1); + y = miny; + END; + END; + aux.lasty = y; END; - page = get_page(pagetype) ; - matches = page.match( page_interval_re ) ; + MACRO timestamp2time BLOCK; + t = timestamp; + h = Math.int(t / 3600); + m = Math.int((t % 3600) / 60); + s = Math.int((t % 3600) % 60); - interval = matches.0 || matches.1 ; - interval = Math.int(ifz(interval)); - interval; -END ; + hs = String.new(h); hs = hs.format("%02d"); + ms = String.new(m); ms = ms.format("%02d"); + ss = String.new(s); ss = ss.format("%02d"); -MACRO show_pages(invoice_details_zones, invoice_details_calls, pagetype, pagenum_in) BLOCK; -#todo: remove copypast with some macro, later; - - pagewithblock(); - - total = {perpage => [], global => {}, pagetype => { call=> {perpage => [], global => {}}, zone => {perpage => [], global => {}} } } ; - alltitlepages = ifz(alltitlepages) ? alltitlepages : 1; - allzonerowsnumber = invoice_details_zones.size() ; - titlezonerows = get_page_rows_number('titlepage','svg','zone') ; - midzonerows = get_page_rows_number('zonepage','svg') ; - titlezoneinterval = get_page_interval('titlepage','svg','zone') ; - midzoneinterval = get_page_interval('zonepage','svg') ; - midzonerows = Math.int(get_page_rows_number('zonepage','svg')) ; - midzonerows = ( midzonerows < 1 ) ? 30 : midzonerows ; - - allmidzonepages = ( (allzonerowsnumber - titlezonerows) / midzonerows )|format('%d') ; - allmidzonerows = allmidzonepages * midzonerows ; - lastzonerows = allzonerowsnumber - allmidzonerows - titlezonerows ; - allzonepages = allmidzonepages + ( lastzonerows > 0 ? 1 : 0 ) ; - - - allcallrowsnumber = invoice_details_calls.size() ; - titlecallrows = get_page_rows_number('titlepage','svg','call') ; - midcallrows = Math.int(get_page_rows_number('callpage','svg')) ; - midcallrows = ( midcallrows < 1 ) ? 30 : midcallrows ; - titlecallinterval = get_page_interval('titlepage','svg', 'call') ; - midcallinterval = get_page_interval('callpage','svg') ; - - allmidcallpages = ( (allcallrowsnumber - titlecallrows) / midcallrows )|format('%d') ; - allmidcallrows = allmidcallpages * midcallrows ; - lastcallrows = allcallrowsnumber - allmidcallrows - titlecallrows ; - allcallpages = allmidcallpages + ( lastcallrows > 0 ? 1 : 0 ) ; - - - IF ( pagetype == 'zone' || pagetype=='all' ) && allzonerowsnumber ; - pages = pagenum_in ? [ pagenum_in ] : [ 1 .. allmidzonepages ] ; - FOREACH pagenum IN pages ; - pagerowsstart = titlezonerows + midzonerows * ( pagenum - 1 ); - pagerowsend = titlezonerows + midzonerows * pagenum - 1 ; - # pagerowsend = pagerowsend > invoice_details_zones.size() - 1 ? invoice_details_zones.size() - 1 : pagerowsend ; - output = output _ document_header(); - output = output _ zonepage( invoice_details_zones.slice( pagerowsstart, pagerowsend ), total, pagenum + alltitlepages, pagenum, allzonepages, midzoneinterval ) ; - #+1 because of 1 for titlepage ; - output = output _ bgpage(pagenum + alltitlepages, pagenum, allzonepages) ; - output = output _ document_footer(); - END; - IF lastzonerows > 0 ; - #+1 is for last page number, so ( alltitlepages + allmidzonepages ) was previous page - pagenum = 1 + alltitlepages + allmidzonepages ; - output = output _ document_header(); - output = output _ zonepage( invoice_details_zones.slice( allzonerowsnumber - lastzonerows ), total, pagenum, allzonepages, allzonepages, midzoneinterval ) ; - output = output _ bgpage(pagenum, allzonepages, allzonepages) ; - output = output _ document_footer(); - END; + aux.val = hs _ ':' _ ms _ ':' _ ss; END; - #Dumper.dump(total); - - total.pagetype.call.global.import(total.global); - # total.pagetype.call.perpage.import(total.perpage); - - #Dumper.dump(total); + MACRO money_format BLOCK; + comma = comma || '.'; + full = Math.int(amount / 100); + cent = Math.int(amount % 100); + cents = String.new(cent); cents = cents.format("%02d"); + aux.val = full _ comma _ cents; + END; - total.global = {}; - total.perpage = [] ; - - IF ( pagetype == 'call' || pagetype=='all' ); - pages = pagenum_in ? [ pagenum_in ] : [ 1 .. allmidcallpages ] ; - FOREACH pagenum IN pages ; - pagerowsstart = titlecallrows + midcallrows * ( pagenum - 1 ); - pagerowsend = titlecallrows + midcallrows * pagenum - 1 ; - output = output _ document_header(); - output = output _ callpage( invoice_details_calls.slice( pagerowsstart, pagerowsend ), total, pagenum + alltitlepages + allzonepages, pagenum, allcallpages, midcallinterval ) ; - output = output _ bgpage(pagenum + alltitlepages + allzonepages, pagenum, allcallpages ) ; - output = output _ document_footer(); - END; - IF lastcallrows > 0 ; - #2 because callpages started from 1, not from 0, and we need add 1 for titlepage ; - pagenum = 1 + alltitlepages + allmidcallpages + allzonepages ; - output = output _ document_header(); - output = output _ callpage( invoice_details_calls.slice( allcallrowsnumber - lastcallrows ), total, pagenum, allcallpages, allcallpages, midcallinterval ) ; - output = output _ bgpage(pagenum, allcallpages, allcallpages) ; - output = output _ document_footer(); + MACRO format_field BLOCK; + in = val; out = ''; + IF field.date_format; + out = date.format(in, field.date_format); + ELSIF field.timestamp2time; + timestamp2time(timestamp=in); + out = aux.val; + ELSIF field.money_cents; + money_format(amount=in, comma=field.comma); + out = aux.val; + ELSE; + out = in; END; + aux.val = out; END; - # Dumper.dump(total); - - total.pagetype.zone.global.import(total.global); - # total.pagetype.zone.perpage = total.pagetype.zone.perpage.import(total.perpage); - - DEFAULT invoice.amount_netto = ifz(total.pagetype.call.global.cost) + ifz(total.pagetype.zone.global.cost) ; - DEFAULT invoice.amount_vat = ifz(invoice.amount_netto) * 0.2 ; - DEFAULT invoice.amount = ifz(invoice.amount_netto) + ifz(invoice.amount_vat) ; - DEFAULT invoice.amount_payment = invoice.amount - ifz(invoice.debit) ; - - IF ( pagetype == 'title' || pagetype=='all') ; - pagenum = 1; - output_title = output_title _ document_header(); - - output_title = output_title _ titlepage( - ( titlezonerows > 0 && invoice_details_zones.size > 0 ) ? invoice_details_zones.slice(0, titlezonerows - 1 ) : [], - - ( titlecallrows > 0 && invoice_details_calls.size > 0 ) ? invoice_details_calls.slice(0, titlecallrows - 1 ) : [], - pagenum, - titlezoneinterval - titlecallinterval - ) ; - output_title = output_title _ bgpage(pagenum) ; - output_title = output_title _ document_footer(); + MACRO print_money BLOCK; + money_format(amount=amount, comma=comma); + aux.val; END; - output_title _ output; -END; -%] + +-%] + diff --git a/share/templates/invoice/default/invoice_template_svg.tt b/share/templates/invoice/default/invoice_template_svg.tt index c888cbdd4a..beb523885c 100644 --- a/share/templates/invoice/default/invoice_template_svg.tt +++ b/share/templates/invoice/default/invoice_template_svg.tt @@ -1,324 +1,160 @@ - - - - - - + + + + + + Background + + + + [% rescontact.company %] + [% rescontact.street %] + [% rescontact.postcode %] [% custcontact.city %] + [% rescontact.country %] + + + + Company Reg.Nr.: [% rescontact.comregnum %] + VAT.Nr.: [% rescontact.vatnum %] + IBAN: [% rescontact.iban %] + BIC: [% rescontact.bic %] + + Page [% aux.page %] + + + - - - - - - - - - -TitlePage_1 - -[%rescontact.company%], [%rescontact.street%], [%rescontact.postcode%] [%rescontact.city%] - -[%custcontact.firstname%] [%custcontact.lastname%] -[%custcontact.street%] -[%custcontact.postcode%] [%custcontact.city%] -[%custcontact.country%] - -Contract Owner: [%custcontact.firstname%] [%custcontact.lastname%] - -Invoice -Invoice Nr: -[%invoice.serial%] -Customer Nr: -[%customer.id%] -UID-Nummer: -[% custcontact.vatnum %] -Period -[%invoice.year%]-[%invoice.month%] -Date -[%date.format(datenow.value,'%Y-%m-%d')%] - -[%rescontact.company%] - Your Invoice -Dear Customer, -for our services provided in the month of [%invoice.year%]-[%invoice.month%], we allow to invoice the following items: - - - - - - -Recurring Fees - -Name -Quantity -Price/Quantity -Sum in [% billprof.currency%] - -Monthly Service Fee - -[%billprof.name%] [%invoice.year%]-[%invoice.month%] -1 -[%money_format(billprof.interval_charge)%] -[%money_format(billprof.interval_charge)%] - -Total -[%money_format(billprof.interval_charge)%] - - - - - - - -Total of Recurring Fees -[%money_format(billprof.interval_charge)%] - - - - - - - -Call Fees - -Zone -Quantity -Duration -Sum in EUR - - - -Österreich Festnetz GZ -1 -00:01:36 -0,04 - - - -Österreich Festnetz FZ -9 -01:50:35 -2,21 - -Verbindungsentgelte 01/30123 -2,25 - - - - - - -Gesamtsumme der Verbindungsentgelte -2,25 - - - - - - - -Rabatte - -Bezeichnung -Anzahl -Einzelpreis -Betrag in EUR - -Rabatte [%rescontact.company_short%] Business Phone - -Grundgebühren Rabatt [%date.format(invoice.date_start,'%d.%m.%Y')%] - [%date.format(invoice.date_end,'%d.%m.%Y')%] -1 --70,00 --70,00 - -Summe --70,00 - - - - - - - - - - - - - -TitlePage_2 - -Gesamtsumme der Rabatte --70,00 - - - - - -Summe Entgelte: -32,25 - -Umsatzsteuer (20%): -6,45 - -Zahlungsbetrag -38,70 - -Der Rechnungsbetrag wird 14 Tage nach Rechnungsdatum fällig und wird gemäß vorliegendem -Abbuchungsauftrag von Ihrem Konto IBAN , BIC abgebucht. - -Mit freundlichen Grüßen -Ihr [%rescontact.company_short%] Service Team -Etwaige Einwände gegen die vorliegende Rechnung müssen innerhalb von 30 Tagen ab Zustellungsdatum schriftlich erhoben werden, andernfalls gilt -die Forderung als anerkannt. - - - - - - - - - ZonePage - - Verbindungsentgelte - für den Abrechnungszeitraum vom [%date.format(invoice.date_start,'%d.%m.%Y')%] bis [%date.format(invoice.date_end,'%d.%m.%Y')%] - - - Rufnummer [%%] - - - Zone - Anzahl - Nutzung - Freizeit - Betrag in EUR - - - - - - - Example zone/Fixed - 0 - 00:00:00 - 00:00:00 - 0.0000 - - - - - Total: - 0 - 0.000 - 0.000 - 0.0000 - - - - - - - - - - - - - - - - CallPage - Einzelgesprächsnachweis - für den Abrechnungszeitraum vom [%date.format(invoice.date_start,'%d.%m.%Y')%] bis [%date.format(invoice.date_end,'%d.%m.%Y')%] - - - Rufnummer [%%] - - - Beginnzeit - Dauer - Zielrufnummer - Zone/Destination - Betrag in EUR - - - - - - - 31.01.0001 23:59:59 - 00:00:00 - +00000000**** - Example zone/Fixed - 0.0000 - - - - - Total: - 0.000 - 0.0000 - - - - - - - + + + + Summary + + + + [% custcontact.firstname %] [% custcontact.lastname %] + [% custcontact.street %] + [% custcontact.postcode %] [% custcontact.city %] + [% custcontact.country %] + + + + Invoice Nr. + [% invoice.serial %] + Customer Nr. + [% customer.external_id %] + Invoice Period + [% invoice.year %]-[% invoice.month %] + Date + [% date_now(f='%Y-%m-%d') %] + + + Your Monthly Statement + + + Dear Customer, + For our services provided in the month of [%invoice.year%]-[%invoice.month%], we invoice the following items: + + + + TODO: move all up a bit + + Recurring Fees + + Name + Quantity + Unit Price + Total Price in [% cur %] + + [% billprof.name %] + 1 + [% fixfee %] + [% fixfee %] + + Total + [% fixfee %] + + + + + Call Summary + + Zone + Quantity + Usage + Total Price in [% cur %] + + + + + + + + Total + [% zonefee %] + + + + + + + + + Summary + in [% cur %] + + Total Summary + [% netfee %] + VAT ([% billprof.vat_rate %]%) + [% vatfee %] + + Amount Due + [% allfee %] + + + + The amount is automatically charged via SEPA within 30 days using Mandate ID MID12345 and Creditor ID CID12345 + from your account with IBAN [% rescontact.iban %] and BIC [% rescontact.bic %]. + With best regards, + Your [% rescontact.company %] Service Team + - - - - - - - - Background - - - [%rescontact.company%] - [%rescontact.street%] - [%rescontact.postcode %] [% rescontact.city %] - E-Mail : [%rescontact.email%] - Tel: [% rescontact.phonenumber %] - Fax: [% rescontact.faxnumber %] + - Company Reg.Nr.: [% rescontact.comregnum %] - IBAN: [% rescontact.iban %] BIC: [% rescontact.bic %] - xxx - yyy - zzz - - + + + CallList + + + Call Details + + + Start Time + Duration + Destination + Zone + Total Amount in [% cur %] + + + + + - - - - - - - - - - + + diff --git a/share/templates/invoice/template_editor_aux_embedimage.tt b/share/templates/invoice/template_editor_aux_embedimage.tt new file mode 100644 index 0000000000..d36ec7081c --- /dev/null +++ b/share/templates/invoice/template_editor_aux_embedimage.tt @@ -0,0 +1,9 @@ + + + + + + + diff --git a/share/templates/invoice/template_editor_form.tt b/share/templates/invoice/template_editor_form.tt index a0cc74600e..ad4adf15c4 100644 --- a/share/templates/invoice/template_editor_form.tt +++ b/share/templates/invoice/template_editor_form.tt @@ -6,7 +6,7 @@ function formSerialize(){ return $('form[id=template_editor]').serialize(); } -
+