use FS::part_pkg_taxrate;
use FS::agent;
use FS::cust_main_invoice;
+use FS::cust_tag;
use FS::cust_credit_bill;
use FS::cust_bill_pay;
use FS::prepay_credit;
$self->invoicing_list( $invoicing_list );
}
+ warn " setting customer tags\n"
+ if $DEBUG > 1;
+
+ foreach my $tagnum ( @{ $self->tagnum || [] } ) {
+ my $cust_tag = new FS::cust_tag { 'tagnum' => $tagnum,
+ 'custnum' => $self->custnum };
+ my $error = $cust_tag->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ if ( $invoicing_list ) {
+ $error = $self->check_invoicing_list( $invoicing_list );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ #return "checking invoicing_list (transaction rolled back): $error";
+ return $error;
+ }
+ $self->invoicing_list( $invoicing_list );
+ }
+
+
warn " setting cust_main_exemption\n"
if $DEBUG > 1;
}
}
- foreach my $cust_main_invoice ( #(email invoice destinations, not invoices)
- qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } )
- ) {
- my $error = $cust_main_invoice->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- foreach my $cust_main_exemption (
- qsearch( 'cust_main_exemption', { 'custnum' => $self->custnum } )
- ) {
- my $error = $cust_main_exemption->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
+ foreach my $table (qw( cust_main_invoice cust_main_exemption cust_tag )) {
+ foreach my $record ( qsearch( 'table', { 'custnum' => $self->custnum } ) ) {
+ my $error = $record->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
}
}
$self->invoicing_list( $invoicing_list );
}
+ if ( $self->exists('tagnum') ) { #so we don't delete these on edit by accident
+
+ #this could be more efficient than deleting and re-inserting, if it matters
+ foreach my $cust_tag (qsearch('cust_tag', {'custnum'=>$self->custnum} )) {
+ my $error = $cust_tag->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ foreach my $tagnum ( @{ $self->tagnum || [] } ) {
+ my $cust_tag = new FS::cust_tag { 'tagnum' => $tagnum,
+ 'custnum' => $self->custnum };
+ my $error = $cust_tag->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
my %options = @param;
my $tax_exemption = delete $options{'tax_exemption'};
qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
}
+=item agent_name
+
+Returns the agent name (see L<FS::agent>) for this customer.
+
+=cut
+
+sub agent_name {
+ my $self = shift;
+ $self->agent->agent;
+}
+
+=item cust_tag
+
+Returns any tags associated with this customer, as FS::cust_tag objects,
+or an empty list if there are no tags.
+
+=cut
+
+sub cust_tag {
+ my $self = shift;
+ qsearch('cust_tag', { 'custnum' => $self->custnum } );
+}
+
+=item part_tag
+
+Returns any tags associated with this customer, as FS::part_tag objects,
+or an empty list if there are no tags.
+
+=cut
+
+sub part_tag {
+ my $self = shift;
+ map $_->part_tag, $self->cust_tag;
+}
+
+
=item cust_class
Returns the customer class, as an FS::cust_class object, or the empty string
&& ! 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;
=cut
sub ordered_sql {
- " 0 < ( $select_count_pkgs AND ". FS::cust_pkg->ordered_sql. " ) ";
+ FS::cust_main->none_active_sql.
+ " AND 0 < ( $select_count_pkgs AND ". FS::cust_pkg->ordered_sql. " ) ";
}
=item active_sql
" 0 < ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) ";
}
+=item none_active_sql
+
+Returns an SQL expression identifying cust_main records with no active
+recurring packages. This includes customers of status prospect, ordered,
+inactive, and suspended.
+
+=cut
+
+sub none_active_sql {
+ " 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) ";
+}
+
=item inactive_sql
Returns an SQL expression identifying inactive cust_main records (customers with
=cut
-sub inactive_sql { "
- 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " )
- AND
- 0 < ( $select_count_pkgs AND ". FS::cust_pkg->inactive_sql. " )
-"; }
+sub inactive_sql {
+ FS::cust_main->none_active_sql.
+ " AND 0 < ( $select_count_pkgs AND ". FS::cust_pkg->inactive_sql. " ) ";
+}
=item susp_sql
=item suspended_sql
sub suspended_sql { susp_sql(@_); }
-sub susp_sql { "
- 0 < ( $select_count_pkgs AND ". FS::cust_pkg->suspended_sql. " )
- AND
- 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " )
-"; }
+sub susp_sql {
+ FS::cust_main->none_active_sql.
+ " AND 0 < ( $select_count_pkgs AND ". FS::cust_pkg->suspended_sql. " ) ";
+}
=item cancel_sql
=item cancelled_sql
my $subject = delete $params->{subject};
my $html_body = delete $params->{html_body};
my $text_body = delete $params->{text_body};
+ my $error = '';
- my $job = delete $params->{'job'};
+ my $job = delete $params->{'job'}
+ or die "email_search_result must run from the job queue.\n";
$params->{'payby'} = [ split(/\0/, $params->{'payby'}) ]
unless ref($params->{'payby'});
my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
+ my @retry_jobs = ();
+ my $success = 0;
#eventually order+limit magic to reduce memory use?
foreach my $cust_main ( qsearch($sql_query) ) {
+ #progressbar first, so that the count is right
+ $num++;
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $num / $num_cust )
+ );
+ die $error if $error;
+ $last = time;
+ }
+
my $to = $cust_main->invoicing_list_emailonly_scalar;
- next unless $to;
- my $error = send_email(
- generate_email(
+ if( $to ) {
+ my @message = (
'from' => $from,
'to' => $to,
'subject' => $subject,
'html_body' => $html_body,
'text_body' => $text_body,
- )
- );
- return $error if $error;
+ );
- if ( $job ) { #progressbar foo
- $num++;
- if ( time - $min_sec > $last ) {
- my $error = $job->update_statustext(
- int( 100 * $num / $num_cust )
- );
- die $error if $error;
- $last = time;
+ $error = send_email( generate_email( @message ) );
+
+ if($error) {
+ # queue the sending of this message so that the user can see what we
+ # tried to do, and retry if desired
+ my $queue = new FS::queue {
+ 'job' => 'FS::Misc::process_send_email',
+ 'custnum' => $cust_main->custnum,
+ 'status' => 'failed',
+ 'statustext' => $error,
+ };
+ $queue->insert(@message);
+ push @retry_jobs, $queue;
+ }
+ else {
+ $success++;
}
}
+ if($success == 0 and
+ (scalar(@retry_jobs) > 10 or $num == $num_cust)
+ ) {
+ # 10 is arbitrary, but if we have enough failures, that's
+ # probably a configuration or network problem, and we
+ # abort the batch and run away screaming.
+ # We NEVER do this if anything was successfully sent.
+ $_->delete foreach (@retry_jobs);
+ return "multiple failures: '$error'\n";
+ }
+ }
+
+ if(@retry_jobs) {
+ # fail the job, but with a status message that makes it clear
+ # something was sent.
+ return "Sent $success, failed ".scalar(@retry_jobs).". Failed attempts placed in job queue.\n";
}
return '';
=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
$sth->execute or die $sth->errstr;
local($ignore_expired_card) = 1;
+ local($skip_fuzzyfiles) = 1;
$class->_upgrade_otaker(%opts);
}