X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=f102d97eea2cb459448b8b76c17b09c27698a148;hp=cd675f9d43789077b98643405aa34187ac735676;hb=ad3bcb39580173f0ac1b6357cb49515d48af7ddf;hpb=aeb90ade381fc3d5477db0334048c2af623fccfe diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index cd675f9d4..f102d97ee 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -24,7 +24,6 @@ use Scalar::Util qw( blessed ); use Time::Local qw(timelocal); use Data::Dumper; use Tie::IxHash; -use Digest::MD5 qw(md5_base64); use Date::Format; #use Date::Manip; use File::Temp; #qw( tempfile ); @@ -76,6 +75,7 @@ use FS::Locales; use FS::upgrade_journal; use FS::sales; use FS::cust_payby; +use FS::contact; # 1 is mostly method/subroutine entry and options # 2 traces progress of some operations @@ -92,11 +92,11 @@ our $skip_fuzzyfiles = 0; our $ucfirst_nowarn = 0; +#this info is in cust_payby as of 4.x +#this and the fields themselves can be removed in 5.x our @encrypted_fields = ('payinfo', 'paycvv'); sub nohistory_fields { ('payinfo', 'paycvv'); } -our @paytypes = ('', 'Personal checking', 'Personal savings', 'Business checking', 'Business savings'); - our $conf; #ask FS::UID to run this stuff for us later #$FS::UID::callback{'FS::cust_main'} = sub { @@ -279,6 +279,10 @@ Allow self-service editing of ticket subjects, empty or 'Y' Do not call, empty or 'Y' +=item invoice_ship_address + +Display ship_address ("Service address") on invoices for this customer, empty or 'Y' + =back =head1 METHODS @@ -331,7 +335,7 @@ invoicing_list destination to the newly-created svc_acct. Here's an example: $cust_main->insert( {}, [ $email, 'POST' ] ); Currently available options are: I, I, -I and I. +I, I, I and I. If I is set, all provisioning jobs will have a dependancy on the supplied jobnum (they will not run until the specific job completes). @@ -351,6 +355,14 @@ If I is set, moves contacts and locations from that prospect. If I is set to an arrayref of FS::contact objects, inserts those new contacts with this new customer. +If I is set to a hashref of CGI parameters (and I is +unset), inserts those new contacts with this new customer. Handles CGI +paramaters for an "m2" multiple entry field as passed by edit/cust_main.cgi + +If I is set to a hashref o fCGI parameters, inserts those +new stored payment records with this new customer. Handles CGI parameters +for an "m2" multiple entry field as passed by edit/cust_main.cgi + =cut sub insert { @@ -378,7 +390,7 @@ sub insert { my $payby = ''; if ( $self->payby eq 'PREPAY' ) { - $self->payby('BILL'); + $self->payby(''); #'BILL'); $prepay_identifier = $self->payinfo; $self->payinfo(''); @@ -403,7 +415,7 @@ sub insert { } elsif ( $self->payby =~ /^(CASH|WEST|MCRD|MCHK|PPAL)$/ ) { $payby = $1; - $self->payby('BILL'); + $self->payby(''); #'BILL'); $amount = $self->paid; } @@ -557,8 +569,10 @@ sub insert { } - my $contact = delete $options{'contact'}; - if ( $contact ) { + warn " setting contacts\n" + if $DEBUG > 1; + + if ( my $contact = delete $options{'contact'} ) { foreach my $c ( @$contact ) { $c->custnum($self->custnum); @@ -570,6 +584,34 @@ sub insert { } + } elsif ( my $contact_params = delete $options{'contact_params'} ) { + + my $error = $self->process_o2m( 'table' => 'contact', + 'fields' => FS::contact->cgi_contact_fields, + 'params' => $contact_params, + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + warn " setting cust_payby\n" + if $DEBUG > 1; + + if ( my $cust_payby_params = delete $options{'cust_payby_params'} ) { + + my $error = $self->process_o2m( + 'table' => 'cust_payby', + 'fields' => FS::cust_payby->cgi_cust_payby_fields, + 'params' => $cust_payby_params, + 'hash_callback' => \&FS::cust_payby::cgi_hash_callback, + ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } warn " setting cust_main_exemption\n" @@ -1122,10 +1164,9 @@ sub delete { #cust_tax_adjustment in financials? #cust_pay_pending? ouch - #cust_recon? foreach my $table (qw( cust_main_invoice cust_main_exemption cust_tag cust_attachment contact - cust_location cust_main_note cust_tax_adjustment + cust_payby cust_location cust_main_note cust_tax_adjustment cust_pay_void cust_pay_batch queue cust_tax_exempt )) { foreach my $record ( qsearch( $table, { 'custnum' => $self->custnum } ) ) { @@ -1250,13 +1291,10 @@ sub replace { if $DEBUG; my $curuser = $FS::CurrentUser::CurrentUser; - if ( $self->payby eq 'COMP' - && $self->payby ne $old->payby - && ! $curuser->access_right('Complimentary customer') - ) - { - return "You are not permitted to create complimentary accounts."; - } + return "You are not permitted to create complimentary accounts." + if $self->complimentary eq 'Y' + && $self->complimentary ne $old->complimentary + && ! $curuser->access_right('Complimentary customer'); local($ignore_expired_card) = 1 if $old->payby =~ /^(CARD|DCRD)$/ @@ -1285,8 +1323,8 @@ sub replace { my $dbh = dbh; for my $l (qw(bill_location ship_location)) { - my $old_loc = $old->$l; - my $new_loc = $self->$l; + #my $old_loc = $old->$l; + my $new_loc = $self->$l or next; # find the existing location if there is one $new_loc->set('custnum' => $self->custnum); @@ -1403,21 +1441,19 @@ sub replace { } - if ( $self->payby =~ /^(CARD|CHEK|LECB)$/ - && ( ( $self->get('payinfo') ne $old->get('payinfo') - && $self->get('payinfo') !~ /^99\d{14}$/ - ) - || grep { $self->get($_) ne $old->get($_) } qw(paydate payname) - ) - ) - { + if ( my $cust_payby_params = delete $options{'cust_payby_params'} ) { - # card/check/lec info has changed, want to retry realtime_ invoice events - my $error = $self->retry_realtime; + my $error = $self->process_o2m( + 'table' => 'cust_payby', + 'fields' => FS::cust_payby->cgi_cust_payby_fields, + 'params' => $cust_payby_params, + 'hash_callback' => \&FS::cust_payby::cgi_hash_callback, + ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; } + } unless ( $import || $skip_fuzzyfiles ) { @@ -1555,6 +1591,9 @@ sub check { || $self->ut_flag('message_noemail') || $self->ut_enum('locale', [ '', FS::Locales->locales ]) || $self->ut_currencyn('currency') + || $self->ut_alphan('po_number') + || $self->ut_enum('complimentary', [ '', 'Y' ]) + || $self->ut_flag('invoice_ship_address') ; foreach (qw(company ship_company)) { @@ -1599,6 +1638,11 @@ sub check { $self->ss("$1-$2-$3"); } + #turn off invoice_ship_address if ship & bill are the same + if ($self->bill_locationnum eq $self->ship_locationnum) { + $self->invoice_ship_address(''); + } + # cust_main_county verification now handled by cust_location check $error = @@ -1809,6 +1853,11 @@ sub check { } + return "You are not permitted to create complimentary accounts." + if ! $self->custnum + && $self->complimentary eq 'Y' + && ! $FS::CurrentUser::CurrentUser->access_right('Complimentary customer'); + if ( $self->paydate eq '' || $self->paydate eq '-' ) { return "Expiration date required" # shouldn't payinfo_check do this? @@ -1874,6 +1923,21 @@ sub check { $self->SUPER::check; } +=item replace_check + +Additional checks for replace only. + +=cut + +sub replace_check { + my ($new,$old) = @_; + #preserve old value if global config is set + if ($old && $conf->exists('invoice-ship_address')) { + $new->invoice_ship_address($old->invoice_ship_address); + } + return ''; +} + =item addr_fields Returns a list of fields which have ship_ duplicates. @@ -1947,10 +2011,21 @@ sub cust_payby { qsearch({ 'table' => 'cust_payby', 'hashref' => { 'custnum' => $self->custnum }, - 'order_by' => 'ORDER BY weight ASC', + 'order_by' => "ORDER BY payby IN ('CARD','CHEK') DESC, weight ASC", }); } +sub has_cust_payby_auto { + my $self = shift; + scalar( qsearch({ + 'table' => 'cust_payby', + 'hashref' => { 'custnum' => $self->custnum, }, + 'extra_sql' => " AND payby IN ( 'CARD', 'CHEK' ) ", + 'order_by' => 'LIMIT 1', + }) ); + +} + =item unsuspend Unsuspends all unflagged suspended packages (see L @@ -2089,16 +2164,21 @@ sub cancel { return ( 'access denied' ) unless $FS::CurrentUser::CurrentUser->access_right('Cancel customer'); - if ( $opt{'ban'} && $self->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ ) { + if ( $opt{'ban'} ) { - #should try decryption (we might have the private key) - # and if not maybe queue a job for the server that does? - return ( "Can't (yet) ban encrypted credit cards" ) - if $self->is_encrypted($self->payinfo); + foreach my $cust_payby ( $self->cust_payby ) { - my $ban = new FS::banned_pay $self->_new_banned_pay_hashref; - my $error = $ban->insert; - return ( $error ) if $error; + #well, if they didn't get decrypted on search, then we don't have to + # try again... queue a job for the server that does have decryption + # capability if we're in a paranoid multi-server implementation? + return ( "Can't (yet) ban encrypted credit cards" ) + if $cust_payby->is_encrypted($cust_payby->payinfo); + + my $ban = new FS::banned_pay $cust_payby->_new_banned_pay_hashref; + my $error = $ban->insert; + return ( $error ) if $error; + + } } @@ -2135,13 +2215,6 @@ sub _banned_pay_hashref { }; } -sub _new_banned_pay_hashref { - my $self = shift; - my $hr = $self->_banned_pay_hashref; - $hr->{payinfo} = md5_base64($hr->{payinfo}); - $hr; -} - =item notes Returns all notes (see L) for this customer. @@ -3093,6 +3166,7 @@ sub charge { my ( $setuptax, $taxclass ); #internal taxes my ( $taxproduct, $override ); #vendor (CCH) taxes my $no_auto = ''; + my $separate_bill = ''; my $cust_pkg_ref = ''; my ( $bill_now, $invoice_terms ) = ( 0, '' ); my $locationnum; @@ -3115,7 +3189,8 @@ sub charge { $bill_now = exists($_[0]->{bill_now}) ? $_[0]->{bill_now} : ''; $invoice_terms = exists($_[0]->{invoice_terms}) ? $_[0]->{invoice_terms} : ''; $locationnum = $_[0]->{locationnum} || $self->ship_locationnum; - } else { + $separate_bill = $_[0]->{separate_bill} || ''; + } else { # yuck $amount = shift; $setup_cost = ''; $quantity = 1; @@ -3183,6 +3258,7 @@ sub charge { 'quantity' => $quantity, 'start_date' => $start_date, 'no_auto' => $no_auto, + 'separate_bill' => $separate_bill, 'locationnum'=> $locationnum, } ); @@ -3862,6 +3938,27 @@ sub cust_status { } } +=item is_status_delay_cancel + +Returns true if customer status is 'suspended' +and all suspended cust_pkg return true for +cust_pkg->is_status_delay_cancel. + +This is not a real status, this only meant for hacking display +values, because otherwise treating the customer as suspended is +really the whole point of the delay_cancel option. + +=cut + +sub is_status_delay_cancel { + my ($self) = @_; + return 0 unless $self->status eq 'suspended'; + foreach my $cust_pkg ($self->ncancelled_pkgs) { + return 0 unless $cust_pkg->is_status_delay_cancel; + } + return 1; +} + =item ucfirst_cust_status =item ucfirst_status @@ -4816,18 +4913,31 @@ sub _upgrade_data { #class method while (my $cust_main = $search->fetch) { - my $cust_payby = new FS::cust_payby { - 'custnum' => $cust_main->custnum, - 'weight' => 1, - map { $_ => $cust_main->$_(); } @payfields - }; + unless ( $cust_main->payby =~ /^(BILL|COMP)$/ ) { - my $error = $cust_payby->insert; - die $error if $error; + my $cust_payby = new FS::cust_payby { + 'custnum' => $cust_main->custnum, + 'weight' => 1, + map { $_ => $cust_main->$_(); } @payfields + }; + + my $error = $cust_payby->insert; + die $error if $error; + + } + + $cust_main->complimentary('Y') if $cust_main->payby eq 'COMP'; + + $cust_main->invoice_attn( $cust_main->payname ) + if $cust_main->payby eq 'BILL' && $cust_main->payname; + $cust_main->po_number( $cust_main->payinfo ) + if $cust_main->payby eq 'BILL' && $cust_main->payinfo; $cust_main->setfield($_, '') foreach @payfields; - $error = $cust_main->replace; - die $error if $error; + my $error = $cust_main->replace; + die "Error upgradging payment information for custnum ". + $cust_main->custnum. ": $error" + if $error; };