X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=3bfccfc668a2003d75fda53701bb9e505bf84260;hb=ff8aa9f859273dd5fbbed2ee80ebc63caa27ea32;hp=d8f525e58a933758b8714352592682fd0a391f5f;hpb=5164aa211f893adf641a3b78293d7b0585eb0af0;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index d8f525e58..3bfccfc66 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -57,6 +57,7 @@ use FS::cust_tax_location; 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; @@ -473,6 +474,30 @@ sub insert { $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; @@ -1356,23 +1381,13 @@ sub delete { } } - 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; + } } } @@ -1478,6 +1493,28 @@ sub replace { $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'}; @@ -2475,6 +2512,31 @@ sub agent_name { $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 @@ -5049,28 +5111,42 @@ 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 ) { + # 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) ], + ); + } $perror .= " (also received error sending decline notification: $error)" if $error; @@ -6199,7 +6275,7 @@ sub balance_date_range { 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 @@ -7024,6 +7100,26 @@ sub cust_pay_pending { ); } +=item cust_pay_pending_attempt + +Returns all payment attempts / declined payments for this customer, as pending +payments objects (see L), with status "done" but without +a corresponding payment (see L). + +=cut + +sub cust_pay_pending_attempt { + my $self = shift; + return $self->num_cust_pay_pending_attempt unless wantarray; + sort { $a->_date <=> $b->_date } + qsearch( 'cust_pay_pending', { + 'custnum' => $self->custnum, + 'status' => 'done', + 'paynum' => '', + }, + ); +} + =item num_cust_pay_pending Returns the number of pending payments (see L) for this @@ -7034,11 +7130,28 @@ cust_pay_pending method is used in a scalar context. sub num_cust_pay_pending { my $self = shift; - my $sql = " SELECT COUNT(*) FROM cust_pay_pending ". - " WHERE custnum = ? AND status != 'done' "; - my $sth = dbh->prepare($sql) or die dbh->errstr; - $sth->execute($self->custnum) or die $sth->errstr; - $sth->fetchrow_arrayref->[0]; + $self->scalar_sql( + " SELECT COUNT(*) FROM cust_pay_pending ". + " WHERE custnum = ? AND status != 'done' ", + $self->custnum + ); +} + +=item num_cust_pay_pending_attempt + +Returns the number of pending payments (see L) for this +customer, with status "done" but without a corresp. Also called automatically when the +cust_pay_pending method is used in a scalar context. + +=cut + +sub num_cust_pay_pending_attempt { + my $self = shift; + $self->scalar_sql( + " SELECT COUNT(*) FROM cust_pay_pending ". + " WHERE custnum = ? AND status = 'done' AND paynum IS NULL", + $self->custnum + ); } =item cust_refund @@ -7415,7 +7528,8 @@ recurring packages not yet setup). =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 @@ -7429,6 +7543,18 @@ sub 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 @@ -7436,11 +7562,10 @@ no active recurring packages, but otherwise unsuspended/uncancelled). =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 @@ -7451,11 +7576,10 @@ Returns an SQL expression identifying suspended cust_main records. 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 @@ -8697,6 +8821,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 @@ -8810,6 +8937,7 @@ I<$returnaddress> - the return address defaults to invoice_latexreturnaddress or =cut +# a lot like cust_bill::print_latex sub generate_letter { my ($self, $template, %options) = @_; @@ -8860,8 +8988,13 @@ sub generate_letter { $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} = '~'; @@ -8873,6 +9006,17 @@ sub generate_letter { $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', @@ -8882,7 +9026,8 @@ sub generate_letter { $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 @@ -8893,8 +9038,12 @@ Returns an postscript letter filled in from TEMPLATE, as a scalar. 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 @@ -9030,6 +9179,7 @@ sub _upgrade_data { #class method $sth->execute or die $sth->errstr; local($ignore_expired_card) = 1; + local($skip_fuzzyfiles) = 1; $class->_upgrade_otaker(%opts); }