From: Jeremy Davis Date: Fri, 26 Jun 2015 21:38:02 +0000 (-0400) Subject: Merge branch '3.x-pre' of git.freeside.biz:/home/git/freeside into FREESIDE_3_BRANCH X-Git-Url: http://git.freeside.biz/gitweb/?a=commitdiff_plain;h=daa7e41eabe79e3ca7f65e060e7c588715bf236b;hp=d8a43b104aca6a01f8f1fd821dd29f3dffd59919;p=freeside.git Merge branch '3.x-pre' of git.freeside.biz:/home/git/freeside into FREESIDE_3_BRANCH --- diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index d2113616c..5ea3555e7 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -679,10 +679,12 @@ invoice_latexfooter invoice_latexsmallfooter invoice_latexnotes invoice_latexcoupon +invoice_latexwatermark invoice_html invoice_htmlreturnaddress invoice_htmlfooter invoice_htmlnotes +invoice_htmlwatermark logo.png logo.eps ); @@ -1379,6 +1381,15 @@ sub reason_type_options { }, { + 'key' => 'invoice_htmlwatermark', + 'section' => 'invoicing', + 'description' => 'Watermark for HTML invoices. Appears in a semitransparent positioned DIV overlaid on the main invoice container.', + 'type' => 'textarea', + 'per_agent' => 1, + 'per_locale' => 1, + }, + + { 'key' => 'invoice_latex', 'section' => 'invoicing', 'description' => 'Optional LaTeX template for typeset PostScript invoices. See the billing documentation for details.', @@ -1566,6 +1577,15 @@ and customer address. Include units.', }, { + 'key' => 'invoice_latexwatermark', + 'section' => 'invoicing', + 'description' => 'Watermark for LaTeX invoices. See "texdoc background" for information on what this can contain. The content itself should be enclosed in braces, optionally followed by a comma and any formatting options.', + 'type' => 'textarea', + 'per_agent' => 1, + 'per_locale' => 1, + }, + + { 'key' => 'invoice_email_pdf', 'section' => 'invoicing', 'description' => 'Send PDF invoice as an attachment to emailed invoices. By default, includes the HTML invoice as the email body, unless invoice_email_pdf_note is set.', diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index bab179f33..fc6d301f0 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -4724,6 +4724,8 @@ sub tables_hashref { 'latexsmallfooter', 'text', 'NULL', '', '', '', 'latexreturnaddress', 'text', 'NULL', '', '', '', 'with_latexcoupon', 'char', 'NULL', '1', '', '', + 'htmlwatermark', 'text', 'NULL', '', '', '', + 'latexwatermark', 'text', 'NULL', '', '', '', 'lpr', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'confnum', diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 44d44e185..0d7c1f159 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -818,35 +818,36 @@ sub print_generic { my @include = ( [ $tc, 'notes' ], [ 'invoice_', 'footer' ], [ 'invoice_', 'smallfooter', ], + [ 'invoice_', 'watermark' ], ); push @include, [ $tc, 'coupon', ] unless $params{'no_coupon'}; foreach my $i (@include) { + # load the configuration for this sub-template + my($base, $include) = @$i; my $inc_file = $conf->key_orbase("$base$format$include", $template); - my @inc_src; - - if ( $conf->exists($inc_file, $agentnum) - && length( $conf->config($inc_file, $agentnum) ) ) { - - @inc_src = $conf->config($inc_file, $agentnum); - - } else { - - $inc_file = $conf->key_orbase("${base}latex$include", $template); - - my $convert_map = $convert_maps{$format}{$include}; - @inc_src = map { s/\[\@--/$delimiters{$format}[0]/g; - s/--\@\]/$delimiters{$format}[1]/g; - $_; - } - &$convert_map( $conf->config($inc_file, $agentnum) ); + my @inc_src = $conf->config($inc_file, $agentnum); + if (!@inc_src) { + my $converter = $convert_maps{$format}{$include}; + if ( $converter ) { + # then attempt to convert LaTeX to the requested format + $inc_file = $conf->key_orbase($base.'latex'.$include, $template); + @inc_src = &$converter( $conf->config($inc_file, $agentnum) ); + foreach (@inc_src) { + # this isn't included in the convert_maps + my ($open, $close) = @{ $delimiters{$format} }; + s/\[\@--/$open/g; + s/--\@\]/$close/g; + } + } + } # else @inc_src is empty and that's fine - } + # make a Text::Template out of it my $inc_tt = new Text::Template ( TYPE => 'ARRAY', @@ -860,6 +861,8 @@ sub print_generic { die $error; } + # fill in variables + $invoice_data{$include} = $inc_tt->fill_in( HASH => \%invoice_data ); $invoice_data{$include} =~ s/\n+$// diff --git a/FS/FS/invoice_conf.pm b/FS/FS/invoice_conf.pm index da448b816..d88c89a7c 100644 --- a/FS/FS/invoice_conf.pm +++ b/FS/FS/invoice_conf.pm @@ -49,6 +49,8 @@ and supports the FS::Conf interface. The following fields are supported: =item htmlreturnaddress - return address (HTML) +=item htmlwatermark - watermark to show in background (HTML) + =item latexnotes - "notes" section (LaTeX) =item latexfooter - footer (LaTeX) @@ -59,6 +61,8 @@ and supports the FS::Conf interface. The following fields are supported: =item latexsmallfooter - footer for pages after the first (LaTeX) +=item latexwatermark - watermark to show in background (LaTeX) + =item with_latexcoupon - 'Y' to print the payment coupon (LaTeX) =item lpr - command to print the invoice (passed on stdin as a PDF) @@ -185,11 +189,13 @@ sub check { || $self->ut_anything('htmlfooter') || $self->ut_anything('htmlsummary') || $self->ut_anything('htmlreturnaddress') + || $self->ut_anything('htmlwatermark') || $self->ut_anything('latexnotes') || $self->ut_anything('latexfooter') || $self->ut_anything('latexsummary') || $self->ut_anything('latexsmallfooter') || $self->ut_anything('latexreturnaddress') + || $self->ut_anything('latexwatermark') # flags || $self->ut_flag('with_latexcoupon') ; diff --git a/conf/invoice_html b/conf/invoice_html index 06ee77588..dfd87c79b 100644 --- a/conf/invoice_html +++ b/conf/invoice_html @@ -1,6 +1,18 @@ -
+
+ +
+ <%= $watermark %> +
@@ -279,4 +312,4 @@

><%= $footer %> -

+
diff --git a/conf/invoice_latex b/conf/invoice_latex index 40ec70313..c7c696b5d 100644 --- a/conf/invoice_latex +++ b/conf/invoice_latex @@ -23,6 +23,19 @@ \usepackage{graphicx} % required for logo graphic \usepackage[utf8]{inputenc} % multilanguage support \usepackage[T1]{fontenc} +[@-- if ( length($watermark) ) { + $OUT .= ' +\usepackage{background} +\backgroundsetup{ + placement=center, + opacity=0.25, + color=black, + angle=0, + contents=' . $watermark . ' +}'; +} +''; +--@] \addtolength{\voffset}{-0.0cm} % top margin to top of header \addtolength{\hoffset}{-0.6cm} % left margin on page diff --git a/httemplate/edit/invoice_conf.html b/httemplate/edit/invoice_conf.html index 7122653f2..861114b1c 100644 --- a/httemplate/edit/invoice_conf.html +++ b/httemplate/edit/invoice_conf.html @@ -49,6 +49,7 @@ my @fields = ( 'Footer', 'Summary header', 'Return address', + 'Watermark', 'Small footer', 'Enable coupon', ), @@ -59,6 +60,7 @@ my @fields = ( { field => 'latexfooter', %textarea }, { field => 'latexsummary', %textarea }, { field => 'latexreturnaddress', %textarea }, + { field => 'latexwatermark', %textarea }, { field => 'latexsmallfooter', %textarea }, { field => 'with_latexcoupon', type => 'checkbox', value => 'Y' }, @@ -68,6 +70,7 @@ my @fields = ( { field => 'htmlfooter', %textarea }, { field => 'htmlsummary', %textarea }, { field => 'htmlreturnaddress', %textarea }, + { field => 'htmlwatermark', %textarea }, # logo { type => 'columnend' }, @@ -87,12 +90,14 @@ my %labels = ( latexfooter latexsummary latexreturnaddress + latexwatermark with_latexcoupon latexsmallfooter htmlnotes htmlfooter htmlsummary htmlreturnaddress + htmlwatermark ) ), ); diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index b4d019a67..f96c05ea5 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -290,7 +290,8 @@ $report_rating{'Unrateable CDRs'} = [ $fsurl.'search/cdr.html?freesidestatus=fai if $curuser->access_right("Usage: Unrateable CDRs"); if ( $curuser->access_right("Usage: Time worked") ) { $report_rating{'Time worked'} = [ $fsurl.'search/report_rt_transaction.html', '' ]; - $report_rating{'Time worked summary'} = [ $fsurl.'search/report_rt_ticket.html', '' ]; + $report_rating{'Time worked summary per ticket'} = [ $fsurl.'search/report_rt_ticket.html', '' ]; + $report_rating{'Time worked summary per customer'} = [ $fsurl.'search/report_rt_cust.html', '' ]; } tie my %report_ticketing_statistics, 'Tie::IxHash', diff --git a/httemplate/search/report_rt_cust.html b/httemplate/search/report_rt_cust.html new file mode 100644 index 000000000..07d497fc5 --- /dev/null +++ b/httemplate/search/report_rt_cust.html @@ -0,0 +1,40 @@ +<& /elements/header.html, 'Time worked per-customer summary' &> + +
+ + + + <& /elements/tr-select-cust_main-status.html, + 'label' => emt('Status'), + &> + + <& /elements/tr-input-beginning_ending.html &> + + + +
+ +
+ + +
+ +<& /elements/footer.html &> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +my $conf = new FS::Conf; + + diff --git a/httemplate/search/rt_cust.html b/httemplate/search/rt_cust.html new file mode 100644 index 000000000..7c31e976b --- /dev/null +++ b/httemplate/search/rt_cust.html @@ -0,0 +1,174 @@ +<& elements/search.html, + 'title' => 'Time worked per-customer summary', + 'name_singular' => 'customer', + 'query' => $sql_query, + 'count_query' => $count_query, + 'header' => [ FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ), + @extra_headers, + 'Support time', + #'Development time', + 'Unclassified time', + ], + 'fields' => [ + \&FS::UI::Web::cust_fields, + @extra_fields, + $support_time_sub, + $unclass_time_sub, + ], + 'color' => [ FS::UI::Web::cust_colors(), + map '', @extra_fields + ], + 'style' => [ FS::UI::Web::cust_styles(), + map '', @extra_fields + ], + 'align' => [ FS::UI::Web::cust_aligns(), + map '', @extra_fields + ], + 'links' => [ ( map { $_ ne 'Cust. Status' ? $link : '' } + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ) + ), + map '', @extra_fields + ], + +&> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data') +; + +#false laziness w/cust_main.html (we're really only filtering on status for now) + +my %search_hash = (); + +#$search_hash{'query'} = $cgi->keywords; + +#scalars +my @scalars = qw ( + agentnum salesnum status address city county state zip country + invoice_terms + no_censustract with_geocode with_email tax no_tax POST no_POST + custbatch usernum + cancelled_pkgs + cust_fields flattened_pkgs + all_tags + all_pkg_classnums + any_pkg_status +); + +for my $param ( @scalars ) { + $search_hash{$param} = scalar( $cgi->param($param) ) + if length($cgi->param($param)); +} + +#lists +for my $param (qw( classnum refnum tagnum pkg_classnum )) { + $search_hash{$param} = [ $cgi->param($param) ]; +} + +### +# etc +### + +my $sql_query = FS::cust_main::Search->search(\%search_hash); +my $count_query = delete($sql_query->{'count_query'}); +my @extra_headers = @{ delete($sql_query->{'extra_headers'}) }; +my @extra_fields = @{ delete($sql_query->{'extra_fields'}) }; + +my $link = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +#eofalse (cust_main.html) + +#false laziness / cribbed from search/rt_ticket.html + +my $twhere = " + WHERE Transactions.ObjectType = 'RT::Ticket' +"; #AND Transactions.ObjectId = Tickets.Id + +my $transaction_time = " +CASE transactions.type when 'Set' + THEN (to_number(newvalue,'999999')-to_number(oldvalue, '999999')) * 60 + ELSE timetaken*60 +END"; + +$twhere .= " + AND ( ( Transactions.Type = 'Set' + AND Transactions.Field = 'TimeWorked' + AND Transactions.NewValue != Transactions.OldValue ) + OR ( Transactions.Type IN ( 'Create', 'Comment', 'Correspond', 'Touch' ) + AND Transactions.TimeTaken > 0 + ) + )"; + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +# TIMESTAMP is Pg-specific... ? +if ( $beginning > 0 ) { + $beginning = "TIMESTAMP '". time2str('%Y-%m-%d %X', $beginning). "'"; + $twhere .= " AND Transactions.Created >= $beginning "; +} +if ( $ending < 4294967295 ) { + $ending = "TIMESTAMP '". time2str('%Y-%m-%d %X', $ending). "'"; + $twhere .= " AND Transactions.Created <= $ending "; +} + +my $transactions = "FROM Transactions $twhere"; + +#eofalse (rt_ticket.html) + +my $support_time_sub = sub { + my $cust_main = shift; + my $sec = 0; + foreach my $ticket ($cust_main->tickets) { + + my $TimeType = FS::Record->scalar_sql( + "SELECT Content FROM ObjectCustomFieldValues + JOIN CustomFields + ON (ObjectCustomFieldValues.CustomField = CustomFields.Id) + WHERE CustomFields.Name = 'TimeType' + AND ObjectCustomFieldValues.ObjectType = 'RT::Ticket' + AND ObjectCustomFieldValues.Disabled = 0 + AND ObjectId = ". $ticket->{id} + ); + next unless $TimeType eq 'support'; + + $sec += FS::Record->scalar_sql( + "SELECT SUM($transaction_time) $transactions ". + " AND Transactions.ObjectId = ". $ticket->{id} + ); + } + + (($sec < 0) ? '-' : '' ). int(abs($sec)/3600)."h".sprintf("%02d",(abs($sec)%3600)/60)."m"; + +}; + +my $unclass_time_sub = sub { + my $cust_main = shift; + my $sec = 0; + foreach my $ticket ($cust_main->tickets) { + + my $TimeType = FS::Record->scalar_sql( + "SELECT Content FROM ObjectCustomFieldValues + JOIN CustomFields + ON (ObjectCustomFieldValues.CustomField = CustomFields.Id) + WHERE CustomFields.Name = 'TimeType' + AND ObjectCustomFieldValues.ObjectType = 'RT::Ticket' + AND ObjectCustomFieldValues.Disabled = 0 + AND ObjectId = ". $ticket->{id} + ); + next unless $TimeType eq ''; + + $sec += FS::Record->scalar_sql( + "SELECT SUM($transaction_time) $transactions ". + " AND Transactions.ObjectId = ". $ticket->{id} + ); + } + + (($sec < 0) ? '-' : '' ). int(abs($sec)/3600)."h".sprintf("%02d",(abs($sec)%3600)/60)."m"; + +}; + + diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi index b9e5a7cc9..58764f881 100755 --- a/httemplate/search/svc_acct.cgi +++ b/httemplate/search/svc_acct.cgi @@ -148,10 +148,34 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { if ( $sortby eq 'seconds' ) { my $tot_time = 0; push @header, emt('Time'); - push @fields, sub { my $svc_acct = shift; - $tot_time += $svc_acct->seconds; - format_time($svc_acct->seconds); - }; + + if ( $conf->exists('svc_acct-display_paid_time_remaining') ) { + push @fields, sub { my $svc_acct = shift; + my $seconds = $svc_acct->seconds; + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + my $part_pkg = $cust_pkg->part_pkg; + + $tot_time += $svc_acct->seconds; + + $timepermonth = $part_pkg->option('seconds'); + $timepermonth = $timepermonth / $part_pkg->freq + if $part_pkg->freq =~ /^\d+$/ && $part_pkg->freq != 0; + my $recur = $part_pkg->base_recur($cust_pkg); + + return format_time($seconds) + unless $timepermonth && $recur; + + format_time($seconds). + sprintf(' (%.2fx monthly)', $seconds / $timepermonth ); + + }; + } else { + push @fields, sub { my $svc_acct = shift; + $tot_time += $svc_acct->seconds; + format_time($svc_acct->seconds); + }; + } + push @links, ''; $align .= 'r'; push @color, '';