From e574b96088606fe1624223d977e8091b9eab0600 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 28 Jul 2010 23:16:31 +0000 Subject: [PATCH] msg_template improvements, RT#8324 --- FS/FS/Conf.pm | 130 +++++++++++++++++++-------- FS/FS/Cron/alert_expiration.pm | 79 +++++++++------- FS/FS/Cron/notify.pm | 12 ++- FS/FS/Schema.pm | 1 + FS/FS/Upgrade.pm | 3 + FS/FS/cust_main.pm | 56 +++++++----- FS/FS/cust_pkg.pm | 23 +++-- FS/FS/msg_template.pm | 184 +++++++++++++++++++++++++++++++------- FS/FS/svc_acct.pm | 143 +++++++++++++++-------------- httemplate/config/config-view.cgi | 2 +- httemplate/edit/msg_template.html | 134 ++++++++++++++++++++++++++- 11 files changed, 559 insertions(+), 208 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 0d77f3de6..92a990d8d 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -549,21 +549,36 @@ worry that config_items is freeside-specific and icky. "Solo", ); -@base_items = qw ( - invoice_template - invoice_latex - invoice_latexreturnaddress - invoice_latexfooter - invoice_latexsmallfooter - invoice_latexnotes - invoice_latexcoupon - invoice_html - invoice_htmlreturnaddress - invoice_htmlfooter - invoice_htmlnotes - logo.png - logo.eps - ); +@base_items = qw( +invoice_template +invoice_latex +invoice_latexreturnaddress +invoice_latexfooter +invoice_latexsmallfooter +invoice_latexnotes +invoice_latexcoupon +invoice_html +invoice_htmlreturnaddress +invoice_htmlfooter +invoice_htmlnotes +logo.png +logo.eps +); + +my %msg_template_options = ( + 'type' => 'select-sub', + 'options_sub' => sub { require FS::Record; + require FS::agent; + require FS::msg_template; + map { $_->msgnum, $_->msgname } + qsearch('msg_template', { disabled => '' }); + }, + 'option_sub' => sub { require FS::msg_template; + my $msg_template = FS::msg_template->by_key(shift); + $msg_template ? $msg_template->msgname : '' + }, +); + #Billing (81 items) #Invoicing (50 items) @@ -572,7 +587,6 @@ worry that config_items is freeside-specific and icky. #... #Unclassified (77 items) - @config_items = map { new FS::ConfItem $_ } ( { @@ -584,7 +598,7 @@ worry that config_items is freeside-specific and icky. { 'key' => 'alert_expiration', - 'section' => 'billing', + 'section' => 'notification', 'description' => 'Enable alerts about billing method expiration (i.e. expiring credit cards).', 'type' => 'checkbox', 'per_agent' => 1, @@ -592,11 +606,18 @@ worry that config_items is freeside-specific and icky. { 'key' => 'alerter_template', - 'section' => 'billing', - 'description' => 'Template file for billing method expiration alerts (i.e. expiring credit cards). See the billing documentation for details.', + 'section' => 'deprecated', + 'description' => 'Template file for billing method expiration alerts (i.e. expiring credit cards).', 'type' => 'textarea', 'per_agent' => 1, }, + + { + 'key' => 'alerter_msgnum', + 'section' => 'notification', + 'description' => 'Template to use for credit card expiration alerts.', + %msg_template_options, + }, { 'key' => 'apacheip', @@ -1787,44 +1808,58 @@ and customer address. Include units.', }, { + 'key' => 'decline_msgnum', + 'section' => 'notification', + 'description' => 'Template to use for credit card and electronic check decline messages.', + %msg_template_options, + }, + + { 'key' => 'declinetemplate', - 'section' => 'billing', + 'section' => 'deprecated', 'description' => 'Template file for credit card and electronic check decline emails.', 'type' => 'textarea', }, { 'key' => 'emaildecline', - 'section' => 'billing', + 'section' => 'notification', 'description' => 'Enable emailing of credit card and electronic check decline notices.', 'type' => 'checkbox', }, { 'key' => 'emaildecline-exclude', - 'section' => 'billing', + 'section' => 'notification', 'description' => 'List of error messages that should not trigger email decline notices, one per line.', 'type' => 'textarea', }, { + 'key' => 'cancel_msgnum', + 'section' => 'notification', + 'description' => 'Template to use for cancellation emails.', + %msg_template_options, + }, + + { 'key' => 'cancelmessage', - 'section' => 'billing', + 'section' => 'deprecated', 'description' => 'Template file for cancellation emails.', 'type' => 'textarea', }, { 'key' => 'cancelsubject', - 'section' => 'billing', + 'section' => 'deprecated', 'description' => 'Subject line for cancellation emails.', 'type' => 'text', }, { 'key' => 'emailcancel', - 'section' => 'billing', - 'description' => 'Enable emailing of cancellation notices. Make sure to fill in the cancelmessage and cancelsubject configuration values as well.', + 'section' => 'notification', + 'description' => 'Enable emailing of cancellation notices. Make sure to select the template in the cancel_msgnum option.', 'type' => 'checkbox', }, @@ -1878,16 +1913,23 @@ and customer address. Include units.', }, { + 'key' => 'welcome_msgnum', + 'section' => 'notification', + 'description' => 'Template to use for welcome messages when a svc_acct record is created.', + %msg_template_options, + }, + + { 'key' => 'welcome_email', - 'section' => '', - 'description' => 'Template file for welcome email. Welcome emails are sent to the customer email invoice destination(s) each time a svc_acct record is created. See the Text::Template documentation for details on the template substitution language. The following variables are available', + 'section' => 'deprecated', + 'description' => 'Template file for welcome email. Welcome emails are sent to the customer email invoice destination(s) each time a svc_acct record is created.', 'type' => 'textarea', 'per_agent' => 1, }, { 'key' => 'welcome_email-from', - 'section' => '', + 'section' => 'deprecated', 'description' => 'From: address header for welcome email', 'type' => 'text', 'per_agent' => 1, @@ -1895,7 +1937,7 @@ and customer address. Include units.', { 'key' => 'welcome_email-subject', - 'section' => '', + 'section' => 'deprecated', 'description' => 'Subject: header for welcome email', 'type' => 'text', 'per_agent' => 1, @@ -1903,7 +1945,7 @@ and customer address. Include units.', { 'key' => 'welcome_email-mimetype', - 'section' => '', + 'section' => 'deprecated', 'description' => 'MIME type for welcome email', 'type' => 'select', 'select_enum' => [ 'text/plain', 'text/html' ], @@ -1917,37 +1959,44 @@ and customer address. Include units.', 'type' => 'textarea', }, +# { +# 'key' => 'warning_msgnum', +# 'section' => 'notification', +# 'description' => 'Template to use for warning messages, sent to the customer email invoice destination(s) when a svc_acct record has its usage drop below a threshold.', +# %msg_template_options, +# }, + { 'key' => 'warning_email', - 'section' => '', + 'section' => 'notification', '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', 'type' => 'textarea', }, { 'key' => 'warning_email-from', - 'section' => '', + 'section' => 'notification', 'description' => 'From: address header for warning email', 'type' => 'text', }, { 'key' => 'warning_email-cc', - 'section' => '', + 'section' => 'notification', 'description' => 'Additional recipient(s) (comma separated) for warning email when remaining usage reaches zero.', 'type' => 'text', }, { 'key' => 'warning_email-subject', - 'section' => '', + 'section' => 'notification', 'description' => 'Subject: header for warning email', 'type' => 'text', }, { 'key' => 'warning_email-mimetype', - 'section' => '', + 'section' => 'notification', 'description' => 'MIME type for warning email', 'type' => 'select', 'select_enum' => [ 'text/plain', 'text/html' ], @@ -2880,8 +2929,15 @@ and customer address. Include units.', }, { + 'key' => 'impending_recur_msgnum', + 'section' => 'notification', + 'description' => 'Template to use for alerts about first-time recurring billing.', + %msg_template_options, + }, + + { 'key' => 'impending_recur_template', - 'section' => 'billing', + 'section' => 'deprecated', 'description' => 'Template file for alerts about looming first time recurrant billing. See the Text::Template documentation for details on the template substitition language. Also see packages with a flat price plan The following variables are available', #
  • $payby
  • $expdate most likely only confuse 'type' => 'textarea', diff --git a/FS/FS/Cron/alert_expiration.pm b/FS/FS/Cron/alert_expiration.pm index a9b9da9e9..364fc60c7 100644 --- a/FS/FS/Cron/alert_expiration.pm +++ b/FS/FS/Cron/alert_expiration.pm @@ -2,7 +2,7 @@ package FS::Cron::alert_expiration; use vars qw( @ISA @EXPORT_OK); use Exporter; -use FS::Record qw(qsearch); +use FS::Record qw(qsearch qsearchs); use FS::Conf; use FS::cust_main; use FS::Misc; @@ -58,6 +58,7 @@ sub alert_expiration { } return if(!@customers); foreach my $customer (@customers) { + next if !($customer->ncancelled_pkgs); # skip inactive customers my $paydate = $customer->paydate; next if $paydate =~ /^\s*$/; # skip empty expiration dates @@ -91,25 +92,32 @@ sub alert_expiration { if (grep { $expire_time < $_date + $_ && $expire_time > $_date + $_ - $window_time } ($warning_time, $urgent_time, $panic_time) ) { + # Send an expiration notice. my $agentnum = $customer->agentnum; - $mail_sender = $conf->config('invoice_from', $agentnum); - $failure_recipient = $conf->config('invoice_from', $agentnum) - || 'postmaster'; - - my @alerter_template = $conf->config('alerter_template', $agentnum) - or die 'cannot load config file alerter_template'; - - my $alerter = new Text::Template(TYPE => 'ARRAY', - SOURCE => [ - map "$_\n", @alerter_template - ]) - or die "can't create Text::Template object: $Text::Template::ERROR"; - - $alerter->compile() - or die "can't compile template: $Text::Template::ERROR"; - - my @packages = $customer->ncancelled_pkgs; - if(@packages) { + my $error = ''; + + my $msgnum = $conf->config('alerter_msgnum', $agentnum); + if ( $msgnum ) { # new hotness + my $msg_template = qsearchs('msg_template', { msgnum => $msgnum } ); + $error = $msg_template->send('cust_main' => $customer); + } + else { #!$msgnum, the hard way + $mail_sender = $conf->config('invoice_from', $agentnum); + $failure_recipient = $conf->config('invoice_from', $agentnum) + || 'postmaster'; + + my @alerter_template = $conf->config('alerter_template', $agentnum) + or die 'cannot load config file alerter_template'; + + my $alerter = new Text::Template(TYPE => 'ARRAY', + SOURCE => [ + map "$_\n", @alerter_template + ]) + or die "can't create Text::Template object: $Text::Template::ERROR"; + + $alerter->compile() + or die "can't compile template: $Text::Template::ERROR"; + my @invoicing_list = $customer->invoicing_list; my @to_addrs = grep { $_ ne 'POST' } @invoicing_list; if(@to_addrs) { @@ -133,26 +141,29 @@ sub alert_expiration { $fill_in{'payby'} = 'current method'; } # Send it already! - my $error = FS::Misc::send_email ( + $error = FS::Misc::send_email ( from => $mail_sender, to => [ @to_addrs ], subject => 'Billing Arrangement Expiration', body => [ $alerter->fill_in( HASH => \%fill_in ) ], ); - die "can't send expiration alert: $error" - if $error; - } - else { # if(@to_addrs) - push @{$agent_failure_body{$customer->agentnum}}, - sprintf(qq{%5d %-32.32s %4s %10s %12s %12s}, - $custnum, - $first . " " . $last . " " . $company, - $payby, - $paydate, - $daytime, - $night ); - } - } # if(@packages) + } + else { # if(@to_addrs) + push @{$agent_failure_body{$customer->agentnum}}, + sprintf(qq{%5d %-32.32s %4s %10s %12s %12s}, + $custnum, + $first . " " . $last . " " . $company, + $payby, + $paydate, + $daytime, + $night ); + } + } # if($msgnum) + +# should we die here rather than report failure as below? + die "can't send expiration alert: $error" + if $error; + } # if(expired) } # foreach(@customers) diff --git a/FS/FS/Cron/notify.pm b/FS/FS/Cron/notify.pm index 5b0e186ad..74840a6e7 100644 --- a/FS/FS/Cron/notify.pm +++ b/FS/FS/Cron/notify.pm @@ -21,6 +21,8 @@ sub notify_flat_delay { #we're at now now (and later). my($time) = $^T; + my $conf = new FS::Conf; + my $error = ''; my $integer = driver_name =~ /^mysql/ ? 'SIGNED' : 'INTEGER'; @@ -101,14 +103,20 @@ END push @cust_pkgs, $cust_pkg[0]; shift @cust_pkg; } - my $error = - $cust_main->notify( 'impending_recur_template', + my $msgnum = $conf->config('impending_recur_msgnum',$cust_main->agentnum); + if ( $msgnum ) { + my $msg_template = qsearchs('msg_template', { msgnum => $msgnum }); + $error = $msg_template->send($cust_main); + } + else { + $error = $cust_main->notify( 'impending_recur_template', 'extra_fields' => { 'packages' => \@packages, 'recurdates' => \@recurdates, 'package' => $packages[0], 'recurdate' => $recurdates[0], }, ); + } #if $msgnum warn "Error notifying, custnum ". $cust_main->custnum. ": $error" if $error; unless ($error) { diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 1aefbd044..60d2bcef5 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2939,6 +2939,7 @@ sub tables_hashref { 'mime_type', 'varchar', '', $char_d, '', '', 'body', 'blob', 'NULL', '', '', '', 'disabled', 'char', 'NULL', 1, '', '', + 'from_addr', 'varchar', 'NULL', 255, '', '', ], 'primary_key' => 'msgnum', 'unique' => [ ['msgname', 'mime_type'] ], diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm index ce8932262..b7a1c661a 100644 --- a/FS/FS/Upgrade.pm +++ b/FS/FS/Upgrade.pm @@ -157,6 +157,9 @@ sub upgrade_data { #default namespace 'payment_gateway' => [], + #migrate to templates + 'msg_template' => [], + ; \%hash; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 6d32e00fc..002b0c1d1 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -5111,28 +5111,39 @@ sub _realtime_bop_result { && ! grep { $transaction->error_message =~ /$_/ } $conf->config('emaildecline-exclude') ) { - my @templ = $conf->config('declinetemplate'); - my $template = new Text::Template ( - TYPE => 'ARRAY', - SOURCE => [ map "$_\n", @templ ], - ) or return "($perror) can't create template: $Text::Template::ERROR"; - $template->compile() - or return "($perror) can't compile template: $Text::Template::ERROR"; - - my $templ_hash = { - 'company_name' => - scalar( $conf->config('company_name', $self->agentnum ) ), - 'company_address' => - join("\n", $conf->config('company_address', $self->agentnum ) ), - 'error' => $transaction->error_message, - }; - my $error = send_email( - 'from' => $conf->config('invoice_from', $self->agentnum ), - 'to' => [ grep { $_ ne 'POST' } $self->invoicing_list ], - 'subject' => 'Your payment could not be processed', - 'body' => [ $template->fill_in(HASH => $templ_hash) ], - ); + # Send a decline alert to the customer. + my $msgnum = $conf->config('decline_msgnum', $self->agentnum); + my $error = ''; + if ( $msgnum ) { + my $msg_template = qsearchs('msg_template', { msgnum => $msgnum }); + $error = $msg_template->send( 'cust_main' => $self ); + } + else { #!$msgnum + + my @templ = $conf->config('declinetemplate'); + my $template = new Text::Template ( + TYPE => 'ARRAY', + SOURCE => [ map "$_\n", @templ ], + ) or return "($perror) can't create template: $Text::Template::ERROR"; + $template->compile() + or return "($perror) can't compile template: $Text::Template::ERROR"; + + my $templ_hash = { + 'company_name' => + scalar( $conf->config('company_name', $self->agentnum ) ), + 'company_address' => + join("\n", $conf->config('company_address', $self->agentnum ) ), + 'error' => $transaction->error_message, + }; + + my $error = send_email( + 'from' => $conf->config('invoice_from', $self->agentnum ), + 'to' => [ grep { $_ ne 'POST' } $self->invoicing_list ], + 'subject' => 'Your payment could not be processed', + 'body' => [ $template->fill_in(HASH => $templ_hash) ], + ); + } $perror .= " (also received error sending decline notification: $error)" if $error; @@ -8759,6 +8770,9 @@ sub batch_charge { =item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS +Deprecated. Use event notification and message templates +(L) instead. + Sends a templated email notification to the customer (see L). OPTIONS is a hash and may include diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index fea693e82..0f9a611eb 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -708,12 +708,21 @@ sub cancel { my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $self->cust_main->invoicing_list; if ( !$options{'quiet'} && $conf->exists('emailcancel') && @invoicing_list ) { - my $error = send_email( - 'from' => $conf->config('invoice_from', $self->cust_main->agentnum), - 'to' => \@invoicing_list, - 'subject' => ( $conf->config('cancelsubject') || 'Cancellation Notice' ), - 'body' => [ map "$_\n", $conf->config('cancelmessage') ], - ); + my $msgnum = $conf->config('cancel_msgnum', $self->cust_main->agentnum); + my $error = ''; + if ( $msgnum ) { + my $msg_template = qsearchs('msg_template', { msgnum => $msgnum }); + $error = $msg_template->send( 'cust_main' => $self->cust_main, + 'object' => $self ); + } + else { + $error = send_email( + 'from' => $conf->config('invoice_from', $self->cust_main->agentnum), + 'to' => \@invoicing_list, + 'subject' => ( $conf->config('cancelsubject') || 'Cancellation Notice' ), + 'body' => [ map "$_\n", $conf->config('cancelmessage') ], + ); + } #should this do something on errors? } @@ -1930,7 +1939,7 @@ sub _labels_short { my %labels; #tie %labels, 'Tie::IxHash'; push @{ $labels{$_->[0]} }, $_->[1] - foreach $self->h_labels(@_); + foreach $self->$method(@_); my @labels; foreach my $label ( keys %labels ) { my %seen = (); diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm index de804b6cc..958acef1b 100644 --- a/FS/FS/msg_template.pm +++ b/FS/FS/msg_template.pm @@ -7,6 +7,12 @@ use FS::Misc qw( generate_email send_email ); use FS::Conf; use FS::Record qw( qsearch qsearchs ); +use Date::Format qw( time2str ); +use HTML::Entities qw( encode_entities) ; +use vars '$DEBUG'; + +$DEBUG=1; + =head1 NAME FS::msg_template - Object methods for msg_template records @@ -40,25 +46,32 @@ primary key =item msgname -msgname +Template name. =item agentnum -agentnum +Agent associated with this template. Can be NULL for a global template. =item mime_type -mime_type +MIME type. Defaults to text/html. + +=item from_addr + +Source email address. + +=item subject + +The message subject line, in L format. =item body -body +The message body, as plain text or HTML, in L format. =item disabled disabled - =back =head1 METHODS @@ -126,17 +139,23 @@ sub check { || $self->ut_anything('subject') || $self->ut_anything('body') || $self->ut_enum('disabled', [ '', 'Y' ] ) + || $self->ut_textn('from_addr') ; return $error if $error; + my $body = $self->body; + $body =~ s/ / /g; # just in case these somehow get in + $self->body($body); + $self->mime_type('text/html') unless $self->mime_type; $self->SUPER::check; } -=item send OPTION => VALUE, ... +=item prepare OPTION => VALUE -Fills in the template and emails it to the customer. +Fills in the template and returns a hash of the 'from' address, 'to' +addresses, subject line, and body. Options are passed as a list of name/value pairs: @@ -155,24 +174,42 @@ object, or cust_bill object). =cut -sub send { +sub prepare { my( $self, %opt ) = @_; my $cust_main = $opt{'cust_main'}; my $object = $opt{'object'}; + warn "preparing template '".$self->msgname."' to cust#".$cust_main->custnum."\n" + if($DEBUG); + + my $subs = $self->substitutions; + + ### + # create substitution table + ### + my %hash; + foreach my $obj ($cust_main, $object || ()) { + foreach my $name (@{ $subs->{$obj->table} }) { + if(!ref($name)) { + # simple case + $hash{$name} = $obj->$name(); + } + elsif( ref($name) eq 'ARRAY' ) { + # [ foo => sub { ... } ] + $hash{$name->[0]} = $name->[1]->($obj); + } + else { + warn "bad msg_template substitution: '$name'\n"; + #skip it? + } + } + } + $_ = encode_entities($_) foreach values(%hash); # HTML escape ### # fill-in ### - my $subs = $self->substitutions; - - #XXX html escape this stuff - my %hash = map { $_ => $cust_main->$_() } @{ $subs->{'cust_main'} }; - unless ( ! $object || $object->table eq 'cust_main' ) { - %hash = ( %hash, map { $_ => $object->$_() } @{ $subs->{$object->table} } ); - } - my $subject_tmpl = new Text::Template ( TYPE => 'STRING', SOURCE => $self->subject, @@ -194,21 +231,36 @@ sub send { my $conf = new FS::Conf; - send_email( - generate_email( - #XXX override from in event? - 'from' => scalar( $conf->config('invoice_from', $cust_main->agentnum) ), - 'to' => \@to, - 'subject' => $subject, - 'html_body' => $body, - #XXX auto-make a text copy w/HTML::FormatText? - # alas, us luddite mutt/pine users just aren't that big a deal - ) + ( + 'from' => $self->from || + scalar( $conf->config('invoice_from', $cust_main->agentnum) ), + 'to' => \@to, + 'subject' => $subject, + 'html_body' => $body, + #XXX auto-make a text copy w/HTML::FormatText? + # alas, us luddite mutt/pine users just aren't that big a deal ); } +=item send OPTION => VALUE + +Fills in the template and sends it to the customer. Options are as for +'prepare'. + +=cut + +sub send { + my $self = shift; + send_email(generate_email($self->prepare(@_))); +} + +# helper sub for package dates +my $ymd = sub { $_[0] ? time2str('%Y-%m-%d', $_[0]) : '' }; + #return contexts and fill-in values +# If you add anything, be sure to add a description in +# httemplate/edit/msg_template.html. sub substitutions { { 'cust_main' => [qw( display_custnum agentnum agent_name @@ -232,22 +284,86 @@ sub substitutions { balance invoicing_list_emailonly cust_status ucfirst_cust_status cust_statuscolor - )], - #XXX make these pretty: signupdate dundate paydate_monthyear usernum - # next_bill_date - - 'cust_pkg' => [qw( - )], - #XXX these are going to take more pretty-ing up + signupdate dundate + ), + [ signupdate_ymd => sub { time2str('%Y-%m-%d', shift->signupdate) } ], + [ dundate_ymd => sub { time2str('%Y-%m-%d', shift->dundate) } ], + [ paydate_my => sub { sprintf('%02d/%04d', shift->paydate_monthyear) } ], + [ otaker_first => sub { shift->access_user->first } ], + [ otaker_last => sub { shift->access_user->last } ], + ], + # next_bill_date + 'cust_pkg' => [qw( + pkgnum pkg_label pkg_label_long + location_label + status statuscolor + + start_date setup bill last_bill + adjourn susp expire + labels_short + ), + [ cancel => sub { shift->getfield('cancel') } ], # grrr... + [ start_ymd => sub { $ymd->(shift->getfield('start_date')) } ], + [ setup_ymd => sub { $ymd->(shift->getfield('setup')) } ], + [ next_bill_ymd => sub { $ymd->(shift->getfield('bill')) } ], + [ last_bill_ymd => sub { $ymd->(shift->getfield('last_bill')) } ], + [ adjourn_ymd => sub { $ymd->(shift->getfield('adjourn')) } ], + [ susp_ymd => sub { $ymd->(shift->getfield('susp')) } ], + [ expire_ymd => sub { $ymd->(shift->getfield('expire')) } ], + [ cancel_ymd => sub { $ymd->(shift->getfield('cancel')) } ], + ], 'cust_bill' => [qw( invnum )], #XXX not really thinking about cust_bill substitutions quite yet - + + 'svc_acct' => [qw( + username + ), + [ password => sub { shift->getfield('_password') } ], + ], # for welcome messages }; } +sub _upgrade_data { + my ($self, %opts) = @_; + + my @fixes = ( + [ 'alerter_msgnum', 'alerter_template', '', '' ], + [ 'cancel_msgnum', 'cancelmessage', 'cancelsubject', '' ], + [ 'decline_msgnum', 'declinetemplate', '', '' ], + [ 'impending_recur_msgnum', 'impending_recur_template', '', '' ], + [ 'welcome_msgnum', 'welcome_email', 'welcome_email-subject', 'welcome_email-from' ], + [ 'warning_msgnum', 'warning_email', 'warning_email-subject', 'warning_email-from' ], + ); + + my $conf = new FS::Conf; + my @agentnums = ('', map {$_->agentnum} qsearch('agent', {})); + foreach my $agentnum (@agentnums) { + foreach (@fixes) { + my ($newname, $oldname, $subject, $from) = @$_; + if ($conf->exists($oldname, $agentnum)) { + my $new = new FS::msg_template({ + 'msgname' => $oldname, + 'agentnum' => $agentnum, + 'from_addr' => ($from && $conf->config($from, $agentnum)) || + $conf->config('invoice_from', $agentnum), + 'subject' => ($subject && $conf->config($subject, $agentnum)) || '', + 'mime_type' => 'text/html', + 'body' => join('
    ',$conf->config($oldname, $agentnum)), + }); + my $error = $new->insert; + die $error if $error; + $conf->set($newname, $new->msgnum, $agentnum); + $conf->delete($oldname, $agentnum); + $conf->delete($from, $agentnum) if $from; + $conf->delete($subject, $agentnum) if $subject; + } + } + } +} + =back =head1 BUGS diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 0458bb998..3b26688bf 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -721,82 +721,89 @@ sub insert { } #welcome email - my ($to,$welcome_template,$welcome_from,$welcome_subject,$welcome_subject_template,$welcome_mimetype) - = ('','','','','',''); - - if ( $conf->exists('welcome_email', $agentnum) ) { - $welcome_template = new Text::Template ( - TYPE => 'ARRAY', - SOURCE => [ map "$_\n", $conf->config('welcome_email', $agentnum) ] - ) or warn "can't create welcome email template: $Text::Template::ERROR"; - $welcome_from = $conf->config('welcome_email-from', $agentnum); - # || 'your-isp-is-dum' - $welcome_subject = $conf->config('welcome_email-subject', $agentnum) - || 'Welcome'; - $welcome_subject_template = new Text::Template ( - TYPE => 'STRING', - SOURCE => $welcome_subject, - ) or warn "can't create welcome email subject template: $Text::Template::ERROR"; - $welcome_mimetype = $conf->config('welcome_email-mimetype', $agentnum) - || 'text/plain'; + my $error = ''; + my $msgnum = $conf->config('welcome_msgnum', $agentnum); + if ( $msgnum ) { + my $msg_template = qsearchs('msg_template', { msgnum => $msgnum }); + $error = $msg_template->send('cust_main' => $cust_main); } - if ( $welcome_template && $cust_pkg ) { - my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list ); - if ( $to ) { - - my %hash = ( - 'custnum' => $self->custnum, - 'username' => $self->username, - 'password' => $self->_password, - 'first' => $cust_main->first, - 'last' => $cust_main->getfield('last'), - 'pkg' => $cust_pkg->part_pkg->pkg, - ); - my $wqueue = new FS::queue { - 'svcnum' => $self->svcnum, - 'job' => 'FS::svc_acct::send_email' - }; - my $error = $wqueue->insert( - 'to' => $to, - 'from' => $welcome_from, - 'subject' => $welcome_subject_template->fill_in( HASH => \%hash, ), - 'mimetype' => $welcome_mimetype, - 'body' => $welcome_template->fill_in( HASH => \%hash, ), - ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error queuing welcome email: $error"; - } + else { #!$msgnum + my ($to,$welcome_template,$welcome_from,$welcome_subject,$welcome_subject_template,$welcome_mimetype) + = ('','','','','',''); + + if ( $conf->exists('welcome_email', $agentnum) ) { + $welcome_template = new Text::Template ( + TYPE => 'ARRAY', + SOURCE => [ map "$_\n", $conf->config('welcome_email', $agentnum) ] + ) or warn "can't create welcome email template: $Text::Template::ERROR"; + $welcome_from = $conf->config('welcome_email-from', $agentnum); + # || 'your-isp-is-dum' + $welcome_subject = $conf->config('welcome_email-subject', $agentnum) + || 'Welcome'; + $welcome_subject_template = new Text::Template ( + TYPE => 'STRING', + SOURCE => $welcome_subject, + ) or warn "can't create welcome email subject template: $Text::Template::ERROR"; + $welcome_mimetype = $conf->config('welcome_email-mimetype', $agentnum) + || 'text/plain'; + } + if ( $welcome_template ) { + my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list ); + if ( $to ) { + + my %hash = ( + 'custnum' => $self->custnum, + 'username' => $self->username, + 'password' => $self->_password, + 'first' => $cust_main->first, + 'last' => $cust_main->getfield('last'), + 'pkg' => $cust_pkg->part_pkg->pkg, + ); + my $wqueue = new FS::queue { + 'svcnum' => $self->svcnum, + 'job' => 'FS::svc_acct::send_email' + }; + my $error = $wqueue->insert( + 'to' => $to, + 'from' => $welcome_from, + 'subject' => $welcome_subject_template->fill_in( HASH => \%hash, ), + 'mimetype' => $welcome_mimetype, + 'body' => $welcome_template->fill_in( HASH => \%hash, ), + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error queuing welcome email: $error"; + } - if ( $options{'depend_jobnum'} ) { - warn "$me depend_jobnum found; adding to welcome email dependancies" - if $DEBUG; - if ( ref($options{'depend_jobnum'}) ) { - warn "$me adding jobs ". join(', ', @{$options{'depend_jobnum'}} ). - "to welcome email dependancies" + if ( $options{'depend_jobnum'} ) { + warn "$me depend_jobnum found; adding to welcome email dependancies" if $DEBUG; - push @jobnums, @{ $options{'depend_jobnum'} }; - } else { - warn "$me adding job $options{'depend_jobnum'} ". - "to welcome email dependancies" - if $DEBUG; - push @jobnums, $options{'depend_jobnum'}; + if ( ref($options{'depend_jobnum'}) ) { + warn "$me adding jobs ". join(', ', @{$options{'depend_jobnum'}} ). + "to welcome email dependancies" + if $DEBUG; + push @jobnums, @{ $options{'depend_jobnum'} }; + } else { + warn "$me adding job $options{'depend_jobnum'} ". + "to welcome email dependancies" + if $DEBUG; + push @jobnums, $options{'depend_jobnum'}; + } } - } - foreach my $jobnum ( @jobnums ) { - my $error = $wqueue->depend_insert($jobnum); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error queuing welcome email job dependancy: $error"; + foreach my $jobnum ( @jobnums ) { + my $error = $wqueue->depend_insert($jobnum); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error queuing welcome email job dependancy: $error"; + } } - } - } - - } + } - } # if ( $cust_pkg ) + } # if $welcome_template + } # if !$msgnum + } # if $cust_pkg $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; #no error diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi index 08f6c1020..11e75707c 100644 --- a/httemplate/config/config-view.cgi +++ b/httemplate/config/config-view.cgi @@ -349,7 +349,7 @@ my @config_items = grep { $page_agent ? $_->per_agent : 1 } my @deleteable = qw( invoice_latexreturnaddress invoice_htmlreturnaddress ); my %deleteable = map { $_ => 1 } @deleteable; -my @sections = qw(required billing invoicing UI self-service username password session shell BIND ); +my @sections = qw(required billing invoicing notification UI self-service username password session shell BIND ); push @sections, '', 'deprecated'; my %section_items = (); diff --git a/httemplate/edit/msg_template.html b/httemplate/edit/msg_template.html index 986629cd7..01d866dfb 100644 --- a/httemplate/edit/msg_template.html +++ b/httemplate/edit/msg_template.html @@ -1,16 +1,24 @@ <% include( 'elements/edit.html', + 'html_init' => '$sidebar
    ', 'name_singular' => 'template', 'table' => 'msg_template', 'viewall_dir' => 'browse', + 'agent_virt' => 1, + 'agent_null' => 1, + 'agent_null_right' => 'Edit global templates', + 'fields' => [ 'msgname', 'subject', + 'from_addr', { field=>'body', type=>'htmlarea', width=>763 }, ], - 'labels' => { 'msgnum' => 'Template', - 'msgname' => 'Template name', - 'subject' => 'Message subject', - 'body' => 'Message template', + 'labels' => { 'msgnum' => 'Template', + 'msgname' => 'Template name', + 'from_addr' => 'Return address', + 'subject' => 'Message subject', + 'body' => 'Message template', }, + 'html_foot' => "
    ", ) %> <%init> @@ -20,4 +28,122 @@ die "access denied" || $FS::CurrentUser::CurrentUser->access_right('Edit global templates') || $FS::CurrentUser::CurrentUser->access_right('Configuration'); +# Create hints pane + +my %substitutions = ( + 'cust_main' => [ + '$display_custnum'=> 'Customer#', + '$agentnum' => 'Agent#', + '$agent_name' => 'Agent name', + '$payby' => 'Payment method', + '$paymask' => 'Card/account# (masked)', + '$payname' => 'Name on card/bank name', + '$paytype' => 'Account type', + '$payip' => 'IP address used to submit payment info', + '$num_ncancelled_pkgs' => '# of active packages', + '$num_cancelled_pkgs' => '# of cancelled packages', + '$num_pkgs' => '# of packages', + '$classname' => 'Customer class', + '$categoryname' => 'Customer category', + '$balance' => 'Current balance', + '$invoicing_list_emailonly' => 'Billing email address', + '$cust_status' => 'Status', + '$ucfirst_cust_status' => 'Status, capitalized', + '$cust_statuscolor' => 'Status color code', + ], + 'contact' => [ # duplicate this for shipping + '$name' => 'Company and contact name', + '$name_short' => 'Company or contact name', + '$company' => 'Company name', + '$contact' => 'Contact name (last, first)', + '$contact_firstlast'=> 'Contact name (first last)', + '$first' => 'First name', + '$last' => 'Last name', + '$address1' => 'Address line 1', + '$address2' => 'Address line 2', + '$city' => 'City', + '$county' => 'County', + '$state' => 'State', + '$zip' => 'Zip', + '$country' => 'Country', + '$daytime' => 'Day phone', + '$night' => 'Night phone', + '$fax' => 'Fax', + ], + 'cust_bill' => [ + '$invnum' => 'Invoice#', + ], + 'cust_pkg' => [ + '$pkgnum' => 'Package#', + '$pkg_label' => 'Package label (short)', + '$pkg_label_long' => 'Package label (long)', + '$status' => 'Status', + '$statuscolor' => 'Status color code', + '$start_ymd' => 'Start date', + '$setup_ymd' => 'Setup date', + '$last_bill_ymd' => 'Last bill date', + '$next_bill_ymd' => 'Next bill date', + '$susp_ymd' => 'Suspended on date', + '$cancel_ymd' => 'Canceled on date', + '$adjourn_ymd' => 'Adjournment date', + '$expire_ymd' => 'Expiration date', + '$labels_short' => 'Service labels', + '$location_label' => 'Service location', + ], + 'svc_acct' => [ + '$username' => 'Login name', + '$password' => 'Password', + ], +); +my @c = @{ $substitutions{'contact'} }; +for (my $i=0; $i 'Name and contact info (billing)', +'shipping' => 'Name and contact info (shipping)', +'cust_main' => 'Customer status and payment info', +'cust_pkg' => 'Package fields', +'cust_bill' => 'Invoice fields', +'svc_acct' => 'Login service fields', +); + +my $widget = new HTML::Widgets::SelectLayers( + 'options' => \%sections, + 'form_name' => 'dummy', + 'html_between'=>'', + 'selected_layer'=>(keys(%sections))[0], + 'layer_callback' => sub { + my $section = shift; + my $html = include('/elements/table-grid.html'); + my @hints = @{ $substitutions{$section} }; + while(@hints) { + my $key = shift @hints; + $html .= qq!\n$key!; + $html .= "\n".shift(@hints).''; + } + $html .= "\n"; + return $html; + }, +); + +my $sidebar = ' + +
    +Substitutions: ' +. $widget->html . +'
    Click links to insert. +
    Enclose substitutions and other Perl expressions in braces: +
    { $name } = ExampleCo (Smith, John) +
    { time2str("%D", time) } = '.time2str("%D", time).' +
    +'; + -- 2.11.0