X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=165e45f6d5388cf48be54e3e2a4a013fc4f6bd89;hb=7f9c2a0086d8f644b27b79c14cedb627a96aa1cf;hp=780c3b992fdd2ffd7dd1dc62134db23787eb90aa;hpb=8c376cbb6b47cf84c8b2afccddd79458296a86a2;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 780c3b992..165e45f6d 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2,8 +2,12 @@ package FS::cust_main; require 5.006; use strict; -use vars qw( @ISA @EXPORT_OK $DEBUG $me $conf @encrypted_fields - $import $skip_fuzzyfiles $ignore_expired_card @paytypes); +use vars qw( @ISA @EXPORT_OK $DEBUG $me $conf + @encrypted_fields + $import $ignore_expired_card + $skip_fuzzyfiles @fuzzyfields + @paytypes + ); use vars qw( $realtime_bop_decline_quiet ); #ugh use Safe; use Carp; @@ -49,6 +53,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; @@ -77,9 +82,11 @@ $DEBUG = 0; $me = '[FS::cust_main]'; $import = 0; -$skip_fuzzyfiles = 0; $ignore_expired_card = 0; +$skip_fuzzyfiles = 0; +@fuzzyfields = ( 'first', 'last', 'company', 'address1' ); + @encrypted_fields = ('payinfo', 'paycvv'); sub nohistory_fields { ('paycvv'); } @@ -465,6 +472,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; @@ -1309,23 +1340,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; + } } } @@ -1414,6 +1435,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'}; @@ -1492,9 +1535,7 @@ sub queue_fuzzyfiles_update { my $dbh = dbh; my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - my $error = $queue->insert( map $self->getfield($_), - qw(first last company) - ); + my $error = $queue->insert( map $self->getfield($_), @fuzzyfields ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; @@ -1502,9 +1543,7 @@ sub queue_fuzzyfiles_update { if ( $self->ship_last ) { $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - $error = $queue->insert( map $self->getfield("ship_$_"), - qw(first last company) - ); + $error = $queue->insert( map $self->getfield("ship_$_"), @fuzzyfields ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; @@ -1908,6 +1947,25 @@ sub has_ship_address { scalar( grep { $self->getfield("ship_$_") ne '' } $self->addr_fields ); } +=item location_hash + +Returns a list of key/value pairs, with the following keys: address1, adddress2, +city, county, state, zip, country. The shipping address is used if present. + +=cut + +#geocode? dependent on tax-ship_address config, not available in cust_location +#mostly. not yet then. + +sub location_hash { + my $self = shift; + my $prefix = $self->has_ship_address ? 'ship_' : ''; + + map { $_ => $self->get($prefix.$_) } + qw( address1 address2 city county state zip country geocode ); + #fields that cust_location has +} + =item all_pkgs [ EXTRA_QSEARCH_PARAMS_HASHREF ] Returns all packages (see L) for this customer. @@ -1951,6 +2009,61 @@ sub cust_location { qsearch('cust_location', { 'custnum' => $self->custnum } ); } +=item location_label [ OPTION => VALUE ... ] + +Returns the label of the service location (see analog in L) for this customer. + +Options are + +=over 4 + +=item join_string + +used to separate the address elements (defaults to ', ') + +=item escape_function + +a callback used for escaping the text of the address elements + +=back + +=cut + +# false laziness with FS::cust_location::line + +sub location_label { + my $self = shift; + my %opt = @_; + + my $separator = $opt{join_string} || ', '; + my $escape = $opt{escape_function} || sub{ shift }; + my $line = ''; + my $cydefault = FS::conf->new->config('countrydefault') || 'US'; + my $prefix = length($self->ship_last) ? 'ship_' : ''; + + my $notfirst = 0; + foreach (qw ( address1 address2 ) ) { + my $method = "$prefix$_"; + $line .= ($notfirst ? $separator : ''). &$escape($self->$method) + if $self->$method; + $notfirst++; + } + $notfirst = 0; + foreach (qw ( city county state zip ) ) { + my $method = "$prefix$_"; + if ( $self->$method ) { + $line .= ' (' if $method eq 'county'; + $line .= ($notfirst ? ' ' : $separator). &$escape($self->$method); + $line .= ' )' if $method eq 'county'; + $notfirst++; + } + } + $line .= $separator. &$escape(code2country($self->country)) + if $self->country ne $cydefault; + + $line; +} + =item ncancelled_pkgs [ EXTRA_QSEARCH_PARAMS_HASHREF ] Returns all non-cancelled packages (see L) for this customer. @@ -2012,6 +2125,9 @@ sub _cust_pkg { # This should be generalized to use config options to determine order. sub sort_packages { + my $locationsort = ( $a->locationnum || 0 ) <=> ( $b->locationnum || 0 ); + return $locationsort if $locationsort; + if ( $a->get('cancel') xor $b->get('cancel') ) { return -1 if $b->get('cancel'); return 1 if $a->get('cancel'); @@ -2025,6 +2141,9 @@ sub sort_packages { return 1 if !$a_num_cust_svc && $b_num_cust_svc; my @a_cust_svc = $a->cust_svc; my @b_cust_svc = $b->cust_svc; + return 0 if !scalar(@a_cust_svc) && !scalar(@b_cust_svc); + return -1 if scalar(@a_cust_svc) && !scalar(@b_cust_svc); + return 1 if !scalar(@a_cust_svc) && scalar(@b_cust_svc); $a_cust_svc[0]->svc_x->label cmp $b_cust_svc[0]->svc_x->label; } @@ -2067,6 +2186,21 @@ sub unsuspended_pkgs { grep { ! $_->susp } $self->ncancelled_pkgs; } +=item active_pkgs + +Returns all unsuspended (and uncancelled) packages (see L) for +this customer that are active (recurring). + +=cut + +sub active_pkgs { + my $self = shift; + grep { my $part_pkg = $_->part_pkg; + $part_pkg->freq ne '' && $part_pkg->freq ne '0'; + } + $self->unsuspended_pkgs;; +} + =item next_bill_date Returns the next date this customer will be billed, as a UNIX timestamp, or @@ -2076,7 +2210,7 @@ undef if no active package has a next bill date. sub next_bill_date { my $self = shift; - min( map $_->get('bill'), grep $_->get('bill'), $self->unsuspended_pkgs ); + min( map $_->get('bill'), grep $_->get('bill'), $self->active_pkgs ); } =item num_cancelled_pkgs @@ -2303,12 +2437,49 @@ sub agent { qsearchs( 'agent', { 'agentnum' => $self->agentnum } ); } +=item agent_name + +Returns the agent name (see L) 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 bill_and_collect Cancels and suspends any packages due, generates bills, applies payments and -cred +credits, and applies collection events to run cards, send bills and notices, +etc. -Warns on errors (Does not currently: If there is an error, returns the error, otherwise returns false.) +By default, warns on errors and continues with the next operation (but see the +"fatal" flag below). Options are passed as name-value pairs. Currently available options are: @@ -2334,6 +2505,12 @@ Used in conjunction with the I