summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Davis <jeremyd@freeside.biz>2015-06-26 17:38:02 -0400
committerJeremy Davis <jeremyd@freeside.biz>2015-06-26 17:38:02 -0400
commitdaa7e41eabe79e3ca7f65e060e7c588715bf236b (patch)
tree556ea2127bbc3f9af5ebcad3caa54a96192630f1
parentd8a43b104aca6a01f8f1fd821dd29f3dffd59919 (diff)
parent71dba4c13f3a420115ad87dfa6df82db6618bd97 (diff)
Merge branch '3.x-pre' of git.freeside.biz:/home/git/freeside into FREESIDE_3_BRANCH
-rw-r--r--FS/FS/Conf.pm20
-rw-r--r--FS/FS/Schema.pm2
-rw-r--r--FS/FS/Template_Mixin.pm39
-rw-r--r--FS/FS/invoice_conf.pm6
-rw-r--r--conf/invoice_html41
-rw-r--r--conf/invoice_latex13
-rw-r--r--httemplate/edit/invoice_conf.html5
-rw-r--r--httemplate/elements/menu.html3
-rw-r--r--httemplate/search/report_rt_cust.html40
-rw-r--r--httemplate/search/rt_cust.html174
-rwxr-xr-xhttemplate/search/svc_acct.cgi32
11 files changed, 348 insertions, 27 deletions
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 <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:2.1:Documentation:Administration#Typeset_.28LaTeX.29_invoice_templates">billing documentation</a> 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 @@
<STYLE TYPE="text/css">
-.invoice { font-family: sans-serif; font-size: 10pt }
-.invoice_header { font-size: 10pt }
+.invoice {
+ font-family: sans-serif;
+ font-size: 10pt;
+ display: inline-block;
+ padding: 4pt;
+ border: 1px solid black;
+ background-color: white;
+ min-width: 625px;
+ position: relative;
+}
+.invoice_header {
+ font-size: 10pt;
+ border-spacing: 4pt;
+}
.invoice_headerright TH { border-top: 2px solid #000000; border-bottom: 2px solid #000000 }
.invoice_headerright TD { font-size: 10pt; empty-cells: show }
.invoice_summary TH { border-bottom: 2px solid #000000 }
@@ -12,9 +24,30 @@
.invoice_extdesc TD { font-size: 8pt }
.invoice_totaldesc TD { font-size: 10pt; empty-cells: show }
.allcaps { text-transform:uppercase; font-size: 12pt }
+.watermark-box {
+ z-index: 10;
+ position: absolute;
+ text-align: center;
+ padding: 0;
+ filter: alpha(opacity=25);
+ opacity: 0.25;
+ width: 100%;
+ height: 100%;
+ display: table;
+ pointer-events: none;
+}
+.watermark-content {
+ display: table-cell;
+ vertical-align: middle;
+ font-size: 96pt;
+}
</STYLE>
-<table class="invoice" bgcolor="#ffffff" WIDTH=625 CELLSPACING=8 STYLE="border:1px solid #000000"><tr><td>
+<div class="invoice">
+
+ <DIV CLASS="watermark-box"><DIV CLASS="watermark-content">
+ <%= $watermark %>
+ </DIV></DIV>
<table class="invoice_header" width="100%">
<tr>
@@ -279,4 +312,4 @@
<hr NOSHADE SIZE=2 COLOR="#000000">
<p align="center" <%= $smallerfooter ? 'STYLE="font-size:75%;"' : '' %>><%= $footer %>
-</td></tr></table>
+</div>
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' &>
+
+<FORM ACTION="rt_cust.html" METHOD="GET">
+
+<TABLE>
+
+ <& /elements/tr-select-cust_main-status.html,
+ 'label' => emt('Status'),
+ &>
+
+ <& /elements/tr-input-beginning_ending.html &>
+
+<!--
+ <& /elements/tr-select.html,
+ label => 'Time category:',
+ field => 'category',
+ options => [ '', 'development', 'support' ],
+ option_labels => { '' => 'all' },
+ curr_value => 'development',
+ &>
+
+ <& /elements/tr-select-otaker.html &>
+-->
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Search">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+my $conf = new FS::Conf;
+
+</%init>
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";
+
+};
+
+</%init>
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, '';