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);
16 use Business::CreditCard;
17 use FS::UID qw( getotaker );
18 use FS::Record qw( qsearchs qsearch );
21 use FS::cust_bill_pkg;
24 use FS::cust_pay_batch;
25 use FS::part_referral;
26 use FS::cust_main_county;
28 use FS::cust_main_invoice;
30 @ISA = qw( FS::Record );
32 #ask FS::UID to run this stuff for us later
33 $FS::UID::callback{'FS::cust_main'} = sub {
35 $lpr = $conf->config('lpr');
36 $invoice_from = $conf->config('invoice_from');
38 if ( $conf->exists('cybercash3.2') ) {
40 #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
41 require CCMckDirectLib3_2;
43 require CCMckErrno3_2;
44 #qw(MCKGetErrorMessage $E_NoErr);
45 import CCMckErrno3_2 qw($E_NoErr);
48 ($merchant_conf,$xaction)= $conf->config('cybercash3.2');
49 my $status = &CCMckLib3_2::InitConfig($merchant_conf);
50 if ( $status != $E_NoErr ) {
51 warn "CCMckLib3_2::InitConfig error:\n";
52 foreach my $key (keys %CCMckLib3_2::Config) {
53 warn " $key => $CCMckLib3_2::Config{$key}\n"
55 my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
56 die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
58 $processor='cybercash3.2';
59 } elsif ( $conf->exists('cybercash2') ) {
62 ( $main::paymentserverhost,
63 $main::paymentserverport,
64 $main::paymentserversecret,
66 ) = $conf->config('cybercash2');
67 $processor='cybercash2';
73 FS::cust_main - Object methods for cust_main records
79 $record = new FS::cust_main \%hash;
80 $record = new FS::cust_main { 'column' => 'value' };
82 $error = $record->insert;
84 $error = $new_record->replace($old_record);
86 $error = $record->delete;
88 $error = $record->check;
90 @cust_pkg = $record->all_pkgs;
92 @cust_pkg = $record->ncancelled_pkgs;
94 $error = $record->bill;
95 $error = $record->bill %options;
96 $error = $record->bill 'time' => $time;
98 $error = $record->collect;
99 $error = $record->collect %options;
100 $error = $record->collect 'invoice_time' => $time,
101 'batch_card' => 'yes',
102 'report_badcard' => 'yes',
107 An FS::cust_main object represents a customer. FS::cust_main inherits from
108 FS::Record. The following fields are currently supported:
112 =item custnum - primary key (assigned automatically for new customers)
114 =item agentnum - agent (see L<FS::agent>)
116 =item refnum - referral (see L<FS::part_referral>)
122 =item ss - social security number (optional)
124 =item company - (optional)
128 =item address2 - (optional)
132 =item county - (optional, see L<FS::cust_main_county>)
134 =item state - (see L<FS::cust_main_county>)
138 =item country - (see L<FS::cust_main_county>)
140 =item daytime - phone (optional)
142 =item night - phone (optional)
144 =item payby - `CARD' (credit cards), `BILL' (billing), or `COMP' (free)
146 =item payinfo - card number, P.O.#, or comp issuer (4-8 lowercase alphanumerics; think username)
148 =item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
150 =item payname - name on card or billing name
152 =item tax - tax exempt, empty or `Y'
154 =item otaker - order taker (assigned automatically, see L<FS::UID>)
164 Creates a new customer. To add the customer to the database, see L<"insert">.
166 Note that this stores the hash reference, not a distinct copy of the hash it
167 points to. You can ask the object for a copy with the I<hash> method.
171 sub table { 'cust_main'; }
175 Adds this customer to the database. If there is an error, returns the error,
176 otherwise returns false.
180 Currently unimplemented. Maybe cancel all of this customer's
183 I don't remove the customer record in the database because there would then
184 be no record the customer ever existed (which is bad, no?)
189 return "Can't (yet?) delete customers.";
192 =item replace OLD_RECORD
194 Replaces the OLD_RECORD with this one in the database. If there is an error,
195 returns the error, otherwise returns false.
199 Checks all fields to make sure this is a valid customer record. If there is
200 an error, returns the error, otherwise returns false. Called by the insert
209 $self->ut_numbern('custnum')
210 || $self->ut_number('agentnum')
211 || $self->ut_number('refnum')
212 || $self->ut_textn('company')
213 || $self->ut_text('address1')
214 || $self->ut_textn('address2')
215 || $self->ut_text('city')
216 || $self->ut_textn('county')
217 || $self->ut_text('state')
218 || $self->ut_phonen('daytime')
219 || $self->ut_phonen('night')
220 || $self->ut_phonen('fax')
222 return $error if $error;
224 return "Unknown agent"
225 unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
227 return "Unknown referral"
228 unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
230 $self->getfield('last') =~ /^([\w \,\.\-\']+)$/ or return "Illegal last name";
231 $self->setfield('last',$1);
233 $self->first =~ /^([\w \,\.\-\']+)$/ or return "Illegal first name";
236 if ( $self->ss eq '' ) {
241 $ss =~ /^(\d{3})(\d{2})(\d{4})$/
242 or return "Illegal social security number";
243 $self->ss("$1-$2-$3");
246 $self->country =~ /^(\w\w)$/ or return "Illegal country";
248 unless ( qsearchs('cust_main_county', {
249 'country' => $self->country,
252 return "Unknown state/county/country"
253 #" state ". $self->state. " county ". $self->county. " country ". $self->country
254 unless qsearchs('cust_main_county',{
255 'state' => $self->state,
256 'county' => $self->county,
257 'country' => $self->country,
261 $self->zip =~ /^([\w\-]{5,10})$/ or return "Illegal zip";
264 $self->payby =~ /^(CARD|BILL|COMP)$/ or return "Illegal payby";
267 if ( $self->payby eq 'CARD' ) {
269 my $payinfo = $self->payinfo;
271 $payinfo =~ /^(\d{13,16})$/
272 or return "Illegal credit card number";
274 $self->payinfo($payinfo);
275 validate($payinfo) or return "Illegal credit card number";
276 return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
278 } elsif ( $self->payby eq 'BILL' ) {
280 $error = $self->ut_textn('payinfo');
281 return "Illegal P.O. number" if $error;
283 } elsif ( $self->payby eq 'COMP' ) {
285 $error = $self->ut_textn('payinfo');
286 return "Illegal comp account issuer" if $error;
290 if ( $self->paydate eq '' ) {
291 return "Expriation date required" unless $self->payby eq 'BILL';
294 $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/
295 or return "Illegal expiration date";
296 if ( length($2) == 4 ) {
297 $self->paydate("$2-$1-01");
298 } elsif ( $2 > 97 ) { #should pry change to check for "this year"
299 $self->paydate("19$2-$1-01");
301 $self->paydate("20$2-$1-01");
305 if ( $self->payname eq '' ) {
306 $self->payname( $self->first. " ". $self->getfield('last') );
308 $self->payname =~ /^([\w \,\.\-\']+)$/
309 or return "Illegal billing name";
313 $self->tax =~ /^(Y?)$/ or return "Illegal tax";
316 $self->otaker(getotaker);
323 Returns all packages (see L<FS::cust_pkg>) for this customer.
329 qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
332 =item ncancelled_pkgs
334 Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
338 sub ncancelled_pkgs {
340 qsearch( 'cust_pkg', {
341 'custnum' => $self->custnum,
348 Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
349 conjunction with the collect method.
351 The only currently available option is `time', which bills the customer as if
352 it were that time. It is specified as a UNIX timestamp; see
353 L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
356 If there is an error, returns the error, otherwise returns false.
361 my( $self, %options ) = @_;
362 my $time = $options{'time'} || time;
367 local $SIG{HUP} = 'IGNORE';
368 local $SIG{INT} = 'IGNORE';
369 local $SIG{QUIT} = 'IGNORE';
370 local $SIG{TERM} = 'IGNORE';
371 local $SIG{TSTP} = 'IGNORE';
372 local $SIG{PIPE} = 'IGNORE';
374 # find the packages which are due for billing, find out how much they are
375 # & generate invoice database.
377 my( $total_setup, $total_recur ) = ( 0, 0 );
380 foreach my $cust_pkg (
381 qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
384 next if $cust_pkg->getfield('cancel');
386 #? to avoid use of uninitialized value errors... ?
387 $cust_pkg->setfield('bill', '')
388 unless defined($cust_pkg->bill);
390 my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
392 #so we don't modify cust_pkg record unnecessarily
393 my $cust_pkg_mod_flag = 0;
394 my %hash = $cust_pkg->hash;
395 my $old_cust_pkg = new FS::cust_pkg \%hash;
399 unless ( $cust_pkg->setup ) {
400 my $setup_prog = $part_pkg->getfield('setup');
402 #$cpt->permit(); #what is necessary?
403 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
404 $setup = $cpt->reval($setup_prog);
405 unless ( defined($setup) ) {
406 warn "Error reval-ing part_pkg->setup pkgpart ",
407 $part_pkg->pkgpart, ": $@";
409 $cust_pkg->setfield('setup',$time);
410 $cust_pkg_mod_flag=1;
417 if ( $part_pkg->getfield('freq') > 0 &&
418 ! $cust_pkg->getfield('susp') &&
419 ( $cust_pkg->getfield('bill') || 0 ) < $time
421 my $recur_prog = $part_pkg->getfield('recur');
423 #$cpt->permit(); #what is necessary?
424 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
425 $recur = $cpt->reval($recur_prog);
426 unless ( defined($recur) ) {
427 warn "Error reval-ing part_pkg->recur pkgpart ",
428 $part_pkg->pkgpart, ": $@";
430 #change this bit to use Date::Manip?
431 #$sdate=$cust_pkg->bill || time;
432 #$sdate=$cust_pkg->bill || $time;
433 $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
434 my ($sec,$min,$hour,$mday,$mon,$year) =
435 (localtime($sdate) )[0,1,2,3,4,5];
436 $mon += $part_pkg->getfield('freq');
437 until ( $mon < 12 ) { $mon -= 12; $year++; }
438 $cust_pkg->setfield('bill',
439 timelocal($sec,$min,$hour,$mday,$mon,$year));
440 $cust_pkg_mod_flag = 1;
444 warn "setup is undefinded" unless defined($setup);
445 warn "recur is undefinded" unless defined($recur);
446 warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
448 if ( $cust_pkg_mod_flag ) {
449 $error=$cust_pkg->replace($old_cust_pkg);
450 if ( $error ) { #just in case
451 warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
453 $setup = sprintf( "%.2f", $setup );
454 $recur = sprintf( "%.2f", $recur );
455 my $cust_bill_pkg = new FS::cust_bill_pkg ({
456 'pkgnum' => $cust_pkg->pkgnum,
460 'edate' => $cust_pkg->bill,
462 push @cust_bill_pkg, $cust_bill_pkg;
463 $total_setup += $setup;
464 $total_recur += $recur;
470 my $charged = sprintf( "%.2f", $total_setup + $total_recur );
472 return '' if scalar(@cust_bill_pkg) == 0;
474 unless ( $self->getfield('tax') =~ /Y/i
475 || $self->getfield('payby') eq 'COMP'
477 my $cust_main_county = qsearchs('cust_main_county',{
478 'state' => $self->state,
479 'county' => $self->county,
480 'country' => $self->country,
482 my $tax = sprintf( "%.2f",
483 $charged * ( $cust_main_county->getfield('tax') / 100 )
485 $charged = sprintf( "%.2f", $charged+$tax );
487 my $cust_bill_pkg = new FS::cust_bill_pkg ({
494 push @cust_bill_pkg, $cust_bill_pkg;
497 my $cust_bill = new FS::cust_bill ( {
498 'custnum' => $self->getfield('custnum'),
500 'charged' => $charged,
502 $error = $cust_bill->insert;
503 #shouldn't happen, but how else to handle this? (wrap me in eval, to catch
505 die "Error creating cust_bill record: $error!\n",
506 "Check updated but unbilled packages for customer", $self->custnum, "\n"
509 my $invnum = $cust_bill->invnum;
511 foreach $cust_bill_pkg ( @cust_bill_pkg ) {
512 $cust_bill_pkg->setfield( 'invnum', $invnum );
513 $error = $cust_bill_pkg->insert;
514 #shouldn't happen, but how else tohandle this?
515 die "Error creating cust_bill_pkg record: $error!\n",
516 "Check incomplete invoice ", $invnum, "\n"
523 =item collect OPTIONS
525 (Attempt to) collect money for this customer's outstanding invoices (see
526 L<FS::cust_bill>). Usually used after the bill method.
528 Depending on the value of `payby', this may print an invoice (`BILL'), charge
529 a credit card (`CARD'), or just add any necessary (pseudo-)payment (`COMP').
531 If there is an error, returns the error, otherwise returns false.
533 Currently available options are:
535 invoice_time - Use this time when deciding when to print invoices and
536 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>
537 for conversion functions.
539 batch_card - Set this true to batch cards (see L<cust_pay_batch>). By
540 default, cards are processed immediately, which will generate an error if
541 CyberCash is not installed.
543 report_badcard - Set this true if you want bad card transactions to
544 return an error. By default, they don't.
549 my( $self, %options ) = @_;
550 my $invoice_time = $options{'invoice_time'} || time;
552 my $total_owed = $self->balance;
553 return '' unless $total_owed > 0; #redundant?????
556 local $SIG{HUP} = 'IGNORE';
557 local $SIG{INT} = 'IGNORE';
558 local $SIG{QUIT} = 'IGNORE';
559 local $SIG{TERM} = 'IGNORE';
560 local $SIG{TSTP} = 'IGNORE';
561 local $SIG{PIPE} = 'IGNORE';
563 foreach my $cust_bill (
564 qsearch('cust_bill', { 'custnum' => $self->custnum, } )
567 #this has to be before next's
568 my $amount = sprintf( "%.2f", $total_owed < $cust_bill->owed
572 $total_owed = sprintf( "%.2f", $total_owed - $amount );
574 next unless $cust_bill->owed > 0;
576 next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
578 #warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)";
580 next unless $amount > 0;
582 if ( $self->payby eq 'BILL' ) {
585 my $since = $invoice_time - ( $cust_bill->_date || 0 );
586 #warn "$invoice_time ", $cust_bill->_date, " $since";
587 if ( $since >= 0 #don't print future invoices
588 && ( $cust_bill->printed * 2592000 ) <= $since
591 #my @print_text = $cust_bill->print_text; #( date )
592 my @invoicing_list = $self->invoicing_list;
593 if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
594 my $header = new Mail::Header ( [
595 "From: $invoice_from",
596 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
597 "Sender: $invoice_from",
598 "Reply-To: $invoice_from",
599 "Date: ". time2str("%a, %d %b %Y %X %z", time),
602 my $message = new Mail::Internet (
604 'Body' => [ $cust_bill->print_text ], #( date)
606 $message->smtpsend or die "Can't send invoice email!"; #die? warn?
608 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
609 open(LPR, "|$lpr") or die "Can't open pipe to $lpr: $!";
610 print LPR $cust_bill->print_text; #( date )
612 or die $! ? "Error closing $lpr: $!"
613 : "Exit status $? from $lpr";
616 my %hash = $cust_bill->hash;
618 my $new_cust_bill = new FS::cust_bill(\%hash);
619 my $error = $new_cust_bill->replace($cust_bill);
620 warn "Error updating $cust_bill->printed: $error" if $error;
624 } elsif ( $self->payby eq 'COMP' ) {
625 my $cust_pay = new FS::cust_pay ( {
626 'invnum' => $cust_bill->invnum,
630 'payinfo' => $self->payinfo,
633 my $error = $cust_pay->insert;
634 return 'Error COMPing invnum #' . $cust_bill->invnum .
635 ':' . $error if $error;
637 } elsif ( $self->payby eq 'CARD' ) {
639 if ( $options{'batch_card'} ne 'yes' ) {
641 return "Real time card processing not enabled!" unless $processor;
643 if ( $processor =~ /^cybercash/ ) {
645 #fix exp. date for cybercash
646 $self->paydate =~ /^(\d+)\/\d*(\d{2})$/;
649 my $paybatch = $cust_bill->invnum.
650 '-' . time2str("%y%m%d%H%M%S", time);
652 my $payname = $self->payname ||
653 $self->getfield('first'). ' '. $self->getfield('last');
655 my $address = $self->address1;
656 $address .= ", ". $self->address2 if $self->address2;
658 my $country = 'USA' if $self->country eq 'US';
660 my @full_xaction = ( $xaction,
661 'Order-ID' => $paybatch,
662 'Amount' => "usd $amount",
663 'Card-Number' => $self->getfield('payinfo'),
664 'Card-Name' => $payname,
665 'Card-Address' => $address,
666 'Card-City' => $self->getfield('city'),
667 'Card-State' => $self->getfield('state'),
668 'Card-Zip' => $self->getfield('zip'),
669 'Card-Country' => $country,
674 if ( $processor eq 'cybercash2' ) {
675 $^W=0; #CCLib isn't -w safe, ugh!
676 %result = &CCLib::sendmserver(@full_xaction);
678 } elsif ( $processor eq 'cybercash3.2' ) {
679 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
681 return "Unkonwn real-time processor $processor\n";
684 #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
685 #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
686 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
687 my $cust_pay = new FS::cust_pay ( {
688 'invnum' => $cust_bill->invnum,
692 'payinfo' => $self->payinfo,
693 'paybatch' => "$processor:$paybatch",
695 my $error = $cust_pay->insert;
696 return 'Error applying payment, invnum #' .
697 $cust_bill->invnum. ':'. $error if $error;
698 } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
699 || $options{'report_badcard'} ) {
700 return 'Cybercash error, invnum #' .
701 $cust_bill->invnum. ':'. $result{'MErrMsg'};
707 return "Unkonwn real-time processor $processor\n";
712 my $cust_pay_batch = new FS::Record ('cust_pay_batch', {
713 'invnum' => $cust_bill->getfield('invnum'),
714 'custnum' => $self->getfield('custnum'),
715 'last' => $self->getfield('last'),
716 'first' => $self->getfield('first'),
717 'address1' => $self->getfield('address1'),
718 'address2' => $self->getfield('address2'),
719 'city' => $self->getfield('city'),
720 'state' => $self->getfield('state'),
721 'zip' => $self->getfield('zip'),
722 'country' => $self->getfield('country'),
724 'cardnum' => $self->getfield('payinfo'),
725 'exp' => $self->getfield('paydate'),
726 'payname' => $self->getfield('payname'),
729 my $error = $cust_pay_batch->insert;
730 return "Error adding to cust_pay_batch: $error" if $error;
735 return "Unknown payment type ". $self->payby;
749 Returns the total owed for this customer on all invoices
750 (see L<FS::cust_bill>).
757 foreach my $cust_bill ( qsearch('cust_bill', {
758 'custnum' => $self->custnum,
760 $total_bill += $cust_bill->owed;
762 sprintf( "%.2f", $total_bill );
767 Returns the total credits (see L<FS::cust_credit>) for this customer.
773 my $total_credit = 0;
774 foreach my $cust_credit ( qsearch('cust_credit', {
775 'custnum' => $self->custnum,
777 $total_credit += $cust_credit->credited;
779 sprintf( "%.2f", $total_credit );
784 Returns the balance for this customer (total owed minus total credited).
790 sprintf( "%.2f", $self->total_owed - $self->total_credited );
793 =item invoicing_list [ ARRAYREF ]
795 If an arguement is given, sets these email addresses as invoice recipients
796 (see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
797 (except as warnings), so use check_invoicing_list first.
799 Returns a list of email addresses (with svcnum entries expanded).
801 Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
802 check it without disturbing anything by passing nothing.
804 This interface may change in the future.
809 my( $self, $arrayref ) = @_;
811 my @cust_main_invoice =
812 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
813 foreach my $cust_main_invoice ( @cust_main_invoice ) {
814 #warn $cust_main_invoice->destnum;
815 unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
816 #warn $cust_main_invoice->destnum;
817 my $error = $cust_main_invoice->delete;
818 warn $error if $error;
822 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
823 foreach my $address ( @{$arrayref} ) {
824 unless ( grep { $address eq $_->address } @cust_main_invoice ) {
825 my $cust_main_invoice = new FS::cust_main_invoice ( {
826 'custnum' => $self->custnum,
829 my $error = $cust_main_invoice->insert;
830 warn $error if $error;
835 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
838 =item check_invoicing_list ARRAYREF
840 Checks these arguements as valid input for the invoicing_list method. If there
841 is an error, returns the error, otherwise returns false.
845 sub check_invoicing_list {
846 my( $self, $arrayref ) = @_;
847 foreach my $address ( @{$arrayref} ) {
848 my $cust_main_invoice = new FS::cust_main_invoice ( {
849 'custnum' => $self->custnum,
852 my $error = $self->custnum
853 ? $cust_main_invoice->check
854 : $cust_main_invoice->checkdest
856 return $error if $error;
865 $Id: cust_main.pm,v 1.11 1999-02-23 08:09:27 ivan Exp $
871 Bill and collect options should probably be passed as references instead of a
874 CyberCash v2 forces us to define some variables in package main.
876 There should probably be a configuration file with a list of allowed credit
879 CyberCash is the only processor.
881 No multiple currency support (probably a larger project than just this module).
885 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
886 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
887 L<FS::cust_main_county>, L<FS::cust_main_invoice>,
888 L<FS::UID>, schema.html from the base documentation.
892 ivan@voicenet.com 97-jul-28
894 Changed to standard Business::CreditCard
896 EXPORT_OK FS::Record's hfields
897 removed unique calls and locking (not needed here now)
898 wrapped the (now) optional fields in if statements in sub check (notyetdone!)
899 ivan@sisd.com 97-nov-12
901 updated paydate with SQL-type date info ivan@sisd.com 98-mar-5
903 Added export of datasrc from UID.pm for Pg6.3
904 changed 'day' to 'daytime' because Pg6.3 reserves the day word
905 bmccane@maxbaud.net 98-apr-3
907 in ->create, s/svc_acct/cust_main/, now it should actually eliminate the
908 warnings it was meant to ivan@sisd.com 98-jul-16
910 don't require a phone number and allow '/' in company names
911 ivan@sisd.com 98-jul-18
913 use ut_ and rewrite &check, &*_pkgs ivan@sisd.com 98-sep-5
915 pod, merge with FS::Bill (about time!), total_owed, total_credited and balance
916 methods, cleaned collect method, source modifications no longer necessary to
917 enable cybercash, cybercash v3 support, don't need to import
918 FS::UID::{datasrc,checkruid} ivan@sisd.com 98-sep-19-21
920 $Log: cust_main.pm,v $
921 Revision 1.11 1999-02-23 08:09:27 ivan
922 beginnings of one-screen new customer entry and some other miscellania
924 Revision 1.10 1999/01/25 12:26:09 ivan
925 yet more mod_perl stuff
927 Revision 1.9 1999/01/18 09:22:41 ivan
928 changes to track email addresses for email invoicing
930 Revision 1.8 1998/12/29 11:59:39 ivan
931 mostly properly OO, some work still to be done with svc_ stuff
933 Revision 1.7 1998/12/16 09:58:52 ivan
934 library support for editing email invoice destinations (not in sub collect yet)
936 Revision 1.6 1998/11/18 09:01:42 ivan
939 Revision 1.5 1998/11/15 11:23:14 ivan
940 use FS::table_name for all searches to eliminate warnings,
941 emit state/county when they don't match
943 Revision 1.4 1998/11/15 05:30:48 ivan
944 bugfix for new config layout
946 Revision 1.3 1998/11/13 09:56:54 ivan
947 change configuration file layout to support multiple distinct databases (with
948 own set of config files, export, etc.)
950 Revision 1.2 1998/11/07 10:24:25 ivan
951 don't use depriciated FS::Bill and FS::Invoice, other miscellania