X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=e01154fb8f29c922a960970c685cdf47beee58fa;hb=5cb3aee153f8fd73defe6bfceef5983241739348;hp=07b2c115722edff23d8108fca892ef7051549f06;hpb=893ba3c4c659154ec34d30fb96853977a4479f09;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 07b2c1157..e01154fb8 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -42,7 +42,7 @@ use FS::cust_bill_pay; use FS::prepay_credit; use FS::queue; use FS::part_pkg; -use FS::part_bill_event; +use FS::part_bill_event qw(due_events); use FS::cust_bill_event; use FS::cust_tax_exempt; use FS::cust_tax_exempt_pkg; @@ -397,6 +397,8 @@ sub insert { warn " inserting $self\n" if $DEBUG > 1; + $self->signupdate(time) unless $self->signupdate; + my $error = $self->SUPER::insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -512,7 +514,7 @@ sub _copy_skel { my $child_table; my $child_pkey = ''; - if ( $child_table =~ /^(\w+)\.(\w+)$/ ) { + if ( $child_table_def =~ /^(\w+)\.(\w+)$/ ) { ( $child_table, $child_pkey ) = ( $1, $2 ); } else { $child_table = $child_table_def; @@ -523,7 +525,7 @@ sub _copy_skel { } my $sequence = ''; - if ( keys %{ $child_tables{$child_table} } ) { + if ( keys %{ $child_tables{$child_table_def} } ) { return "$child_table has no primary key". " (run dbdef-create or try specifying it?)\n" @@ -584,7 +586,7 @@ sub _copy_skel { # don't drink soap! recurse! recurse! okay! my $error = - _copy_skel( $child_table, + _copy_skel( $child_table_def, $row->{$child_pkey}, #sourceid $insertid, #destid %{ $child_tables{$child_table_def} }, @@ -690,21 +692,23 @@ sub order_pkgs { ''; #no error } -=item recharge_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , AMOUNTREF, SECONDSREF ] +=item recharge_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , AMOUNTREF, SECONDSREF, UPBYTEREF, DOWNBYTEREF ] Recharges this (existing) customer with the specified prepaid card (see L), specified either by I or as an FS::prepay_credit object. If there is an error, returns the error, otherwise returns false. -Optionally, two scalar references can be passed as well. They will have their -values filled in with the amount and number of seconds applied by this prepaid +Optionally, four scalar references can be passed as well. They will have their +values filled in with the amount, number of seconds, and number of upload and +download bytes applied by this prepaid card. =cut sub recharge_prepay { - my( $self, $prepay_credit, $amountref, $secondsref ) = @_; + my( $self, $prepay_credit, $amountref, $secondsref, + $upbytesref, $downbytesref ) = @_; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -717,10 +721,14 @@ sub recharge_prepay { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my( $amount, $seconds ) = ( 0, 0 ); + my( $amount, $seconds, $upbytes, $downbytes ) = ( 0, 0, 0, 0 ); - my $error = $self->get_prepay($prepay_credit, \$amount, \$seconds) + my $error = $self->get_prepay($prepay_credit, \$amount, + \$seconds, \$upbytes, \$downbytes) || $self->increment_seconds($seconds) + || $self->increment_upbytes($upbytes) + || $self->increment_downbytes($downbytes) + || $self->increment_totalbytes($upbytes + $downbytes) || $self->insert_cust_pay_prepay( $amount, ref($prepay_credit) ? $prepay_credit->identifier @@ -734,6 +742,8 @@ sub recharge_prepay { if ( defined($amountref) ) { $$amountref = $amount; } if ( defined($secondsref) ) { $$secondsref = $seconds; } + if ( defined($upbytesref) ) { $$upbytesref = $upbytes; } + if ( defined($downbytesref) ) { $$downbytesref = $downbytes; } $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -757,7 +767,7 @@ If there is an error, returns the error, otherwise returns false. sub get_prepay { - my( $self, $prepay_credit, $amountref, $secondsref ) = @_; + my( $self, $prepay_credit, $amountref, $secondsref, $upref, $downref) = @_; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -804,12 +814,50 @@ sub get_prepay { $$amountref += $prepay_credit->amount; $$secondsref += $prepay_credit->seconds; + $$upref += $prepay_credit->upbytes; + $$downref += $prepay_credit->downbytes; $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; } +=item increment_upbytes SECONDS + +Updates this customer's single or primary account (see L) by +the specified number of upbytes. If there is an error, returns the error, +otherwise returns false. + +=cut + +sub increment_upbytes { + _increment_column( shift, 'upbytes', @_); +} + +=item increment_downbytes SECONDS + +Updates this customer's single or primary account (see L) by +the specified number of downbytes. If there is an error, returns the error, +otherwise returns false. + +=cut + +sub increment_downbytes { + _increment_column( shift, 'downbytes', @_); +} + +=item increment_totalbytes SECONDS + +Updates this customer's single or primary account (see L) by +the specified number of totalbytes. If there is an error, returns the error, +otherwise returns false. + +=cut + +sub increment_totalbytes { + _increment_column( shift, 'totalbytes', @_); +} + =item increment_seconds SECONDS Updates this customer's single or primary account (see L) by @@ -819,10 +867,24 @@ otherwise returns false. =cut sub increment_seconds { - my( $self, $seconds ) = @_; - warn "$me increment_seconds called: $seconds seconds\n" + _increment_column( shift, 'seconds', @_); +} + +=item _increment_column AMOUNT + +Updates this customer's single or primary account (see L) by +the specified number of seconds or bytes. If there is an error, returns +the error, otherwise returns false. + +=cut + +sub _increment_column { + my( $self, $column, $amount ) = @_; + warn "$me increment_column called: $column, $amount\n" if $DEBUG; + return '' unless $amount; + my @cust_pkg = grep { $_->part_pkg->svcpart('svc_acct') } $self->ncancelled_pkgs; @@ -852,7 +914,8 @@ sub increment_seconds { ' ('. $svc_acct->email. ")\n" if $DEBUG > 1; - $svc_acct->increment_seconds($seconds); + $column = "increment_$column"; + $svc_acct->$column($amount); } @@ -1209,6 +1272,8 @@ sub check { || $self->ut_number('refnum') || $self->ut_name('last') || $self->ut_name('first') + || $self->ut_snumbern('birthdate') + || $self->ut_snumbern('signupdate') || $self->ut_textn('company') || $self->ut_text('address1') || $self->ut_textn('address2') @@ -1641,7 +1706,7 @@ Returns a list: an empty list on success or a list of errors. sub suspend { my $self = shift; - grep { $_->suspend } $self->unsuspended_pkgs; + grep { $_->suspend(@_) } $self->unsuspended_pkgs; } =item suspend_if_pkgpart PKGPART [ , PKGPART ... ] @@ -1655,8 +1720,14 @@ Returns a list: an empty list on success or a list of errors. sub suspend_if_pkgpart { my $self = shift; - my @pkgparts = @_; - grep { $_->suspend } + my (@pkgparts, %opt); + if (ref($_[0]) eq 'HASH'){ + @pkgparts = @{$_[0]{pkgparts}}; + %opt = %{$_[0]}; + }else{ + @pkgparts = @_; + } + grep { $_->suspend(%opt) } grep { my $pkgpart = $_->pkgpart; grep { $pkgpart eq $_ } @pkgparts } $self->unsuspended_pkgs; } @@ -1672,8 +1743,14 @@ Returns a list: an empty list on success or a list of errors. sub suspend_unless_pkgpart { my $self = shift; - my @pkgparts = @_; - grep { $_->suspend } + my (@pkgparts, %opt); + if (ref($_[0]) eq 'HASH'){ + @pkgparts = @{$_[0]{pkgparts}}; + %opt = %{$_[0]}; + }else{ + @pkgparts = @_; + } + grep { $_->suspend(%opt) } grep { my $pkgpart = $_->pkgpart; ! grep { $pkgpart eq $_ } @pkgparts } $self->unsuspended_pkgs; } @@ -1732,6 +1809,22 @@ sub _banned_pay_hashref { }; } +=item notes + +Returns all notes (see L) for this customer. + +=cut + +sub notes { + my $self = shift; + #order by? + qsearch( 'cust_main_note', + { 'custnum' => $self->custnum }, + '', + 'ORDER BY _DATE DESC' + ); +} + =item agent Returns the agent (see L) for this customer. @@ -2251,79 +2344,28 @@ sub collect { warn " invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ")\n" if $DEBUG > 1; - foreach my $part_bill_event ( - sort { $a->seconds <=> $b->seconds - || $a->weight <=> $b->weight - || $a->eventpart <=> $b->eventpart } - grep { $_->seconds <= ( $invoice_time - $cust_bill->_date ) - && ! qsearch( 'cust_bill_event', { - 'invnum' => $cust_bill->invnum, - 'eventpart' => $_->eventpart, - 'status' => 'done', - } ) - } - qsearch( { - 'table' => 'part_bill_event', - 'hashref' => { 'payby' => (exists($options{'payby'}) - ? $options{'payby'} - : $self->payby - ), - 'disabled' => '', }, - 'extra_sql' => $extra_sql, - } ) - ) { + foreach my $part_bill_event ( due_events ( $cust_bill, + exists($options{'payby'}) + ? $options{'payby'} + : $self->payby, + $invoice_time, + $extra_sql ) ) { last if $cust_bill->owed <= 0 # don't run subsequent events if owed<=0 || $self->balance <= 0; # or if balance<=0 - warn " calling invoice event (". $part_bill_event->eventcode. ")\n" - if $DEBUG > 1; - my $cust_main = $self; #for callback - - my $error; { local $realtime_bop_decline_quiet = 1 if $options{'quiet'}; - local $SIG{__DIE__}; # don't want Mason __DIE__ handler active - $error = eval $part_bill_event->eventcode; - } - - my $status = ''; - my $statustext = ''; - if ( $@ ) { - $status = 'failed'; - $statustext = $@; - } elsif ( $error ) { - $status = 'done'; - $statustext = $error; - } else { - $status = 'done' - } - - #add cust_bill_event - my $cust_bill_event = new FS::cust_bill_event { - 'invnum' => $cust_bill->invnum, - 'eventpart' => $part_bill_event->eventpart, - #'_date' => $invoice_time, - '_date' => time, - 'status' => $status, - 'statustext' => $statustext, - }; - $error = $cust_bill_event->insert; - if ( $error ) { - #$dbh->rollback if $oldAutoCommit; - #return "error: $error"; + warn " do_event " . $cust_bill . " ". (%options) . "\n" + if $DEBUG > 1; - # gah, even with transactions. - $dbh->commit if $oldAutoCommit; #well. - my $e = 'WARNING: Event run but database not updated - '. - 'error inserting cust_bill_event, invnum #'. $cust_bill->invnum. - ', eventpart '. $part_bill_event->eventpart. - ": $error"; - warn $e; - return $e; + if (my $error = $part_bill_event->do_event($cust_bill, %options)) { + # gah, even with transactions. + $dbh->commit if $oldAutoCommit; #well. + return $error; + } } - } } @@ -2335,9 +2377,10 @@ sub collect { =item retry_realtime -Schedules realtime credit card / electronic check / LEC billing events for -for retry. Useful if card information has changed or manual retry is desired. -The 'collect' method must be called to actually retry the transaction. +Schedules realtime / batch credit card / electronic check / LEC billing +events for for retry. Useful if card information has changed or manual +retry is desired. The 'collect' method must be called to actually retry +the transaction. Implementation details: For each of this customer's open invoices, changes the status of the first "done" (with statustext error) realtime processing @@ -2368,7 +2411,7 @@ sub retry_realtime { grep { #$_->part_bill_event->plan eq 'realtime-card' $_->part_bill_event->eventcode =~ - /\$cust_bill\->realtime_(card|ach|lec)/ + /\$cust_bill\->(batch|realtime)_(card|ach|lec)/ && $_->status eq 'done' && $_->statustext } @@ -2536,6 +2579,9 @@ sub realtime_bop { $content{customer_ip} = $payip if length($payip); + $content{invoice_number} = $options{'invnum'} + if exists($options{'invnum'}) && length($options{'invnum'}); + if ( $method eq 'CC' ) { $content{card_number} = $payinfo; @@ -2601,7 +2647,7 @@ sub realtime_bop { 'action' => $action1, 'description' => $options{'description'}, 'amount' => $amount, - 'invoice_number' => $options{'invnum'}, + #'invoice_number' => $options{'invnum'}, 'customer_id' => $self->custnum, 'last_name' => $paylast, 'first_name' => $payfirst, @@ -2647,7 +2693,8 @@ sub realtime_bop { description => $options{'description'}, ); - foreach my $field (qw( authorization_source_code returned_ACI transaction_identifier validation_code + foreach my $field (qw( authorization_source_code returned_ACI + transaction_identifier validation_code transaction_sequence_num local_transaction_date local_transaction_time AVS_result_code )) { $capture{$field} = $transaction->$field() if $transaction->can($field); @@ -4002,12 +4049,12 @@ sub fuzzy_search { check_and_rebuild_fuzzyfiles(); foreach my $field ( keys %$fuzzy ) { + + my $all = $self->all_X($field); + next unless scalar(@$all); + my %match = (); - $match{$_}=1 foreach ( amatch( $fuzzy->{$field}, - ['i'], - @{ $self->all_X($field) } - ) - ); + $match{$_}=1 foreach ( amatch( $fuzzy->{$field}, ['i'], @$all ) ); my @fcust = (); foreach ( keys %match ) { @@ -4036,10 +4083,10 @@ sub fuzzy_search { Accepts the following options: I, the string to search for. The string will be searched for as a customer number, phone number, name or company name, -first searching for an exact match then fuzzy and substring matches (in some -cases - see the source code for the exact heuristics used). +as an exact, or, in some cases, a substring or fuzzy match (see the source code +for the exact heuristics used). -Any additional options treated as an additional qualifier on the search +Any additional options are treated as an additional qualifier on the search (i.e. I). Returns a (possibly empty) array of FS::cust_main objects. @@ -4189,7 +4236,9 @@ sub smart_search { 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization } ); - unless ( @cust_main ) { #no exact match, trying substring/fuzzy + #always do substring & fuzzy, + #getting complains searches are not returning enough + #unless ( @cust_main ) { #no exact match, trying substring/fuzzy #still some false laziness w/ search/cust_main.cgi @@ -4250,7 +4299,7 @@ sub smart_search { FS::cust_main->fuzzy_search( { $field => $value }, @fuzopts ); } - } + #} #eliminate duplicates my %saw = (); @@ -4395,7 +4444,7 @@ sub batch_import { cust_pkg.pkgpart svc_acct.username svc_acct._password ); - $payby = 'CARD'; + $payby = 'BILL'; } else { die "unknown format $format"; } @@ -4466,16 +4515,22 @@ sub batch_import { if ( $field eq 'refnum' && $columns[0] !~ /^\s*(\d+)\s*$/ ) { my $referral = $columns[0]; - my $part_referral = new FS::part_referral { - 'referral' => $referral, - 'agentnum' => $agentnum, - }; - - my $error = $part_referral->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't auto-insert advertising source: $referral: $error"; + my %hash = ( 'referral' => $referral, + 'agentnum' => $agentnum, + 'disabled' => '', + ); + + my $part_referral = qsearchs('part_referral', \%hash ) + || new FS::part_referral \%hash; + + unless ( $part_referral->refnum ) { + my $error = $part_referral->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't auto-insert advertising source: $referral: $error"; + } } + $columns[0] = $part_referral->refnum; } @@ -4484,6 +4539,8 @@ sub batch_import { } } + $cust_main{'payby'} = 'CARD' if length($cust_main{'payinfo'}); + my $invoicing_list = $cust_main{'invoicing_list'} ? [ delete $cust_main{'invoicing_list'} ] : []; @@ -4498,7 +4555,12 @@ sub batch_import { my @svc_acct = (); if ( $svc_acct{'username'} ) { - $svc_acct{svcpart} = $cust_pkg->part_pkg->svcpart( 'svc_acct' ); + my $part_pkg = $cust_pkg->part_pkg; + unless ( $part_pkg ) { + $dbh->rollback if $oldAutoCommit; + return "unknown pkgnum ". $cust_pkg{'pkgpart'}; + } + $svc_acct{svcpart} = $part_pkg->svcpart( 'svc_acct' ); push @svc_acct, new FS::svc_acct ( \%svc_acct ) } @@ -4645,6 +4707,8 @@ No multiple currency support (probably a larger project than just this module). payinfo_masked false laziness with cust_pay.pm and cust_refund.pm +Birthdates rely on negative epoch values. + =head1 SEE ALSO L, L, L, L