}
foreach my $table (qw( cust_main_invoice cust_main_exemption cust_tag )) {
- foreach my $record ( qsearch( 'table', { 'custnum' => $self->custnum } ) ) {
+ foreach my $record ( qsearch( $table, { 'custnum' => $self->custnum } ) ) {
my $error = $record->delete;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
my $real_pkgpart = $cust_pkg->pkgpart;
my %hash = $cust_pkg->hash;
- foreach my $part_pkg ( $cust_pkg->part_pkg->self_and_bill_linked ) {
+ # we could implement this bit as FS::part_pkg::has_hidden, but we already
+ # suffer from performance issues
+ $options{has_hidden} = 0;
+ my @part_pkg = $cust_pkg->part_pkg->self_and_bill_linked;
+ $options{has_hidden} = 1 if ($part_pkg[1] && $part_pkg[1]->hidden);
+
+ foreach my $part_pkg ( @part_pkg ) {
$cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill );
} #foreach my $cust_pkg
+ @cust_bill_pkg = _omit_zero_value_bundles(@cust_bill_pkg);
+
unless ( @cust_bill_pkg ) { #don't create an invoice w/o line items
#but do commit any package date cycling that happened
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
} elsif ( $postal_pkg ) {
my $real_pkgpart = $postal_pkg->pkgpart;
- foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) {
+ # we could implement this bit as FS::part_pkg::has_hidden, but we alre
+ady
+ # suffer from performance issues
+ $options{has_hidden} = 0;
+ my @part_pkg = $postal_pkg->part_pkg->self_and_bill_linked;
+ $options{has_hidden} = 1 if ($part_pkg[1] && $part_pkg[1]->hidden);
+
+ foreach my $part_pkg ( @part_pkg ) {
my %postal_options = %options;
delete $postal_options{cancel};
my $error =
}
}
+ # it's silly to have a zero value postal_pkg, but....
+ @cust_bill_pkg = _omit_zero_value_bundles(@cust_bill_pkg);
+
}
}
''; #no error
}
+#discard bundled packages of 0 value
+sub _omit_zero_value_bundles {
+
+ my @cust_bill_pkg = ();
+ my @cust_bill_pkg_bundle = ();
+ my $sum = 0;
+
+ foreach my $cust_bill_pkg ( @_ ) {
+ if (scalar(@cust_bill_pkg_bundle) && !$cust_bill_pkg->pkgpart_override) {
+ push @cust_bill_pkg, @cust_bill_pkg_bundle if $sum > 0;
+ @cust_bill_pkg_bundle = ();
+ $sum = 0;
+ }
+ $sum += $cust_bill_pkg->setup + $cust_bill_pkg->recur;
+ push @cust_bill_pkg_bundle, $cust_bill_pkg;
+ }
+ push @cust_bill_pkg, @cust_bill_pkg_bundle if $sum > 0;
+
+ (@cust_bill_pkg);
+
+}
sub _make_lines {
my ($self, %params) = @_;
# If $cust_pkg has been modified, update it (if we're a real pkgpart)
###
- if ( $lineitems ) {
+ if ( $lineitems || $options{has_hidden} ) {
if ( $cust_pkg->modified && $cust_pkg->pkgpart == $real_pkgpart ) {
# hmm.. and if just the options are modified in some weird price plan?
return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
}
- if ( $setup != 0 || $recur != 0 ) {
+ if ( $setup != 0 ||
+ $recur != 0 ||
+ !$part_pkg->hidden && $options{has_hidden} ) #include some $0 lines
+ {
warn " charges (setup=$setup, recur=$recur); adding line items\n"
if $DEBUG > 1;
my @display = ();
my $separate = $conf->exists('separate_usage');
- my $usage_mandate = $cust_pkg->part_pkg->option('usage_mandate', 'Hush!');
- if ( $separate || $cust_bill_pkg->hidden || $usage_mandate ) {
+ my $temp_pkg = new FS::cust_pkg { pkgpart => $real_pkgpart };
+ my $usage_mandate = $temp_pkg->part_pkg->option('usage_mandate', 'Hush!');
+ my $section = $temp_pkg->part_pkg->categoryname;
+ if ( $separate || $section || $usage_mandate ) {
- my $temp_pkg = new FS::cust_pkg { pkgpart => $real_pkgpart };
- my %hash = $cust_bill_pkg->hidden # maybe for all bill linked?
- ? ( 'section' => $temp_pkg->part_pkg->categoryname )
- : ();
+ my %hash = ( 'section' => $section );
- my $section = $cust_pkg->part_pkg->option('usage_section', 'Hush!');
- my $summary = $cust_pkg->part_pkg->option('summarize_usage', 'Hush!');
+ $section = $temp_pkg->part_pkg->option('usage_section', 'Hush!');
+ my $summary = $temp_pkg->part_pkg->option('summarize_usage', 'Hush!');
if ( $separate ) {
push @display, new FS::cust_bill_pkg_display { type => 'S', %hash };
push @display, new FS::cust_bill_pkg_display { type => 'R', %hash };
$hash{post_total} = 'Y';
}
- $hash{section} = $section if ($separate || $usage_mandate);
- push @display, new FS::cust_bill_pkg_display { type => 'U', %hash };
+ if ($separate || $usage_mandate) {
+ $hash{section} = $section if ($separate || $usage_mandate);
+ push @display, new FS::cust_bill_pkg_display { type => 'U', %hash };
+ }
}
$cust_bill_pkg->set('display', \@display);
&& ! 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,
- };
+ # Send a decline alert to the customer.
+ my $msgnum = $conf->config('decline_msgnum', $self->agentnum);
+ my $error = '';
+ if ( $msgnum ) {
+ # include the raw error message in the transaction state
+ $cust_pay_pending->setfield('error', $transaction->error_message);
+ my $msg_template = qsearchs('msg_template', { msgnum => $msgnum });
+ $error = $msg_template->send( 'cust_main' => $self,
+ 'object' => $cust_pay_pending );
+ }
+ 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) ],
- );
+ 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;
#false laziness w/misc/process/payment.cgi - check both to make sure working
# correctly
- if ( defined $self->dbdef_table->column('paycvv')
- && length($self->paycvv)
+ if ( length($self->paycvv)
&& ! grep { $_ eq cardtype($options{payinfo}) } $conf->config('cvv-save')
) {
my $error = $self->remove_cvv;
my $self = shift;
my $sql = 'SELECT SUM('. $self->balance_date_sql(@_).
') FROM cust_main WHERE custnum='. $self->custnum;
- sprintf( "%.2f", $self->scalar_sql($sql) );
+ sprintf( '%.2f', $self->scalar_sql($sql) );
}
=item balance_pkgnum PKGNUM
sub uncancelled_sql { uncancel_sql(@_); }
sub uncancel_sql { "
+
( 0 < ( $select_count_pkgs
AND ( cust_pkg.cancel IS NULL
OR cust_pkg.cancel = 0
=item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
+Deprecated. Use event notification and message templates
+(L<FS::msg_template>) instead.
+
Sends a templated email notification to the customer (see L<Text::Template>).
OPTIONS is a hash and may include
I<to> - comma-separated scalar or arrayref of recipients
(default is invoicing_list)
+I<bcc> - blind-copy recipient address (default is none)
+
I<subject> - The subject line of the sent email notification
(default is "Notice from company_name")
send_email(from => $from,
to => $to,
+ bcc => $options{bcc},
subject => $subject,
body => $notify_template->fill_in( PACKAGE =>
'FS::notify_template::_template' ),
=cut
+# a lot like cust_bill::print_latex
sub generate_letter {
my ($self, $template, %options) = @_;
$letter_data{returnaddress} = $retadd;
} elsif ( grep /\S/, $conf->config('company_address', $self->agentnum) ) {
$letter_data{returnaddress} =
- join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg,
- $conf->config('company_address', $self->agentnum)
+ join( "\n", map { s/( {2,})/'~' x length($1)/eg;
+ s/$/\\\\\*/;
+ $_;
+ }
+ ( $conf->config('company_name', $self->agentnum),
+ $conf->config('company_address', $self->agentnum),
+ )
);
} else {
$letter_data{returnaddress} = '~';
$letter_data{company_name} = $conf->config('company_name', $self->agentnum);
my $dir = $FS::UID::conf_dir."/cache.". $FS::UID::datasrc;
+
+ my $lh = new File::Temp( TEMPLATE => 'letter.'. $self->custnum. '.XXXXXXXX',
+ DIR => $dir,
+ SUFFIX => '.eps',
+ UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+ print $lh $conf->config_binary('logo.eps', $self->agentnum)
+ or die "can't write temp file: $!\n";
+ close $lh;
+ $letter_data{'logo_file'} = $lh->filename;
+
my $fh = new File::Temp( TEMPLATE => 'letter.'. $self->custnum. '.XXXXXXXX',
DIR => $dir,
SUFFIX => '.tex',
$letter_template->fill_in( OUTPUT => $fh, HASH => \%letter_data );
close $fh;
$fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
- return $1;
+ return ($1, $letter_data{'logo_file'});
+
}
=item print_ps TEMPLATE
sub print_ps {
my $self = shift;
- my $file = $self->generate_letter(@_);
- FS::Misc::generate_ps($file);
+ my($file, $lfile) = $self->generate_letter(@_);
+ my $ps = FS::Misc::generate_ps($file);
+ unlink($file.'.tex');
+ unlink($lfile);
+
+ $ps;
}
=item print TEMPLATE