From affa0392b9ad9ecd49543e30fa096f3be1ece764 Mon Sep 17 00:00:00 2001 From: jeff Date: Thu, 21 Jun 2007 04:03:14 +0000 Subject: [PATCH] latex welcome letters (1677) --- FS/FS/Conf.pm | 7 +++ FS/FS/Misc.pm | 75 ++++++++++++++++++++++++ FS/FS/cust_bill.pm | 90 +++-------------------------- FS/FS/cust_main.pm | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++- FS/FS/cust_pkg.pm | 15 +++++ conf/welcome_letter | 121 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 386 insertions(+), 84 deletions(-) create mode 100644 conf/welcome_letter diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 394ffeb02..fdefd56c0 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 Text::Template documentation and the billing documentation for details on the template substitution language. A variable exists for each fieldname in the customer record ($first, $last, etc). The following additional variables are available', + '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 Text::Template documentation for details on the template substitution language. The following variables are available', diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm index a1d41e0a4..231ef0654 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 () { + $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 da36a85f4..990a9fc30 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 () { - $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 ac1fba5ba..641b8cd30 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). + +OPTIONS is a hash and may include + +I - 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 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 9b5066f75..055b87b61 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 000000000..3fcf04e68 --- /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} + -- 2.11.0