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 );
33 #ask FS::UID to run this stuff for us later
34 $FS::UID::callback{'FS::cust_main'} = sub {
36 $lpr = $conf->config('lpr');
37 $invoice_from = $conf->config('invoice_from');
38 $smtpmachine = $conf->config('smtpmachine');
40 if ( $conf->exists('cybercash3.2') ) {
42 #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
43 require CCMckDirectLib3_2;
45 require CCMckErrno3_2;
46 #qw(MCKGetErrorMessage $E_NoErr);
47 import CCMckErrno3_2 qw($E_NoErr);
50 ($merchant_conf,$xaction)= $conf->config('cybercash3.2');
51 my $status = &CCMckLib3_2::InitConfig($merchant_conf);
52 if ( $status != $E_NoErr ) {
53 warn "CCMckLib3_2::InitConfig error:\n";
54 foreach my $key (keys %CCMckLib3_2::Config) {
55 warn " $key => $CCMckLib3_2::Config{$key}\n"
57 my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
58 die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
60 $processor='cybercash3.2';
61 } elsif ( $conf->exists('cybercash2') ) {
64 ( $main::paymentserverhost,
65 $main::paymentserverport,
66 $main::paymentserversecret,
68 ) = $conf->config('cybercash2');
69 $processor='cybercash2';
75 FS::cust_main - Object methods for cust_main records
81 $record = new FS::cust_main \%hash;
82 $record = new FS::cust_main { 'column' => 'value' };
84 $error = $record->insert;
86 $error = $new_record->replace($old_record);
88 $error = $record->delete;
90 $error = $record->check;
92 @cust_pkg = $record->all_pkgs;
94 @cust_pkg = $record->ncancelled_pkgs;
96 $error = $record->bill;
97 $error = $record->bill %options;
98 $error = $record->bill 'time' => $time;
100 $error = $record->collect;
101 $error = $record->collect %options;
102 $error = $record->collect 'invoice_time' => $time,
103 'batch_card' => 'yes',
104 'report_badcard' => 'yes',
109 An FS::cust_main object represents a customer. FS::cust_main inherits from
110 FS::Record. The following fields are currently supported:
114 =item custnum - primary key (assigned automatically for new customers)
116 =item agentnum - agent (see L<FS::agent>)
118 =item refnum - referral (see L<FS::part_referral>)
124 =item ss - social security number (optional)
126 =item company - (optional)
130 =item address2 - (optional)
134 =item county - (optional, see L<FS::cust_main_county>)
136 =item state - (see L<FS::cust_main_county>)
140 =item country - (see L<FS::cust_main_county>)
142 =item daytime - phone (optional)
144 =item night - phone (optional)
146 =item fax - phone (optional)
148 =item payby - `CARD' (credit cards), `BILL' (billing), or `COMP' (free)
150 =item payinfo - card number, P.O.#, or comp issuer (4-8 lowercase alphanumerics; think username)
152 =item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
154 =item payname - name on card or billing name
156 =item tax - tax exempt, empty or `Y'
158 =item otaker - order taker (assigned automatically, see L<FS::UID>)
168 Creates a new customer. To add the customer to the database, see L<"insert">.
170 Note that this stores the hash reference, not a distinct copy of the hash it
171 points to. You can ask the object for a copy with the I<hash> method.
175 sub table { 'cust_main'; }
179 Adds this customer to the database. If there is an error, returns the error,
180 otherwise returns false.
182 =item delete NEW_CUSTNUM
184 This deletes the customer. If there is an error, returns the error, otherwise
187 This will completely remove all traces of the customer record. This is not
188 what you want when a customer cancels service; for that, cancel all of the
189 customer's packages (see L<FS::cust_pkg/cancel>).
191 If the customer has any packages, you need to pass a new (valid) customer
192 number for those packages to be transferred to.
194 You can't delete a customer with invoices (see L<FS::cust_bill>),
195 or credits (see L<FS::cust_credit>).
202 if ( qsearch( 'cust_bill', { 'custnum' => $self->custnum } ) ) {
203 return "Can't delete a customer with invoices";
205 if ( qsearch( 'cust_credit', { 'custnum' => $self->custnum } ) ) {
206 return "Can't delete a customer with credits";
209 local $SIG{HUP} = 'IGNORE';
210 local $SIG{INT} = 'IGNORE';
211 local $SIG{QUIT} = 'IGNORE';
212 local $SIG{TERM} = 'IGNORE';
213 local $SIG{TSTP} = 'IGNORE';
214 local $SIG{PIPE} = 'IGNORE';
216 my @cust_pkg = qsearch( 'cust_pkg', { 'custnum' => $self->custnum } );
218 my $new_custnum = shift;
219 return "Invalid new customer number: $new_custnum"
220 unless qsearchs( 'cust_main', { 'custnum' => $new_custnum } );
221 foreach my $cust_pkg ( @cust_pkg ) {
222 my %hash = $cust_pkg->hash;
223 $hash{'custnum'} = $new_custnum;
224 my $new_cust_pkg = new FS::cust_pkg ( \%hash );
225 my $error = $new_cust_pkg->replace($cust_pkg);
226 return $error if $error;
229 foreach my $cust_main_invoice (
230 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } )
232 my $error = $cust_main_invoice->delete;
233 return $error if $error;
236 $self->SUPER::delete;
239 =item replace OLD_RECORD
241 Replaces the OLD_RECORD with this one in the database. If there is an error,
242 returns the error, otherwise returns false.
246 Checks all fields to make sure this is a valid customer record. If there is
247 an error, returns the error, otherwise returns false. Called by the insert
256 $self->ut_numbern('custnum')
257 || $self->ut_number('agentnum')
258 || $self->ut_number('refnum')
259 || $self->ut_textn('company')
260 || $self->ut_text('address1')
261 || $self->ut_textn('address2')
262 || $self->ut_text('city')
263 || $self->ut_textn('county')
264 || $self->ut_textn('state')
265 || $self->ut_phonen('daytime')
266 || $self->ut_phonen('night')
267 || $self->ut_phonen('fax')
269 return $error if $error;
271 return "Unknown agent"
272 unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
274 return "Unknown referral"
275 unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
277 $self->getfield('last') =~ /^([\w \,\.\-\']+)$/
278 or return "Illegal last name: ". $self->getfield('last');
279 $self->setfield('last',$1);
281 $self->first =~ /^([\w \,\.\-\']+)$/
282 or return "Illegal first name: ". $self->first;
285 if ( $self->ss eq '' ) {
290 $ss =~ /^(\d{3})(\d{2})(\d{4})$/
291 or return "Illegal social security number: ". $self->ss;
292 $self->ss("$1-$2-$3");
295 $self->country =~ /^(\w\w)$/ or return "Illegal country: ". $self->country;
297 unless ( qsearchs('cust_main_county', {
298 'country' => $self->country,
301 return "Unknown state/county/country: ".
302 $self->state. "/". $self->county. "/". $self->country
303 unless qsearchs('cust_main_county',{
304 'state' => $self->state,
305 'county' => $self->county,
306 'country' => $self->country,
310 $self->zip =~ /^\s*(\w[\w\-\s]{3,8}\w)\s*$/
311 or return "Illegal zip: ". $self->zip;
314 $self->payby =~ /^(CARD|BILL|COMP)$/
315 or return "Illegal payby: ". $self->payby;
318 if ( $self->payby eq 'CARD' ) {
320 my $payinfo = $self->payinfo;
322 $payinfo =~ /^(\d{13,16})$/
323 or return "Illegal credit card number: ". $self->payinfo;
325 $self->payinfo($payinfo);
327 or return "Illegal credit card number: ". $self->payinfo;
328 return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
330 } elsif ( $self->payby eq 'BILL' ) {
332 $error = $self->ut_textn('payinfo');
333 return "Illegal P.O. number: ". $self->payinfo if $error;
335 } elsif ( $self->payby eq 'COMP' ) {
337 $error = $self->ut_textn('payinfo');
338 return "Illegal comp account issuer: ". $self->payinfo if $error;
342 if ( $self->paydate eq '' ) {
343 return "Expriation date required" unless $self->payby eq 'BILL';
346 $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/
347 or return "Illegal expiration date: ". $self->paydate;
348 if ( length($2) == 4 ) {
349 $self->paydate("$2-$1-01");
350 } elsif ( $2 > 97 ) { #should pry change to check for "this year"
351 $self->paydate("19$2-$1-01");
353 $self->paydate("20$2-$1-01");
357 if ( $self->payname eq '' ) {
358 $self->payname( $self->first. " ". $self->getfield('last') );
360 $self->payname =~ /^([\w \,\.\-\']+)$/
361 or return "Illegal billing name: ". $self->payname;
365 $self->tax =~ /^(Y?)$/ or return "Illegal tax: ". $self->tax;
368 $self->otaker(getotaker);
375 Returns all packages (see L<FS::cust_pkg>) for this customer.
381 qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
384 =item ncancelled_pkgs
386 Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
390 sub ncancelled_pkgs {
392 qsearch( 'cust_pkg', {
393 'custnum' => $self->custnum,
400 Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
401 conjunction with the collect method.
403 The only currently available option is `time', which bills the customer as if
404 it were that time. It is specified as a UNIX timestamp; see
405 L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
408 If there is an error, returns the error, otherwise returns false.
413 my( $self, %options ) = @_;
414 my $time = $options{'time'} || time;
419 local $SIG{HUP} = 'IGNORE';
420 local $SIG{INT} = 'IGNORE';
421 local $SIG{QUIT} = 'IGNORE';
422 local $SIG{TERM} = 'IGNORE';
423 local $SIG{TSTP} = 'IGNORE';
424 local $SIG{PIPE} = 'IGNORE';
426 # find the packages which are due for billing, find out how much they are
427 # & generate invoice database.
429 my( $total_setup, $total_recur ) = ( 0, 0 );
432 foreach my $cust_pkg (
433 qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
436 next if $cust_pkg->getfield('cancel');
438 #? to avoid use of uninitialized value errors... ?
439 $cust_pkg->setfield('bill', '')
440 unless defined($cust_pkg->bill);
442 my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
444 #so we don't modify cust_pkg record unnecessarily
445 my $cust_pkg_mod_flag = 0;
446 my %hash = $cust_pkg->hash;
447 my $old_cust_pkg = new FS::cust_pkg \%hash;
451 unless ( $cust_pkg->setup ) {
452 my $setup_prog = $part_pkg->getfield('setup');
454 #$cpt->permit(); #what is necessary?
455 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
456 $setup = $cpt->reval($setup_prog);
457 unless ( defined($setup) ) {
458 warn "Error reval-ing part_pkg->setup pkgpart ",
459 $part_pkg->pkgpart, ": $@";
461 $cust_pkg->setfield('setup',$time);
462 $cust_pkg_mod_flag=1;
469 if ( $part_pkg->getfield('freq') > 0 &&
470 ! $cust_pkg->getfield('susp') &&
471 ( $cust_pkg->getfield('bill') || 0 ) < $time
473 my $recur_prog = $part_pkg->getfield('recur');
475 #$cpt->permit(); #what is necessary?
476 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
477 $recur = $cpt->reval($recur_prog);
478 unless ( defined($recur) ) {
479 warn "Error reval-ing part_pkg->recur pkgpart ",
480 $part_pkg->pkgpart, ": $@";
482 #change this bit to use Date::Manip?
483 #$sdate=$cust_pkg->bill || time;
484 #$sdate=$cust_pkg->bill || $time;
485 $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
486 my ($sec,$min,$hour,$mday,$mon,$year) =
487 (localtime($sdate) )[0,1,2,3,4,5];
488 $mon += $part_pkg->getfield('freq');
489 until ( $mon < 12 ) { $mon -= 12; $year++; }
490 $cust_pkg->setfield('bill',
491 timelocal($sec,$min,$hour,$mday,$mon,$year));
492 $cust_pkg_mod_flag = 1;
496 warn "setup is undefinded" unless defined($setup);
497 warn "recur is undefinded" unless defined($recur);
498 warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
500 if ( $cust_pkg_mod_flag ) {
501 $error=$cust_pkg->replace($old_cust_pkg);
502 if ( $error ) { #just in case
503 warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
505 $setup = sprintf( "%.2f", $setup );
506 $recur = sprintf( "%.2f", $recur );
507 my $cust_bill_pkg = new FS::cust_bill_pkg ({
508 'pkgnum' => $cust_pkg->pkgnum,
512 'edate' => $cust_pkg->bill,
514 push @cust_bill_pkg, $cust_bill_pkg;
515 $total_setup += $setup;
516 $total_recur += $recur;
522 my $charged = sprintf( "%.2f", $total_setup + $total_recur );
524 return '' if scalar(@cust_bill_pkg) == 0;
526 unless ( $self->getfield('tax') =~ /Y/i
527 || $self->getfield('payby') eq 'COMP'
529 my $cust_main_county = qsearchs('cust_main_county',{
530 'state' => $self->state,
531 'county' => $self->county,
532 'country' => $self->country,
534 my $tax = sprintf( "%.2f",
535 $charged * ( $cust_main_county->getfield('tax') / 100 )
537 $charged = sprintf( "%.2f", $charged+$tax );
539 my $cust_bill_pkg = new FS::cust_bill_pkg ({
546 push @cust_bill_pkg, $cust_bill_pkg;
549 my $cust_bill = new FS::cust_bill ( {
550 'custnum' => $self->getfield('custnum'),
552 'charged' => $charged,
554 $error = $cust_bill->insert;
555 #shouldn't happen, but how else to handle this? (wrap me in eval, to catch
557 die "Error creating cust_bill record: $error!\n",
558 "Check updated but unbilled packages for customer", $self->custnum, "\n"
561 my $invnum = $cust_bill->invnum;
563 foreach $cust_bill_pkg ( @cust_bill_pkg ) {
564 $cust_bill_pkg->setfield( 'invnum', $invnum );
565 $error = $cust_bill_pkg->insert;
566 #shouldn't happen, but how else tohandle this?
567 die "Error creating cust_bill_pkg record: $error!\n",
568 "Check incomplete invoice ", $invnum, "\n"
575 =item collect OPTIONS
577 (Attempt to) collect money for this customer's outstanding invoices (see
578 L<FS::cust_bill>). Usually used after the bill method.
580 Depending on the value of `payby', this may print an invoice (`BILL'), charge
581 a credit card (`CARD'), or just add any necessary (pseudo-)payment (`COMP').
583 If there is an error, returns the error, otherwise returns false.
585 Currently available options are:
587 invoice_time - Use this time when deciding when to print invoices and
588 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>
589 for conversion functions.
591 batch_card - Set this true to batch cards (see L<cust_pay_batch>). By
592 default, cards are processed immediately, which will generate an error if
593 CyberCash is not installed.
595 report_badcard - Set this true if you want bad card transactions to
596 return an error. By default, they don't.
601 my( $self, %options ) = @_;
602 my $invoice_time = $options{'invoice_time'} || time;
604 my $total_owed = $self->balance;
605 return '' unless $total_owed > 0; #redundant?????
608 local $SIG{HUP} = 'IGNORE';
609 local $SIG{INT} = 'IGNORE';
610 local $SIG{QUIT} = 'IGNORE';
611 local $SIG{TERM} = 'IGNORE';
612 local $SIG{TSTP} = 'IGNORE';
613 local $SIG{PIPE} = 'IGNORE';
615 foreach my $cust_bill (
616 qsearch('cust_bill', { 'custnum' => $self->custnum, } )
619 #this has to be before next's
620 my $amount = sprintf( "%.2f", $total_owed < $cust_bill->owed
624 $total_owed = sprintf( "%.2f", $total_owed - $amount );
626 next unless $cust_bill->owed > 0;
628 next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
630 #warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)";
632 next unless $amount > 0;
634 if ( $self->payby eq 'BILL' ) {
637 my $since = $invoice_time - ( $cust_bill->_date || 0 );
638 #warn "$invoice_time ", $cust_bill->_date, " $since";
639 if ( $since >= 0 #don't print future invoices
640 && ( $cust_bill->printed * 2592000 ) <= $since
643 #my @print_text = $cust_bill->print_text; #( date )
644 my @invoicing_list = $self->invoicing_list;
645 if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
646 $ENV{SMTPHOSTS} = $smtpmachine;
647 $ENV{MAILADDRESS} = $invoice_from;
648 my $header = new Mail::Header ( [
649 "From: $invoice_from",
650 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
651 "Sender: $invoice_from",
652 "Reply-To: $invoice_from",
653 "Date: ". time2str("%a, %d %b %Y %X %z", time),
656 my $message = new Mail::Internet (
658 'Body' => [ $cust_bill->print_text ], #( date)
660 $message->smtpsend or die "Can't send invoice email!"; #die? warn?
662 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
663 open(LPR, "|$lpr") or die "Can't open pipe to $lpr: $!";
664 print LPR $cust_bill->print_text; #( date )
666 or die $! ? "Error closing $lpr: $!"
667 : "Exit status $? from $lpr";
670 my %hash = $cust_bill->hash;
672 my $new_cust_bill = new FS::cust_bill(\%hash);
673 my $error = $new_cust_bill->replace($cust_bill);
674 warn "Error updating $cust_bill->printed: $error" if $error;
678 } elsif ( $self->payby eq 'COMP' ) {
679 my $cust_pay = new FS::cust_pay ( {
680 'invnum' => $cust_bill->invnum,
684 'payinfo' => $self->payinfo,
687 my $error = $cust_pay->insert;
688 return 'Error COMPing invnum #' . $cust_bill->invnum .
689 ':' . $error if $error;
691 } elsif ( $self->payby eq 'CARD' ) {
693 if ( $options{'batch_card'} ne 'yes' ) {
695 return "Real time card processing not enabled!" unless $processor;
697 if ( $processor =~ /^cybercash/ ) {
699 #fix exp. date for cybercash
700 #$self->paydate =~ /^(\d+)\/\d*(\d{2})$/;
701 $self->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
704 my $paybatch = $cust_bill->invnum.
705 '-' . time2str("%y%m%d%H%M%S", time);
707 my $payname = $self->payname ||
708 $self->getfield('first'). ' '. $self->getfield('last');
710 my $address = $self->address1;
711 $address .= ", ". $self->address2 if $self->address2;
713 my $country = 'USA' if $self->country eq 'US';
715 my @full_xaction = ( $xaction,
716 'Order-ID' => $paybatch,
717 'Amount' => "usd $amount",
718 'Card-Number' => $self->getfield('payinfo'),
719 'Card-Name' => $payname,
720 'Card-Address' => $address,
721 'Card-City' => $self->getfield('city'),
722 'Card-State' => $self->getfield('state'),
723 'Card-Zip' => $self->getfield('zip'),
724 'Card-Country' => $country,
729 if ( $processor eq 'cybercash2' ) {
730 $^W=0; #CCLib isn't -w safe, ugh!
731 %result = &CCLib::sendmserver(@full_xaction);
733 } elsif ( $processor eq 'cybercash3.2' ) {
734 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
736 return "Unkonwn real-time processor $processor\n";
739 #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
740 #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
741 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
742 my $cust_pay = new FS::cust_pay ( {
743 'invnum' => $cust_bill->invnum,
747 'payinfo' => $self->payinfo,
748 'paybatch' => "$processor:$paybatch",
750 my $error = $cust_pay->insert;
751 return 'Error applying payment, invnum #' .
752 $cust_bill->invnum. ':'. $error if $error;
753 } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
754 || $options{'report_badcard'} ) {
755 return 'Cybercash error, invnum #' .
756 $cust_bill->invnum. ':'. $result{'MErrMsg'};
762 return "Unkonwn real-time processor $processor\n";
767 my $cust_pay_batch = new FS::cust_pay_batch ( {
768 'invnum' => $cust_bill->getfield('invnum'),
769 'custnum' => $self->getfield('custnum'),
770 'last' => $self->getfield('last'),
771 'first' => $self->getfield('first'),
772 'address1' => $self->getfield('address1'),
773 'address2' => $self->getfield('address2'),
774 'city' => $self->getfield('city'),
775 'state' => $self->getfield('state'),
776 'zip' => $self->getfield('zip'),
777 'country' => $self->getfield('country'),
779 'cardnum' => $self->getfield('payinfo'),
780 'exp' => $self->getfield('paydate'),
781 'payname' => $self->getfield('payname'),
784 my $error = $cust_pay_batch->insert;
785 return "Error adding to cust_pay_batch: $error" if $error;
790 return "Unknown payment type ". $self->payby;
804 Returns the total owed for this customer on all invoices
805 (see L<FS::cust_bill>).
812 foreach my $cust_bill ( qsearch('cust_bill', {
813 'custnum' => $self->custnum,
815 $total_bill += $cust_bill->owed;
817 sprintf( "%.2f", $total_bill );
822 Returns the total credits (see L<FS::cust_credit>) for this customer.
828 my $total_credit = 0;
829 foreach my $cust_credit ( qsearch('cust_credit', {
830 'custnum' => $self->custnum,
832 $total_credit += $cust_credit->credited;
834 sprintf( "%.2f", $total_credit );
839 Returns the balance for this customer (total owed minus total credited).
845 sprintf( "%.2f", $self->total_owed - $self->total_credited );
848 =item invoicing_list [ ARRAYREF ]
850 If an arguement is given, sets these email addresses as invoice recipients
851 (see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
852 (except as warnings), so use check_invoicing_list first.
854 Returns a list of email addresses (with svcnum entries expanded).
856 Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
857 check it without disturbing anything by passing nothing.
859 This interface may change in the future.
864 my( $self, $arrayref ) = @_;
866 my @cust_main_invoice;
867 if ( $self->custnum ) {
869 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
871 @cust_main_invoice = ();
873 foreach my $cust_main_invoice ( @cust_main_invoice ) {
874 #warn $cust_main_invoice->destnum;
875 unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
876 #warn $cust_main_invoice->destnum;
877 my $error = $cust_main_invoice->delete;
878 warn $error if $error;
881 if ( $self->custnum ) {
883 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
885 @cust_main_invoice = ();
887 foreach my $address ( @{$arrayref} ) {
888 unless ( grep { $address eq $_->address } @cust_main_invoice ) {
889 my $cust_main_invoice = new FS::cust_main_invoice ( {
890 'custnum' => $self->custnum,
893 my $error = $cust_main_invoice->insert;
894 warn $error if $error;
898 if ( $self->custnum ) {
900 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
906 =item check_invoicing_list ARRAYREF
908 Checks these arguements as valid input for the invoicing_list method. If there
909 is an error, returns the error, otherwise returns false.
913 sub check_invoicing_list {
914 my( $self, $arrayref ) = @_;
915 foreach my $address ( @{$arrayref} ) {
916 my $cust_main_invoice = new FS::cust_main_invoice ( {
917 'custnum' => $self->custnum,
920 my $error = $self->custnum
921 ? $cust_main_invoice->check
922 : $cust_main_invoice->checkdest
924 return $error if $error;
933 $Id: cust_main.pm,v 1.1 1999-08-04 09:03:53 ivan Exp $
939 The delete method should possibly take an FS::cust_main object reference
940 instead of a scalar customer number.
942 Bill and collect options should probably be passed as references instead of a
945 CyberCash v2 forces us to define some variables in package main.
947 There should probably be a configuration file with a list of allowed credit
950 CyberCash is the only processor.
952 No multiple currency support (probably a larger project than just this module).
956 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
957 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
958 L<FS::cust_main_county>, L<FS::cust_main_invoice>,
959 L<FS::UID>, schema.html from the base documentation.