summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjeff <jeff>2007-06-21 04:03:14 +0000
committerjeff <jeff>2007-06-21 04:03:14 +0000
commitaffa0392b9ad9ecd49543e30fa096f3be1ece764 (patch)
treefb946ee494d49b1a95c6e31fad232e9af4a36fa5
parent275264b6d4da672231c48678d9f1267884128a0d (diff)
latex welcome letters (1677)
-rw-r--r--FS/FS/Conf.pm7
-rw-r--r--FS/FS/Misc.pm75
-rw-r--r--FS/FS/cust_bill.pm90
-rw-r--r--FS/FS/cust_main.pm162
-rw-r--r--FS/FS/cust_pkg.pm15
-rw-r--r--conf/welcome_letter121
6 files changed, 386 insertions, 84 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 394ffeb..fdefd56 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -1196,6 +1196,13 @@ httemplate/docs/config.html
},
{
+ 'key' => 'welcome_letter',
+ 'section' => '',
+ 'description' => 'Optional LaTex template file for a printed welcome letter. A welcome letter is printed the first time a cust_pkg record is created. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation and the billing documentation for details on the template substitution language. A variable exists for each fieldname in the customer record (<code>$first, $last, etc</code>). The following additional variables are available<ul><li><code>$payby</code> - a friendler represenation of the field<li><code>$payinfo</code> - the masked payment information<li><code>$expdate</code> - the time at which the payment method expires (a UNIX timestamp)<li><code>$returnaddress</code> - the invoice return address for this customer\'s agent</ul>',
+ 'type' => 'textarea',
+ },
+
+ {
'key' => 'warning_email',
'section' => '',
'description' => 'Template file for warning email. Warning emails are sent to the customer email invoice destination(s) each time a svc_acct record has its usage drop below a threshold or 0. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code> <li><code>$column</code> <li><code>$amount</code> <li><code>$threshold</code></ul>',
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
index a1d41e0..231ef06 100644
--- a/FS/FS/Misc.pm
+++ b/FS/FS/Misc.pm
@@ -13,6 +13,7 @@ use Data::Dumper;
@EXPORT_OK = qw( send_email send_fax
states_hash counties state_label
card_types
+ generate_ps do_print
);
$DEBUG = 0;
@@ -471,6 +472,80 @@ sub card_types {
\%card_types;
}
+=item generate_ps FILENAME
+
+Returns an postscript rendition of the LaTex file, as a scalar.
+FILENAME does not contain the .tex suffix and is unlinked by this function.
+
+=cut
+
+use String::ShellQuote;
+
+sub generate_ps {
+ my $file = shift;
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ chdir($dir);
+
+ my $sfile = shell_quote $file;
+
+ system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+ or die "pslatex $file.tex failed; see $file.log for details?\n";
+ system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+ or die "pslatex $file.tex failed; see $file.log for details?\n";
+
+ system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ) == 0
+ or die "dvips failed";
+
+ open(POSTSCRIPT, "<$file.ps")
+ or die "can't open $file.ps: $! (error in LaTeX template?)\n";
+
+ unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps", "$file.tex");
+
+ my $ps = '';
+
+ if ( $conf->exists('lpr-postscript_prefix') ) {
+ my $prefix = $conf->config('lpr-postscript_prefix');
+ $ps .= eval qq("$prefix");
+ }
+
+ while (<POSTSCRIPT>) {
+ $ps .= $_;
+ }
+
+ close POSTSCRIPT;
+
+ if ( $conf->exists('lpr-postscript_suffix') ) {
+ my $suffix = $conf->config('lpr-postscript_suffix');
+ $ps .= eval qq("$suffix");
+ }
+
+ return $ps;
+
+}
+
+=item print ARRAYREF
+
+Sends the lines in ARRAYREF to the printer.
+
+=cut
+
+use IPC::Run3;
+
+sub do_print {
+ my $data = shift;
+
+ my $lpr = $conf->config('lpr');
+
+ my $outerr = '';
+ run3 $lpr, $data, \$outerr, \$outerr;
+ if ( $? ) {
+ $outerr = ": $outerr" if length($outerr);
+ die "Error from $lpr (exit status ". ($?>>8). ")$outerr\n";
+ }
+
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
index da36a85..990a9fc 100644
--- a/FS/FS/cust_bill.pm
+++ b/FS/FS/cust_bill.pm
@@ -5,7 +5,6 @@ use vars qw( @ISA $DEBUG $me $conf $money_char );
use vars qw( $invoice_lines @buf ); #yuck
use Fcntl qw(:flock); #for spool_csv
use List::Util qw(min max);
-use IPC::Run3;
use Date::Format;
use Text::Template 1.20;
use File::Temp 0.14;
@@ -13,7 +12,7 @@ use String::ShellQuote;
use HTML::Entities;
use Locale::Country;
use FS::UID qw( datasrc );
-use FS::Misc qw( send_email send_fax );
+use FS::Misc qw( send_email send_fax generate_ps do_print );
use FS::Record qw( qsearch qsearchs dbh );
use FS::cust_main_Mixin;
use FS::cust_main;
@@ -854,15 +853,7 @@ sub print {
my $self = shift;
my $template = scalar(@_) ? shift : '';
- my $lpr = $conf->config('lpr');
-
- my $outerr = '';
- run3 $lpr, $self->lpr_data($template), \$outerr, \$outerr;
- if ( $? ) {
- $outerr = ": $outerr" if length($outerr);
- die "Error from $lpr (exit status ". ($?>>8). ")$outerr\n";
- }
-
+ do_print $self->lpr_data($template);
}
=item fax [ TEMPLATENAME ]
@@ -1441,43 +1432,12 @@ sub batch_card {
sub _agent_template {
my $self = shift;
- $self->_agent_plandata('agent_templatename');
+ $self->cust_main->agent_template;
}
sub _agent_invoice_from {
my $self = shift;
- $self->_agent_plandata('agent_invoice_from');
-}
-
-sub _agent_plandata {
- my( $self, $option ) = @_;
-
- my $part_bill_event = qsearchs( 'part_bill_event',
- {
- 'payby' => $self->cust_main->payby,
- 'plan' => 'send_agent',
- 'plandata' => { 'op' => '~',
- 'value' => "(^|\n)agentnum ".
- '([0-9]*, )*'.
- $self->cust_main->agentnum.
- '(, [0-9]*)*'.
- "(\n|\$)",
- },
- },
- '',
- 'ORDER BY seconds LIMIT 1'
- );
-
- return '' unless $part_bill_event;
-
- if ( $part_bill_event->plandata =~ /^$option (.*)$/m ) {
- return $1;
- } else {
- warn "can't parse part_bill_event eventpart#". $part_bill_event->eventpart.
- " plandata for $option";
- return '';
- }
-
+ $self->cust_main->agent_invoice_from;
}
=item print_text [ TIME [ , TEMPLATE ] ]
@@ -2088,45 +2048,9 @@ sub print_ps {
my $self = shift;
my ($file, $lfile) = $self->print_latex(@_);
-
- my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
- chdir($dir);
-
- my $sfile = shell_quote $file;
-
- system("pslatex $sfile.tex >/dev/null 2>&1") == 0
- or die "pslatex $file.tex failed; see $file.log for details?\n";
- system("pslatex $sfile.tex >/dev/null 2>&1") == 0
- or die "pslatex $file.tex failed; see $file.log for details?\n";
-
- system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ) == 0
- or die "dvips failed";
-
- open(POSTSCRIPT, "<$file.ps")
- or die "can't open $file.ps: $! (error in LaTeX template?)\n";
-
- unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps", "$file.tex");
- unlink("$lfile");
-
- my $ps = '';
-
- if ( $conf->exists('lpr-postscript_prefix') ) {
- my $prefix = $conf->config('lpr-postscript_prefix');
- $ps .= eval qq("$prefix");
- }
-
- while (<POSTSCRIPT>) {
- $ps .= $_;
- }
-
- if ( $conf->exists('lpr-postscript_suffix') ) {
- my $suffix = $conf->config('lpr-postscript_suffix');
- $ps .= eval qq("$suffix");
- }
-
- close POSTSCRIPT;
-
- return $ps;
+ my $ps = generate_ps($file);
+ unlink($lfile);
+ $ps;
}
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index ac1fba5..641b8cd 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -24,7 +24,7 @@ use Locale::Country;
use Data::Dumper;
use FS::UID qw( getotaker dbh );
use FS::Record qw( qsearchs qsearch dbdef );
-use FS::Misc qw( send_email );
+use FS::Misc qw( send_email generate_ps do_print );
use FS::Msgcat qw(gettext);
use FS::cust_pkg;
use FS::cust_svc;
@@ -5006,6 +5006,166 @@ sub notify {
}
+=item generate_letter CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
+
+Generates a templated notification to the customer (see L<Text::Template>).
+
+OPTIONS is a hash and may include
+
+I<extra_fields> - a hashref of name/value pairs which will be substituted
+ into the template. These values may override values mentioned below
+ and those from the customer record.
+
+The following variables are available in the template instead of or in addition
+to the fields of the customer record.
+
+I<$payby> - a description of the method of payment for the customer
+ # would be nice to use FS::payby::shortname
+I<$payinfo> - the masked account information used to collect for this customer
+I<$expdate> - the expiration of the customer payment method in seconds from epoch
+I<$returnaddress> - the return address defaults to invoice_latexreturnaddress
+
+=cut
+
+sub generate_letter {
+ my ($self, $template, %options) = @_;
+
+ return unless $conf->exists($template);
+
+ my $letter_template = new Text::Template
+ ( TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", $conf->config($template)],
+ DELIMITERS => [ '[@--', '--@]' ],
+ )
+ or die "can't create new Text::Template object: Text::Template::ERROR";
+
+ $letter_template->compile()
+ or die "can't compile template: Text::Template::ERROR";
+
+ my %letter_data = map { $_ => $self->$_ } $self->fields;
+ $letter_data{payinfo} = $self->mask_payinfo;
+
+ my $paydate = $self->paydate;
+ my $payby = $self->payby;
+ my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
+ my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear);
+
+ #credit cards expire at the end of the month/year of their exp date
+ if ($payby eq 'CARD' || $payby eq 'DCRD') {
+ $letter_data{payby} = 'credit card';
+ ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
+ $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
+ $expire_time--;
+ }elsif ($payby eq 'COMP') {
+ $letter_data{payby} = 'complimentary account';
+ }else{
+ $letter_data{payby} = 'current method';
+ }
+ $letter_data{expdate} = $expire_time;
+
+ for (keys %{$options{extra_fields}}){
+ $letter_data{$_} = $options{extra_fields}->{$_};
+ }
+
+ unless(exists($letter_data{returnaddress})){
+ my $retadd = join("\n", $conf->config_orbase( 'invoice_latexreturnaddress',
+ $self->_agent_template)
+ );
+
+ $letter_data{returnaddress} = length($retadd) ? $retadd : '~';
+ }
+
+ $letter_data{conf_dir} = "$FS::UID::conf_dir/conf.$FS::UID::datasrc";
+
+ my $dir = $FS::UID::conf_dir."cache.". $FS::UID::datasrc;
+ my $fh = new File::Temp( TEMPLATE => 'letter.'. $self->custnum. '.XXXXXXXX',
+ DIR => $dir,
+ SUFFIX => '.tex',
+ UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+
+ $letter_template->fill_in( OUTPUT => $fh, HASH => \%letter_data );
+ close $fh;
+ $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
+ return $1;
+}
+
+=item print_ps TEMPLATE
+
+Returns an postscript letter filled in from TEMPLATE, as a scalar.
+
+=cut
+
+sub print_ps {
+ my $self = shift;
+ my $file = $self->generate_letter(@_);
+ FS::Misc::generate_ps($file);
+}
+
+=item print TEMPLATE
+
+Prints the filled in template.
+
+TEMPLATE is the name of a L<Text::Template> to fill in and print.
+
+=cut
+
+sub queueable_print {
+ my %opt = @_;
+
+ my $self = qsearchs('cust_main', { 'custnum' => $opt{custnum} } )
+ or die "invalid customer number: " . $opt{custvnum};
+
+ my $error = $self->print( $opt{template} );
+ die $error if $error;
+}
+
+sub print {
+ my ($self, $template) = (shift, shift);
+ do_print [ $self->print_ps($template) ];
+}
+
+sub agent_template {
+ my $self = shift;
+ $self->_agent_plandata('agent_templatename');
+}
+
+sub agent_invoice_from {
+ my $self = shift;
+ $self->_agent_plandata('agent_invoice_from');
+}
+
+sub _agent_plandata {
+ my( $self, $option ) = @_;
+
+ my $part_bill_event = qsearchs( 'part_bill_event',
+ {
+ 'payby' => $self->payby,
+ 'plan' => 'send_agent',
+ 'plandata' => { 'op' => '~',
+ 'value' => "(^|\n)agentnum ".
+ '([0-9]*, )*'.
+ $self->agentnum.
+ '(, [0-9]*)*'.
+ "(\n|\$)",
+ },
+ },
+ '',
+ 'ORDER BY seconds LIMIT 1'
+ );
+
+ return '' unless $part_bill_event;
+
+ if ( $part_bill_event->plandata =~ /^$option (.*)$/m ) {
+ return $1;
+ } else {
+ warn "can't parse part_bill_event eventpart#". $part_bill_event->eventpart.
+ " plandata for $option";
+ return '';
+ }
+
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index 9b5066f..055b87b 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -226,6 +226,21 @@ sub insert {
}
}
+ if ($conf->config('welcome_letter') && $self->cust_main->num_pkgs == 1) {
+ my $queue = new FS::queue {
+ 'job' => 'FS::cust_main::queueable_print',
+ };
+ $error = $queue->insert(
+ 'custnum' => $self->custnum,
+ 'template' => 'welcome_letter',
+ );
+
+ if ($error) {
+ warn "can't send welcome letter: $error";
+ }
+
+ }
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
diff --git a/conf/welcome_letter b/conf/welcome_letter
new file mode 100644
index 0000000..3fcf04e
--- /dev/null
+++ b/conf/welcome_letter
@@ -0,0 +1,121 @@
+%% file: random_latex
+%% Purpose: Multipage template for welcome letters
+%%
+%% Based on work by
+%%
+%% Mark Asplen-Taylor
+%% Asplen Management Ltd
+%% www.asplen.co.uk
+%%
+%% Kristian Hoffman
+%%
+%% Changes
+%% 0.1 6/19/07 Created
+
+\documentclass[letterpaper]{article}
+
+\hyphenpenalty=5000
+\usepackage{fancyhdr,lastpage,ifthen,afterpage}
+\usepackage{graphicx} % required for logo graphic
+
+\addtolength{\voffset}{-0.0cm} % top margin to top of header
+\addtolength{\hoffset}{-0.6cm} % left margin on page
+\addtolength{\topmargin}{-1.25cm} % top margin to top of header
+\setlength{\headheight}{2.0cm} % height of header
+\setlength{\headsep}{1.0cm} % between header and text
+\setlength{\footskip}{1.0cm} % bottom of footer from bottom of text
+
+%\addtolength{\textwidth}{2.1in} % width of text
+\setlength{\textwidth}{19.5cm}
+\setlength{\textheight}{19.5cm}
+\setlength{\oddsidemargin}{-0.9cm} % odd page left margin
+\setlength{\evensidemargin}{-0.9cm} % even page left margin
+
+\renewcommand{\headrulewidth}{0pt}
+\renewcommand{\footrulewidth}{0pt}
+
+% Adjust the inset of the mailing address
+\newcommand{\addressinset}[1][]{\hspace{1.0cm}}
+
+% Adjust the inset of the return address and logo
+\newcommand{\returninset}[1][]{\hspace{-0.25cm}}
+
+% New command for address lines i.e. skip them if blank
+\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\newline}}
+
+% Remove plain style header/footer
+\fancypagestyle{plain}{
+ \fancyhead{}
+}
+\fancyhf{}
+
+% Define fancy header/footer for first and subsequent pages
+
+\fancyfoot[R]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ ~
+ }
+ { % ... pages
+ \small{\thepage\ of \pageref{LastPage}}
+ }
+}
+
+\fancyhead[L]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \returninset
+ \makebox{
+ \begin{tabular}{ll}
+ \includegraphics{[@-- $conf_dir --@]/logo.eps} &
+ \begin{minipage}[b]{5.5cm}
+[@-- $returnaddress --@]
+ \end{minipage}
+ \end{tabular}
+ }
+ }
+ { % ... pages
+ %\includegraphics{[@-- $conf_dir --@]/logo.eps} % Uncomment if you want the logo on all pages.
+ }
+}
+
+\pagestyle{fancy}
+
+
+%% Font options are:
+%% bch Bitsream Charter
+%% put Utopia
+%% phv Adobe Helvetica
+%% pnc New Century Schoolbook
+%% ptm Times
+%% pcr Courier
+
+\renewcommand{\familydefault}{phv}
+
+
+\begin{document}
+%
+\begin{tabular}{ll}
+\addressinset \rule{0cm}{0cm} &
+\makebox{
+\begin{minipage}[t]{5.0cm}
+\vspace{0.25cm}
+\textbf{[@-- $payname --@]}\\
+\addressline{[@-- $company --@]}
+\addressline{[@-- $address1 --@]}
+\addressline{[@-- $address2 --@]}
+\addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}
+\addressline{[@-- $country --@]}
+\end{minipage}}
+\end{tabular}
+\vspace{1.5cm}
+\\
+% Your content goes here
+Dear [@-- $first --@] [@-- $last --@]:\\
+\\
+ Thank you for choosing Freeside. We aim to meet all the billing needs of
+ [@-- $company --@]. Please do not hesitate to contact us for any additional
+ service or features you require.\\
+
+\end{document}
+