+=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 or company_address
+
+=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 || '2037-12-31';
+ my $paydate = $self->paydate =~ /^\S+$/ ? $self->paydate : '2037-12-31';
+
+ 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)
+ );
+ if ( length($retadd) ) {
+ $letter_data{returnaddress} = $retadd;
+ } elsif ( grep /\S/, $conf->config('company_address') ) {
+ $letter_data{returnaddress} =
+ join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg,
+ $conf->config('company_address')
+ );
+ } else {
+ $letter_data{returnaddress} = '~';
+ }
+ }
+
+ $letter_data{conf_dir} = "$FS::UID::conf_dir/conf.$FS::UID::datasrc";
+
+ $letter_data{company_name} = $conf->config('company_name');
+
+ 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 ) = @_;
+
+ #yuck. this whole thing needs to be reconciled better with 1.9's idea of
+ #agent-specific Conf
+
+ use FS::part_event::Condition;
+
+ my $agentnum = $self->agentnum;
+
+ my $regexp = '';
+ if ( driver_name =~ /^Pg/i ) {
+ $regexp = '~';
+ } elsif ( driver_name =~ /^mysql/i ) {
+ $regexp = 'REGEXP';
+ } else {
+ die "don't know how to use regular expressions in ". driver_name. " databases";
+ }
+
+ my $part_event_option =
+ qsearchs({
+ 'select' => 'part_event_option.*',
+ 'table' => 'part_event_option',
+ 'addl_from' => q{
+ LEFT JOIN part_event USING ( eventpart )
+ LEFT JOIN part_event_option AS peo_agentnum
+ ON ( part_event.eventpart = peo_agentnum.eventpart
+ AND peo_agentnum.optionname = 'agentnum'
+ AND peo_agentnum.optionvalue }. $regexp. q{ '(^|,)}. $agentnum. q{(,|$)'
+ )
+ LEFT JOIN part_event_option AS peo_cust_bill_age
+ ON ( part_event.eventpart = peo_cust_bill_age.eventpart
+ AND peo_cust_bill_age.optionname = 'cust_bill_age'
+ )
+ },
+ #'hashref' => { 'optionname' => $option },
+ #'hashref' => { 'part_event_option.optionname' => $option },
+ 'extra_sql' =>
+ " WHERE part_event_option.optionname = ". dbh->quote($option).
+ " AND action = 'cust_bill_send_agent' ".
+ " AND ( disabled IS NULL OR disabled != 'Y' ) ".
+ " AND peo_agentnum.optionname = 'agentnum' ".
+ " AND agentnum IS NULL OR agentnum = $agentnum ".
+ " ORDER BY
+ CASE WHEN peo_cust_bill_age.optionname != 'cust_bill_age'
+ THEN -1
+ ELSE ". FS::part_event::Condition->age2seconds_sql('peo_cust_bill_age.optionvalue').
+ " END
+ , part_event.weight".
+ " LIMIT 1"
+ });
+
+ unless ( $part_event_option ) {
+ return $self->agent->invoice_template || ''
+ if $option eq 'agent_templatename';
+ return '';
+ }
+
+ $part_event_option->optionvalue;
+
+}
+
+sub queued_bill {
+ ## actual sub, not a method, designed to be called from the queue.
+ ## sets up the customer, and calls the bill_and_collect
+ my (%args) = @_; #, ($time, $invoice_time, $check_freq, $resetup) = @_;
+ my $cust_main = qsearchs( 'cust_main', { custnum => $args{'custnum'} } );
+ $cust_main->bill_and_collect(
+ %args,
+ );
+}
+