1 #this is so kludgy i'd be embarassed if it wasn't cybercash's fault
3 use vars qw($paymentserversecret $paymentserverport $paymentserverhost);
8 use vars qw( @ISA $conf $lpr $processor $xaction $E_NoErr $invoice_from
17 use Business::CreditCard;
18 use FS::UID qw( getotaker );
19 use FS::Record qw( qsearchs qsearch );
22 use FS::cust_bill_pkg;
25 use FS::cust_pay_batch;
26 use FS::part_referral;
27 use FS::cust_main_county;
29 use FS::cust_main_invoice;
31 @ISA = qw( FS::Record );
36 #ask FS::UID to run this stuff for us later
37 $FS::UID::callback{'FS::cust_main'} = sub {
39 $lpr = $conf->config('lpr');
40 $invoice_from = $conf->config('invoice_from');
41 $smtpmachine = $conf->config('smtpmachine');
43 if ( $conf->exists('cybercash3.2') ) {
45 #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
46 require CCMckDirectLib3_2;
48 require CCMckErrno3_2;
49 #qw(MCKGetErrorMessage $E_NoErr);
50 import CCMckErrno3_2 qw($E_NoErr);
53 ($merchant_conf,$xaction)= $conf->config('cybercash3.2');
54 my $status = &CCMckLib3_2::InitConfig($merchant_conf);
55 if ( $status != $E_NoErr ) {
56 warn "CCMckLib3_2::InitConfig error:\n";
57 foreach my $key (keys %CCMckLib3_2::Config) {
58 warn " $key => $CCMckLib3_2::Config{$key}\n"
60 my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
61 die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
63 $processor='cybercash3.2';
64 } elsif ( $conf->exists('cybercash2') ) {
67 ( $main::paymentserverhost,
68 $main::paymentserverport,
69 $main::paymentserversecret,
71 ) = $conf->config('cybercash2');
72 $processor='cybercash2';
78 FS::cust_main - Object methods for cust_main records
84 $record = new FS::cust_main \%hash;
85 $record = new FS::cust_main { 'column' => 'value' };
87 $error = $record->insert;
89 $error = $new_record->replace($old_record);
91 $error = $record->delete;
93 $error = $record->check;
95 @cust_pkg = $record->all_pkgs;
97 @cust_pkg = $record->ncancelled_pkgs;
99 $error = $record->bill;
100 $error = $record->bill %options;
101 $error = $record->bill 'time' => $time;
103 $error = $record->collect;
104 $error = $record->collect %options;
105 $error = $record->collect 'invoice_time' => $time,
106 'batch_card' => 'yes',
107 'report_badcard' => 'yes',
112 An FS::cust_main object represents a customer. FS::cust_main inherits from
113 FS::Record. The following fields are currently supported:
117 =item custnum - primary key (assigned automatically for new customers)
119 =item agentnum - agent (see L<FS::agent>)
121 =item refnum - referral (see L<FS::part_referral>)
127 =item ss - social security number (optional)
129 =item company - (optional)
133 =item address2 - (optional)
137 =item county - (optional, see L<FS::cust_main_county>)
139 =item state - (see L<FS::cust_main_county>)
143 =item country - (see L<FS::cust_main_county>)
145 =item daytime - phone (optional)
147 =item night - phone (optional)
149 =item fax - phone (optional)
151 =item payby - `CARD' (credit cards), `BILL' (billing), or `COMP' (free)
153 =item payinfo - card number, P.O.#, or comp issuer (4-8 lowercase alphanumerics; think username)
155 =item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
157 =item payname - name on card or billing name
159 =item tax - tax exempt, empty or `Y'
161 =item otaker - order taker (assigned automatically, see L<FS::UID>)
171 Creates a new customer. To add the customer to the database, see L<"insert">.
173 Note that this stores the hash reference, not a distinct copy of the hash it
174 points to. You can ask the object for a copy with the I<hash> method.
178 sub table { 'cust_main'; }
182 Adds this customer to the database. If there is an error, returns the error,
183 otherwise returns false.
185 =item delete NEW_CUSTNUM
187 This deletes the customer. If there is an error, returns the error, otherwise
190 This will completely remove all traces of the customer record. This is not
191 what you want when a customer cancels service; for that, cancel all of the
192 customer's packages (see L<FS::cust_pkg/cancel>).
194 If the customer has any packages, you need to pass a new (valid) customer
195 number for those packages to be transferred to.
197 You can't delete a customer with invoices (see L<FS::cust_bill>),
198 or credits (see L<FS::cust_credit>).
205 if ( qsearch( 'cust_bill', { 'custnum' => $self->custnum } ) ) {
206 return "Can't delete a customer with invoices";
208 if ( qsearch( 'cust_credit', { 'custnum' => $self->custnum } ) ) {
209 return "Can't delete a customer with credits";
212 local $SIG{HUP} = 'IGNORE';
213 local $SIG{INT} = 'IGNORE';
214 local $SIG{QUIT} = 'IGNORE';
215 local $SIG{TERM} = 'IGNORE';
216 local $SIG{TSTP} = 'IGNORE';
217 local $SIG{PIPE} = 'IGNORE';
219 my @cust_pkg = qsearch( 'cust_pkg', { 'custnum' => $self->custnum } );
221 my $new_custnum = shift;
222 return "Invalid new customer number: $new_custnum"
223 unless qsearchs( 'cust_main', { 'custnum' => $new_custnum } );
224 foreach my $cust_pkg ( @cust_pkg ) {
225 my %hash = $cust_pkg->hash;
226 $hash{'custnum'} = $new_custnum;
227 my $new_cust_pkg = new FS::cust_pkg ( \%hash );
228 my $error = $new_cust_pkg->replace($cust_pkg);
229 return $error if $error;
232 foreach my $cust_main_invoice (
233 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } )
235 my $error = $cust_main_invoice->delete;
236 return $error if $error;
239 $self->SUPER::delete;
242 =item replace OLD_RECORD
244 Replaces the OLD_RECORD with this one in the database. If there is an error,
245 returns the error, otherwise returns false.
249 Checks all fields to make sure this is a valid customer record. If there is
250 an error, returns the error, otherwise returns false. Called by the insert
259 $self->ut_numbern('custnum')
260 || $self->ut_number('agentnum')
261 || $self->ut_number('refnum')
262 || $self->ut_textn('company')
263 || $self->ut_text('address1')
264 || $self->ut_textn('address2')
265 || $self->ut_text('city')
266 || $self->ut_textn('county')
267 || $self->ut_textn('state')
268 || $self->ut_phonen('daytime')
269 || $self->ut_phonen('night')
270 || $self->ut_phonen('fax')
272 return $error if $error;
274 return "Unknown agent"
275 unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
277 return "Unknown referral"
278 unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
280 $self->getfield('last') =~ /^([\w \,\.\-\']+)$/
281 or return "Illegal last name: ". $self->getfield('last');
282 $self->setfield('last',$1);
284 $self->first =~ /^([\w \,\.\-\']+)$/
285 or return "Illegal first name: ". $self->first;
288 if ( $self->ss eq '' ) {
293 $ss =~ /^(\d{3})(\d{2})(\d{4})$/
294 or return "Illegal social security number: ". $self->ss;
295 $self->ss("$1-$2-$3");
298 $self->country =~ /^(\w\w)$/ or return "Illegal country: ". $self->country;
300 unless ( qsearchs('cust_main_county', {
301 'country' => $self->country,
304 return "Unknown state/county/country: ".
305 $self->state. "/". $self->county. "/". $self->country
306 unless qsearchs('cust_main_county',{
307 'state' => $self->state,
308 'county' => $self->county,
309 'country' => $self->country,
313 $self->zip =~ /^\s*(\w[\w\-\s]{3,8}\w)\s*$/
314 or return "Illegal zip: ". $self->zip;
317 $self->payby =~ /^(CARD|BILL|COMP)$/
318 or return "Illegal payby: ". $self->payby;
321 if ( $self->payby eq 'CARD' ) {
323 my $payinfo = $self->payinfo;
325 $payinfo =~ /^(\d{13,16})$/
326 or return "Illegal credit card number: ". $self->payinfo;
328 $self->payinfo($payinfo);
330 or return "Illegal credit card number: ". $self->payinfo;
331 return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
333 } elsif ( $self->payby eq 'BILL' ) {
335 $error = $self->ut_textn('payinfo');
336 return "Illegal P.O. number: ". $self->payinfo if $error;
338 } elsif ( $self->payby eq 'COMP' ) {
340 $error = $self->ut_textn('payinfo');
341 return "Illegal comp account issuer: ". $self->payinfo if $error;
345 if ( $self->paydate eq '' ) {
346 return "Expriation date required" unless $self->payby eq 'BILL';
349 $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/
350 or return "Illegal expiration date: ". $self->paydate;
351 if ( length($2) == 4 ) {
352 $self->paydate("$2-$1-01");
353 } elsif ( $2 > 97 ) { #should pry change to check for "this year"
354 $self->paydate("19$2-$1-01");
356 $self->paydate("20$2-$1-01");
360 if ( $self->payname eq '' ) {
361 $self->payname( $self->first. " ". $self->getfield('last') );
363 $self->payname =~ /^([\w \,\.\-\']+)$/
364 or return "Illegal billing name: ". $self->payname;
368 $self->tax =~ /^(Y?)$/ or return "Illegal tax: ". $self->tax;
371 $self->otaker(getotaker);
378 Returns all packages (see L<FS::cust_pkg>) for this customer.
384 qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
387 =item ncancelled_pkgs
389 Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
393 sub ncancelled_pkgs {
395 qsearch( 'cust_pkg', {
396 'custnum' => $self->custnum,
399 qsearch( 'cust_pkg', {
400 'custnum' => $self->custnum,
408 Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
409 conjunction with the collect method.
411 The only currently available option is `time', which bills the customer as if
412 it were that time. It is specified as a UNIX timestamp; see
413 L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
416 If there is an error, returns the error, otherwise returns false.
421 my( $self, %options ) = @_;
422 my $time = $options{'time'} || time;
427 local $SIG{HUP} = 'IGNORE';
428 local $SIG{INT} = 'IGNORE';
429 local $SIG{QUIT} = 'IGNORE';
430 local $SIG{TERM} = 'IGNORE';
431 local $SIG{TSTP} = 'IGNORE';
432 local $SIG{PIPE} = 'IGNORE';
434 # find the packages which are due for billing, find out how much they are
435 # & generate invoice database.
437 my( $total_setup, $total_recur ) = ( 0, 0 );
440 foreach my $cust_pkg (
441 qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
444 next if $cust_pkg->getfield('cancel');
446 #? to avoid use of uninitialized value errors... ?
447 $cust_pkg->setfield('bill', '')
448 unless defined($cust_pkg->bill);
450 my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
452 #so we don't modify cust_pkg record unnecessarily
453 my $cust_pkg_mod_flag = 0;
454 my %hash = $cust_pkg->hash;
455 my $old_cust_pkg = new FS::cust_pkg \%hash;
459 unless ( $cust_pkg->setup ) {
460 my $setup_prog = $part_pkg->getfield('setup');
462 #$cpt->permit(); #what is necessary?
463 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
464 $setup = $cpt->reval($setup_prog);
465 unless ( defined($setup) ) {
466 warn "Error reval-ing part_pkg->setup pkgpart ",
467 $part_pkg->pkgpart, ": $@";
469 $cust_pkg->setfield('setup',$time);
470 $cust_pkg_mod_flag=1;
477 if ( $part_pkg->getfield('freq') > 0 &&
478 ! $cust_pkg->getfield('susp') &&
479 ( $cust_pkg->getfield('bill') || 0 ) < $time
481 my $recur_prog = $part_pkg->getfield('recur');
483 #$cpt->permit(); #what is necessary?
484 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
485 $recur = $cpt->reval($recur_prog);
486 unless ( defined($recur) ) {
487 warn "Error reval-ing part_pkg->recur pkgpart ",
488 $part_pkg->pkgpart, ": $@";
490 #change this bit to use Date::Manip?
491 #$sdate=$cust_pkg->bill || time;
492 #$sdate=$cust_pkg->bill || $time;
493 $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
494 my ($sec,$min,$hour,$mday,$mon,$year) =
495 (localtime($sdate) )[0,1,2,3,4,5];
496 $mon += $part_pkg->getfield('freq');
497 until ( $mon < 12 ) { $mon -= 12; $year++; }
498 $cust_pkg->setfield('bill',
499 timelocal($sec,$min,$hour,$mday,$mon,$year));
500 $cust_pkg_mod_flag = 1;
504 warn "setup is undefinded" unless defined($setup);
505 warn "recur is undefinded" unless defined($recur);
506 warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
508 if ( $cust_pkg_mod_flag ) {
509 $error=$cust_pkg->replace($old_cust_pkg);
510 if ( $error ) { #just in case
511 warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
513 $setup = sprintf( "%.2f", $setup );
514 $recur = sprintf( "%.2f", $recur );
515 my $cust_bill_pkg = new FS::cust_bill_pkg ({
516 'pkgnum' => $cust_pkg->pkgnum,
520 'edate' => $cust_pkg->bill,
522 push @cust_bill_pkg, $cust_bill_pkg;
523 $total_setup += $setup;
524 $total_recur += $recur;
530 my $charged = sprintf( "%.2f", $total_setup + $total_recur );
532 return '' if scalar(@cust_bill_pkg) == 0;
534 unless ( $self->getfield('tax') =~ /Y/i
535 || $self->getfield('payby') eq 'COMP'
537 my $cust_main_county = qsearchs('cust_main_county',{
538 'state' => $self->state,
539 'county' => $self->county,
540 'country' => $self->country,
542 my $tax = sprintf( "%.2f",
543 $charged * ( $cust_main_county->getfield('tax') / 100 )
545 $charged = sprintf( "%.2f", $charged+$tax );
547 my $cust_bill_pkg = new FS::cust_bill_pkg ({
554 push @cust_bill_pkg, $cust_bill_pkg;
557 my $cust_bill = new FS::cust_bill ( {
558 'custnum' => $self->getfield('custnum'),
560 'charged' => $charged,
562 $error = $cust_bill->insert;
563 #shouldn't happen, but how else to handle this? (wrap me in eval, to catch
565 die "Error creating cust_bill record: $error!\n",
566 "Check updated but unbilled packages for customer", $self->custnum, "\n"
569 my $invnum = $cust_bill->invnum;
571 foreach $cust_bill_pkg ( @cust_bill_pkg ) {
572 $cust_bill_pkg->setfield( 'invnum', $invnum );
573 $error = $cust_bill_pkg->insert;
574 #shouldn't happen, but how else tohandle this?
575 die "Error creating cust_bill_pkg record: $error!\n",
576 "Check incomplete invoice ", $invnum, "\n"
583 =item collect OPTIONS
585 (Attempt to) collect money for this customer's outstanding invoices (see
586 L<FS::cust_bill>). Usually used after the bill method.
588 Depending on the value of `payby', this may print an invoice (`BILL'), charge
589 a credit card (`CARD'), or just add any necessary (pseudo-)payment (`COMP').
591 If there is an error, returns the error, otherwise returns false.
593 Currently available options are:
595 invoice_time - Use this time when deciding when to print invoices and
596 late notices on those invoices. The default is now. It is specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse>
597 for conversion functions.
599 batch_card - Set this true to batch cards (see L<cust_pay_batch>). By
600 default, cards are processed immediately, which will generate an error if
601 CyberCash is not installed.
603 report_badcard - Set this true if you want bad card transactions to
604 return an error. By default, they don't.
609 my( $self, %options ) = @_;
610 my $invoice_time = $options{'invoice_time'} || time;
612 my $total_owed = $self->balance;
613 warn "collect: total owed $total_owed " if $Debug;
614 return '' unless $total_owed > 0; #redundant?????
617 local $SIG{HUP} = 'IGNORE';
618 local $SIG{INT} = 'IGNORE';
619 local $SIG{QUIT} = 'IGNORE';
620 local $SIG{TERM} = 'IGNORE';
621 local $SIG{TSTP} = 'IGNORE';
622 local $SIG{PIPE} = 'IGNORE';
624 foreach my $cust_bill (
625 qsearch('cust_bill', { 'custnum' => $self->custnum, } )
628 #this has to be before next's
629 my $amount = sprintf( "%.2f", $total_owed < $cust_bill->owed
633 $total_owed = sprintf( "%.2f", $total_owed - $amount );
635 next unless $cust_bill->owed > 0;
637 next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
639 warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)" if $Debug;
641 next unless $amount > 0;
643 if ( $self->payby eq 'BILL' ) {
646 my $since = $invoice_time - ( $cust_bill->_date || 0 );
647 #warn "$invoice_time ", $cust_bill->_date, " $since";
648 if ( $since >= 0 #don't print future invoices
649 && ( $cust_bill->printed * 2592000 ) <= $since
652 #my @print_text = $cust_bill->print_text; #( date )
653 my @invoicing_list = $self->invoicing_list;
654 if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
655 $ENV{SMTPHOSTS} = $smtpmachine;
656 $ENV{MAILADDRESS} = $invoice_from;
657 my $header = new Mail::Header ( [
658 "From: $invoice_from",
659 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
660 "Sender: $invoice_from",
661 "Reply-To: $invoice_from",
662 "Date: ". time2str("%a, %d %b %Y %X %z", time),
665 my $message = new Mail::Internet (
667 'Body' => [ $cust_bill->print_text ], #( date)
669 $message->smtpsend or die "Can't send invoice email!"; #die? warn?
671 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
672 open(LPR, "|$lpr") or die "Can't open pipe to $lpr: $!";
673 print LPR $cust_bill->print_text; #( date )
675 or die $! ? "Error closing $lpr: $!"
676 : "Exit status $? from $lpr";
679 my %hash = $cust_bill->hash;
681 my $new_cust_bill = new FS::cust_bill(\%hash);
682 my $error = $new_cust_bill->replace($cust_bill);
683 warn "Error updating $cust_bill->printed: $error" if $error;
687 } elsif ( $self->payby eq 'COMP' ) {
688 my $cust_pay = new FS::cust_pay ( {
689 'invnum' => $cust_bill->invnum,
693 'payinfo' => $self->payinfo,
696 my $error = $cust_pay->insert;
697 return 'Error COMPing invnum #' . $cust_bill->invnum .
698 ':' . $error if $error;
700 } elsif ( $self->payby eq 'CARD' ) {
702 if ( $options{'batch_card'} ne 'yes' ) {
704 return "Real time card processing not enabled!" unless $processor;
706 if ( $processor =~ /^cybercash/ ) {
708 #fix exp. date for cybercash
709 #$self->paydate =~ /^(\d+)\/\d*(\d{2})$/;
710 $self->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
713 my $paybatch = $cust_bill->invnum.
714 '-' . time2str("%y%m%d%H%M%S", time);
716 my $payname = $self->payname ||
717 $self->getfield('first'). ' '. $self->getfield('last');
719 my $address = $self->address1;
720 $address .= ", ". $self->address2 if $self->address2;
722 my $country = 'USA' if $self->country eq 'US';
724 my @full_xaction = ( $xaction,
725 'Order-ID' => $paybatch,
726 'Amount' => "usd $amount",
727 'Card-Number' => $self->getfield('payinfo'),
728 'Card-Name' => $payname,
729 'Card-Address' => $address,
730 'Card-City' => $self->getfield('city'),
731 'Card-State' => $self->getfield('state'),
732 'Card-Zip' => $self->getfield('zip'),
733 'Card-Country' => $country,
738 if ( $processor eq 'cybercash2' ) {
739 $^W=0; #CCLib isn't -w safe, ugh!
740 %result = &CCLib::sendmserver(@full_xaction);
742 } elsif ( $processor eq 'cybercash3.2' ) {
743 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
745 return "Unkonwn real-time processor $processor\n";
748 #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
749 #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
750 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
751 my $cust_pay = new FS::cust_pay ( {
752 'invnum' => $cust_bill->invnum,
756 'payinfo' => $self->payinfo,
757 'paybatch' => "$processor:$paybatch",
759 my $error = $cust_pay->insert;
760 return 'Error applying payment, invnum #' .
761 $cust_bill->invnum. ':'. $error if $error;
762 } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
763 || $options{'report_badcard'} ) {
764 return 'Cybercash error, invnum #' .
765 $cust_bill->invnum. ':'. $result{'MErrMsg'};
771 return "Unkonwn real-time processor $processor\n";
776 my $cust_pay_batch = new FS::cust_pay_batch ( {
777 'invnum' => $cust_bill->getfield('invnum'),
778 'custnum' => $self->getfield('custnum'),
779 'last' => $self->getfield('last'),
780 'first' => $self->getfield('first'),
781 'address1' => $self->getfield('address1'),
782 'address2' => $self->getfield('address2'),
783 'city' => $self->getfield('city'),
784 'state' => $self->getfield('state'),
785 'zip' => $self->getfield('zip'),
786 'country' => $self->getfield('country'),
788 'cardnum' => $self->getfield('payinfo'),
789 'exp' => $self->getfield('paydate'),
790 'payname' => $self->getfield('payname'),
793 my $error = $cust_pay_batch->insert;
794 return "Error adding to cust_pay_batch: $error" if $error;
799 return "Unknown payment type ". $self->payby;
813 Returns the total owed for this customer on all invoices
814 (see L<FS::cust_bill>).
821 foreach my $cust_bill ( qsearch('cust_bill', {
822 'custnum' => $self->custnum,
824 $total_bill += $cust_bill->owed;
826 sprintf( "%.2f", $total_bill );
831 Returns the total credits (see L<FS::cust_credit>) for this customer.
837 my $total_credit = 0;
838 foreach my $cust_credit ( qsearch('cust_credit', {
839 'custnum' => $self->custnum,
841 $total_credit += $cust_credit->credited;
843 sprintf( "%.2f", $total_credit );
848 Returns the balance for this customer (total owed minus total credited).
854 sprintf( "%.2f", $self->total_owed - $self->total_credited );
857 =item invoicing_list [ ARRAYREF ]
859 If an arguement is given, sets these email addresses as invoice recipients
860 (see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
861 (except as warnings), so use check_invoicing_list first.
863 Returns a list of email addresses (with svcnum entries expanded).
865 Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
866 check it without disturbing anything by passing nothing.
868 This interface may change in the future.
873 my( $self, $arrayref ) = @_;
875 my @cust_main_invoice;
876 if ( $self->custnum ) {
878 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
880 @cust_main_invoice = ();
882 foreach my $cust_main_invoice ( @cust_main_invoice ) {
883 #warn $cust_main_invoice->destnum;
884 unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
885 #warn $cust_main_invoice->destnum;
886 my $error = $cust_main_invoice->delete;
887 warn $error if $error;
890 if ( $self->custnum ) {
892 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
894 @cust_main_invoice = ();
896 foreach my $address ( @{$arrayref} ) {
897 unless ( grep { $address eq $_->address } @cust_main_invoice ) {
898 my $cust_main_invoice = new FS::cust_main_invoice ( {
899 'custnum' => $self->custnum,
902 my $error = $cust_main_invoice->insert;
903 warn $error if $error;
907 if ( $self->custnum ) {
909 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
915 =item check_invoicing_list ARRAYREF
917 Checks these arguements as valid input for the invoicing_list method. If there
918 is an error, returns the error, otherwise returns false.
922 sub check_invoicing_list {
923 my( $self, $arrayref ) = @_;
924 foreach my $address ( @{$arrayref} ) {
925 my $cust_main_invoice = new FS::cust_main_invoice ( {
926 'custnum' => $self->custnum,
929 my $error = $self->custnum
930 ? $cust_main_invoice->check
931 : $cust_main_invoice->checkdest
933 return $error if $error;
942 $Id: cust_main.pm,v 1.2 1999-08-12 04:16:01 ivan Exp $
948 The delete method should possibly take an FS::cust_main object reference
949 instead of a scalar customer number.
951 Bill and collect options should probably be passed as references instead of a
954 CyberCash v2 forces us to define some variables in package main.
956 There should probably be a configuration file with a list of allowed credit
959 CyberCash is the only processor.
961 No multiple currency support (probably a larger project than just this module).
965 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
966 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
967 L<FS::cust_main_county>, L<FS::cust_main_invoice>,
968 L<FS::UID>, schema.html from the base documentation.