use vars qw( $DEBUG $conf );
use FS::Conf;
-use FS::Record qw( qsearch qsearchs );
+use FS::Record qw( qsearch qsearchs dbh );
use FS::cust_msg;
use FS::template_content;
+use Date::Format qw(time2str);
+
FS::UID->install_callback( sub { $conf = new FS::Conf; } );
$DEBUG=0;
$error = $record->check;
+=head1 NOTE
+
+This uses a table-per-subclass ORM strategy, which is a somewhat cleaner
+version of what we do elsewhere with _option tables. We could easily extract
+that functionality into a base class, or even into FS::Record itself.
+
=head1 DESCRIPTION
An FS::msg_template object represents a customer message template.
=item bcc_addr - Bcc all mail to this address.
-=item disabled - disabled ('Y' or NULL).
+=item disabled - disabled (NULL for not-disabled and selectable, 'D' for a
+draft of a one-time message, 'C' for a completed one-time message, 'Y' for a
+normal template disabled by user action).
=back
sub table { 'msg_template'; }
+sub extension_table { ''; } # subclasses don't HAVE to have extensions
+
sub _rebless {
my $self = shift;
my $class = 'FS::msg_template::' . $self->msgclass;
eval "use $class;";
bless($self, $class) unless $@;
+ warn "Error loading msg_template msgclass: " . $@ if $@; #or die?
+
+ # merge in the extension fields (but let fields in $self override them)
+ # except don't ever override the extension's primary key, it's immutable
+ if ( $self->msgnum and $self->extension_table ) {
+ my $extension = $self->_extension;
+ if ( $extension ) {
+ my $ext_key = $extension->get($extension->primary_key);
+ $self->{Hash} = { $extension->hash,
+ $self->hash,
+ $extension->primary_key => $ext_key
+ };
+ }
+ }
+
$self;
}
+# Returns the subclass-specific extension record for this object. For internal
+# use only; everyone else is supposed to think of this as a single record.
+
+sub _extension {
+ my $self = shift;
+ if ( $self->extension_table and $self->msgnum ) {
+ local $FS::Record::nowarn_classload = 1;
+ return qsearchs($self->extension_table, { msgnum => $self->msgnum });
+ }
+ return;
+}
+
=item insert [ CONTENT ]
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
-# inherited
+=cut
+
+sub insert {
+ my $self = shift;
+ $self->_rebless;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+
+ my $error = $self->SUPER::insert;
+ # calling _extension at this point makes it copy the msgnum, so links work
+ if ( $self->extension_table ) {
+ local $FS::Record::nowarn_classload = 1;
+ my $extension = FS::Record->new($self->extension_table, { $self->hash });
+ $error ||= $extension->insert;
+ }
+
+ if ( $error ) {
+ dbh->rollback if $oldAutoCommit;
+ } else {
+ dbh->commit if $oldAutoCommit;
+ }
+ $error;
+}
=item delete
=cut
-# inherited
+sub delete {
+ my $self = shift;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+
+ my $error;
+ my $extension = $self->_extension;
+ if ( $extension ) {
+ $error = $extension->delete;
+ }
+
+ $error ||= $self->SUPER::delete;
+
+ if ( $error ) {
+ dbh->rollback if $oldAutoCommit;
+ } else {
+ dbh->commit if $oldAutoCommit;
+ }
+ $error;
+}
-=item replace [ OLD_RECORD ] [ CONTENT ]
+=item replace [ OLD_RECORD ]
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
=cut
-# inherited
+sub replace {
+ my $new = shift;
+ my $old = shift || $new->replace_old;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+
+ my $error = $new->SUPER::replace($old, @_);
+
+ my $extension = $new->_extension;
+ if ( $extension ) {
+ # merge changes into the extension record and replace it
+ $extension->{Hash} = { $extension->hash, $new->hash };
+ $error ||= $extension->replace;
+ }
+
+ if ( $error ) {
+ dbh->rollback if $oldAutoCommit;
+ } else {
+ dbh->commit if $oldAutoCommit;
+ }
+ $error;
+}
sub replace_check {
my $self = shift;
if ( $old->msgclass ) {
if ( !$self->msgclass ) {
$self->set('msgclass', $old->msgclass);
- } else {
+ } elsif ( $old->msgclass ne $self->msgclass ) {
return "Can't change message template class from ".$old->msgclass.
" to ".$self->msgclass.".";
}
|| $self->ut_text('msgname')
|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
|| $self->ut_textn('mime_type')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
+ || $self->ut_enum('disabled', [ '', 'Y', 'D', 'S' ] )
|| $self->ut_textn('from_addr')
|| $self->ut_textn('bcc_addr')
# fine for now, but change this to some kind of dynamic check if we
=item cust_main
-Customer object (required).
+Customer object
=item object
my( $self, %opt ) = @_;
my $cust_main = $opt{'cust_main'}; # or die 'cust_main required';
- my $object = $opt{'object'} or die 'object required';
+ my $object = $opt{'object'}; # or die 'object required';
warn "preparing substitutions for '".$self->msgname."'\n"
if $DEBUG;
# If you add anything, be sure to add a description in
# httemplate/edit/msg_template.html.
sub substitutions {
+ my $payinfo_sub = sub {
+ my $obj = shift;
+ ($obj->payby eq 'CARD' || $obj->payby eq 'CHEK')
+ ? $obj->paymask
+ : $obj->decrypt($obj->payinfo)
+ };
+ my $payinfo_end = sub {
+ my $obj = shift;
+ my $payinfo = &$payinfo_sub($obj);
+ substr($payinfo, -4);
+ };
{ 'cust_main' => [qw(
display_custnum agentnum agent_name
# overrides the one in cust_main in cases where a cust_pay is passed
[ payby => sub { FS::payby->shortname(shift->payby) } ],
[ date => sub { time2str("%a %B %o, %Y", shift->_date) } ],
- [ payinfo => sub {
- my $cust_pay = shift;
- ($cust_pay->payby eq 'CARD' || $cust_pay->payby eq 'CHEK') ?
- $cust_pay->paymask : $cust_pay->decrypt($cust_pay->payinfo)
- } ],
+ [ 'payinfo' => $payinfo_sub ],
+ [ 'payinfo_end' => $payinfo_end ],
+ ],
+ # for refund receipts
+ 'cust_refund' => [
+ 'refundnum',
+ [ refund => sub { sprintf("%.2f", shift->refund) } ],
+ [ payby => sub { FS::payby->shortname(shift->payby) } ],
+ [ date => sub { time2str("%a %B %o, %Y", shift->_date) } ],
+ [ 'payinfo' => $payinfo_sub ],
+ [ 'payinfo_end' => $payinfo_end ],
],
# for payment decline messages
# try to support all cust_pay fields
[ paid => sub { sprintf("%.2f", shift->paid) } ],
[ payby => sub { FS::payby->shortname(shift->payby) } ],
[ date => sub { time2str("%a %B %o, %Y", shift->_date) } ],
- [ payinfo => sub {
- my $pending = shift;
- ($pending->payby eq 'CARD' || $pending->payby eq 'CHEK') ?
- $pending->paymask : $pending->decrypt($pending->payinfo)
- } ],
+ [ 'payinfo' => $payinfo_sub ],
+ [ 'payinfo_end' => $payinfo_end ],
],
};
}
[ 'decline_msgnum', 'declinetemplate', '', '', '' ],
[ 'impending_recur_msgnum', 'impending_recur_template', '', '', 'impending_recur_bcc' ],
[ 'payment_receipt_msgnum', 'payment_receipt_email', '', '', '' ],
- [ 'welcome_msgnum', 'welcome_email', 'welcome_email-subject', 'welcome_email-from', '' ],
- [ 'warning_msgnum', 'warning_email', 'warning_email-subject', 'warning_email-from', '' ],
+ [ 'welcome_msgnum', 'welcome_email', 'welcome_email-subject', 'welcome_email-from', '', 'welcome_email-mimetype' ],
+ [ 'threshold_warning_msgnum', 'warning_email', 'warning_email-subject', 'warning_email-from', 'warning_email-cc', 'warning_email-mimetype' ],
);
my @agentnums = ('', map {$_->agentnum} qsearch('agent', {}));
foreach my $agentnum (@agentnums) {
foreach (@fixes) {
- my ($newname, $oldname, $subject, $from, $bcc) = @$_;
+ my ($newname, $oldname, $subject, $from, $bcc, $mimetype) = @$_;
+
if ($conf->exists($oldname, $agentnum)) {
my $new = new FS::msg_template({
+ 'msgclass' => 'email',
'msgname' => $oldname,
'agentnum' => $agentnum,
'from_addr' => ($from && $conf->config($from, $agentnum)) || '',
- 'bcc_addr' => ($bcc && $conf->config($from, $agentnum)) || '',
+ 'bcc_addr' => ($bcc && $conf->config($bcc, $agentnum)) || '',
'subject' => ($subject && $conf->config($subject, $agentnum)) || '',
'mime_type' => 'text/html',
'body' => join('<BR>',$conf->config($oldname, $agentnum)),
$conf->delete($oldname, $agentnum);
$conf->delete($from, $agentnum) if $from;
$conf->delete($subject, $agentnum) if $subject;
+ $conf->delete($bcc, $agentnum) if $bcc;
+ $conf->delete($mimetype, $agentnum) if $mimetype;
}
}
###
$self->_populate_initial_data;
+ ### Fix dump-email_to (needs to happen after _populate_initial_data)
+ if ($conf->config('dump-email_to')) {
+ # anyone who still uses dump-email_to should have just had this created
+ my ($msg_template) = qsearch('msg_template',{ msgname => 'System log' });
+ if ($msg_template) {
+ eval "use FS::log_email;";
+ die $@ if $@;
+ my $log_email = new FS::log_email {
+ 'context' => 'Cron::backup',
+ 'min_level' => 1,
+ 'msgnum' => $msg_template->msgnum,
+ 'to_addr' => $conf->config('dump-email_to'),
+ };
+ my $error = $log_email->insert;
+ die $error if $error;
+ $conf->delete('dump-email_to');
+ }
+ }
+
}
sub _populate_initial_data { #class method
eval "use FS::msg_template::InitialData;";
die $@ if $@;
+ eval "use FS::upgrade_journal;";
+ die $@ if $@;
my $initial_data = FS::msg_template::InitialData->_initial_data;
foreach my $hash ( @$initial_data ) {
next if $hash->{_conf} && $conf->config( $hash->{_conf} );
+ next if $hash->{_upgrade_journal} && FS::upgrade_journal->is_done( $hash->{_upgrade_journal} );
my $msg_template = new FS::msg_template($hash);
my $error = $msg_template->insert( @{ $hash->{_insert_args} || [] } );
die $error if $error;
$conf->set( $hash->{_conf}, $msg_template->msgnum ) if $hash->{_conf};
+ FS::upgrade_journal->set_done( $hash->{_upgrade_journal} ) if $hash->{_upgrade_journal};
}