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 @EXPORT_OK $conf $lpr $processor $xaction $E_NoErr);
15 use Business::CreditCard;
16 use FS::UID qw(getotaker);
17 use FS::Record qw(fields hfields qsearchs qsearch);
20 use FS::cust_bill_pkg;
23 use FS::cust_pay_batch;
24 use FS::part_referral;
25 use FS::cust_main_county;
27 use FS::cust_main_invoice;
29 @ISA = qw(FS::Record Exporter);
30 @EXPORT_OK = qw(hfields);
32 #ask FS::UID to run this stuff for us later
33 $FS::UID::callback{'FS::cust_main'} = sub {
35 $lpr = $conf->config('lpr');
37 if ( $conf->exists('cybercash3.2') ) {
39 #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
40 require CCMckDirectLib3_2;
42 require CCMckErrno3_2;
43 #qw(MCKGetErrorMessage $E_NoErr);
44 import CCMckErrno3_2 qw($E_NoErr);
47 ($merchant_conf,$xaction)= $conf->config('cybercash3.2');
48 my $status = &CCMckLib3_2::InitConfig($merchant_conf);
49 if ( $status != $E_NoErr ) {
50 warn "CCMckLib3_2::InitConfig error:\n";
51 foreach my $key (keys %CCMckLib3_2::Config) {
52 warn " $key => $CCMckLib3_2::Config{$key}\n"
54 my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
55 die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
57 $processor='cybercash3.2';
58 } elsif ( $conf->exists('cybercash2') ) {
61 ( $main::paymentserverhost,
62 $main::paymentserverport,
63 $main::paymentserversecret,
65 ) = $conf->config('cybercash2');
66 $processor='cybercash2';
72 FS::cust_main - Object methods for cust_main records
78 $record = create FS::cust_main \%hash;
79 $record = create FS::cust_main { 'column' => 'value' };
81 $error = $record->insert;
83 $error = $new_record->replace($old_record);
85 $error = $record->delete;
87 $error = $record->check;
89 @cust_pkg = $record->all_pkgs;
91 @cust_pkg = $record->ncancelled_pkgs;
93 $error = $record->bill;
94 $error = $record->bill %options;
95 $error = $record->bill 'time' => $time;
97 $error = $record->collect;
98 $error = $record->collect %options;
99 $error = $record->collect 'invoice_time' => $time,
100 'batch_card' => 'yes',
101 'report_badcard' => 'yes',
106 An FS::cust_main object represents a customer. FS::cust_main inherits from
107 FS::Record. The following fields are currently supported:
111 =item custnum - primary key (assigned automatically for new customers)
113 =item agentnum - agent (see L<FS::agent>)
115 =item refnum - referral (see L<FS::part_referral>)
121 =item ss - social security number (optional)
123 =item company - (optional)
127 =item address2 - (optional)
131 =item county - (optional, see L<FS::cust_main_county>)
133 =item state - (see L<FS::cust_main_county>)
137 =item country - (see L<FS::cust_main_county>)
139 =item daytime - phone (optional)
141 =item night - phone (optional)
143 =item payby - `CARD' (credit cards), `BILL' (billing), or `COMP' (free)
145 =item payinfo - card number, P.O.#, or comp issuer (4-8 lowercase alphanumerics; think username)
147 =item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
149 =item payname - name on card or billing name
151 =item tax - tax exempt, empty or `Y'
153 =item otaker - order taker (assigned automatically, see L<FS::UID>)
163 Creates a new customer. To add the customer to the database, see L<"insert">.
165 Note that this stores the hash reference, not a distinct copy of the hash it
166 points to. You can ask the object for a copy with the I<hash> method.
171 my($proto,$hashref)=@_;
173 #now in FS::Record::new
175 #foreach $field (fields('cust_main')) {
176 # $hashref->{$field}='' unless defined $hashref->{$field};
179 $proto->new('cust_main',$hashref);
184 Adds this customer to the database. If there is an error, returns the error,
185 otherwise returns false.
192 #no callbacks in check, only data checks
193 #local $SIG{HUP} = 'IGNORE';
194 #local $SIG{INT} = 'IGNORE';
195 #local $SIG{QUIT} = 'IGNORE';
196 #local $SIG{TERM} = 'IGNORE';
197 #local $SIG{TSTP} = 'IGNORE';
205 Currently unimplemented. Maybe cancel all of this customer's
208 I don't remove the customer record in the database because there would then
209 be no record the customer ever existed (which is bad, no?)
213 # Usage: $error = $record -> delete;
215 return "Can't (yet?) delete customers.";
221 =item replace OLD_RECORD
223 Replaces the OLD_RECORD with this one in the database. If there is an error,
224 returns the error, otherwise returns false.
230 return "(Old) Not a cust_main record!" unless $old->table eq "cust_main";
231 return "Can't change custnum!"
232 unless $old->getfield('custnum') eq $new->getfield('custnum');
239 Checks all fields to make sure this is a valid customer record. If there is
240 an error, returns the error, otherwise returns false. Called by the insert
248 return "Not a cust_main record!" unless $self->table eq "cust_main";
251 $self->ut_number('agentnum')
252 || $self->ut_number('refnum')
253 || $self->ut_textn('company')
254 || $self->ut_text('address1')
255 || $self->ut_textn('address2')
256 || $self->ut_text('city')
257 || $self->ut_textn('county')
258 || $self->ut_text('state')
259 || $self->ut_phonen('daytime')
260 || $self->ut_phonen('night')
261 || $self->ut_phonen('fax')
263 return $error if $error;
265 return "Unknown agent"
266 unless qsearchs('agent',{'agentnum'=>$self->agentnum});
268 return "Unknown referral"
269 unless qsearchs('part_referral',{'refnum'=>$self->refnum});
271 $self->getfield('last') =~ /^([\w \,\.\-\']+)$/ or return "Illegal last name";
272 $self->setfield('last',$1);
274 $self->first =~ /^([\w \,\.\-\']+)$/ or return "Illegal first name";
277 if ( $self->ss eq '' ) {
282 $ss =~ /^(\d{3})(\d{2})(\d{4})$/
283 or return "Illegal social security number";
284 $self->ss("$1-$2-$3");
287 $self->country =~ /^(\w\w)$/ or return "Illegal country";
289 unless ( qsearchs('cust_main_county', {
290 'country' => $self->country,
293 return "Unknown state/county/country"
294 #" state ". $self->state. " county ". $self->county. " country ". $self->country
295 unless qsearchs('cust_main_county',{
296 'state' => $self->state,
297 'county' => $self->county,
298 'country' => $self->country,
302 $self->zip =~ /^([\w\-]{10})$/ or return "Illegal zip";
305 $self->payby =~ /^(CARD|BILL|COMP)$/ or return "Illegal payby";
308 if ( $self->payby eq 'CARD' ) {
310 my $payinfo = $self->payinfo;
312 $payinfo =~ /^(\d{13,16})$/
313 or return "Illegal credit card number";
315 $self->payinfo($payinfo);
316 validate($payinfo) or return "Illegal credit card number";
317 my $type = cardtype($payinfo);
318 return "Unknown credit card type"
319 unless ( $type =~ /^VISA/ ||
320 $type =~ /^MasterCard/ ||
321 $type =~ /^American Express/ ||
322 $type =~ /^Discover/ );
324 } elsif ( $self->payby eq 'BILL' ) {
326 $self->payinfo =~ /^([\w \-]*)$/ or return "Illegal P.O. number";
329 } elsif ( $self->payby eq 'COMP' ) {
331 $self->payinfo =~ /^(\w{2,8})$/ or return "Illegal comp account issuer";
336 if ( $self->paydate eq '' ) {
337 return "Expriation date required" unless $self->payby eq 'BILL';
340 $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/
341 or return "Illegal expiration date";
342 if ( length($2) == 4 ) {
343 $self->paydate("$2-$1-01");
344 } elsif ( $2 > 97 ) { #should pry change to check for "this year"
345 $self->paydate("19$2-$1-01");
347 $self->paydate("20$2-$1-01");
351 if ( $self->payname eq '' ) {
352 $self->payname( $self->first. " ". $self->getfield('last') );
354 $self->payname =~ /^([\w \,\.\-\']+)$/
355 or return "Illegal billing name";
359 $self->tax =~ /^(Y?)$/ or return "Illegal tax";
362 $self->otaker(getotaker);
369 Returns all packages (see L<FS::cust_pkg>) for this customer.
375 qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
378 =item ncancelled_pkgs
380 Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
384 sub ncancelled_pkgs {
386 qsearch( 'cust_pkg', {
387 'custnum' => $self->custnum,
394 Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
395 conjunction with the collect method.
397 The only currently available option is `time', which bills the customer as if
398 it were that time. It is specified as a UNIX timestamp; see
399 L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
402 If there is an error, returns the error, otherwise returns false.
407 my($self,%options)=@_;
408 my($time) = $options{'time'} || $^T;
413 local $SIG{HUP} = 'IGNORE';
414 local $SIG{INT} = 'IGNORE';
415 local $SIG{QUIT} = 'IGNORE';
416 local $SIG{TERM} = 'IGNORE';
417 local $SIG{TSTP} = 'IGNORE';
419 # find the packages which are due for billing, find out how much they are
420 # & generate invoice database.
422 my($total_setup,$total_recur)=(0,0);
428 qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
431 bless($cust_pkg,"FS::cust_pkg");
433 next if ( $cust_pkg->getfield('cancel') );
435 #? to avoid use of uninitialized value errors... ?
436 $cust_pkg->setfield('bill', '')
437 unless defined($cust_pkg->bill);
440 qsearchs('part_pkg',{'pkgpart'=> $cust_pkg->pkgpart } );
442 #so we don't modify cust_pkg record unnecessarily
443 my($cust_pkg_mod_flag)=0;
444 my(%hash)=$cust_pkg->hash;
445 my($old_cust_pkg)=create FS::cust_pkg(\%hash);
449 unless ( $cust_pkg->setup ) {
450 my($setup_prog)=$part_pkg->getfield('setup');
452 #$cpt->permit(); #what is necessary?
453 $cpt->share(qw($cust_pkg)); #can $cpt now use $cust_pkg methods?
454 $setup = $cpt->reval($setup_prog);
455 unless ( defined($setup) ) {
456 warn "Error reval-ing part_pkg->setup pkgpart ",
457 $part_pkg->pkgpart, ": $@";
459 $cust_pkg->setfield('setup',$time);
460 $cust_pkg_mod_flag=1;
467 if ( $part_pkg->getfield('freq') > 0 &&
468 ! $cust_pkg->getfield('susp') &&
469 ( $cust_pkg->getfield('bill') || 0 ) < $time
471 my($recur_prog)=$part_pkg->getfield('recur');
473 #$cpt->permit(); #what is necessary?
474 $cpt->share(qw($cust_pkg)); #can $cpt now use $cust_pkg methods?
475 $recur = $cpt->reval($recur_prog);
476 unless ( defined($recur) ) {
477 warn "Error reval-ing part_pkg->recur pkgpart ",
478 $part_pkg->pkgpart, ": $@";
480 #change this bit to use Date::Manip?
481 #$sdate=$cust_pkg->bill || time;
482 #$sdate=$cust_pkg->bill || $time;
483 $sdate=$cust_pkg->bill || $cust_pkg->setup || $time;
484 my($sec,$min,$hour,$mday,$mon,$year)=
485 (localtime($sdate) )[0,1,2,3,4,5];
486 $mon += $part_pkg->getfield('freq');
487 until ( $mon < 12 ) { $mon -= 12; $year++; }
488 $cust_pkg->setfield('bill',timelocal($sec,$min,$hour,$mday,$mon,$year));
489 $cust_pkg_mod_flag=1;
493 warn "setup is undefinded" unless defined($setup);
494 warn "recur is undefinded" unless defined($recur);
495 warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
497 if ($cust_pkg_mod_flag) {
498 $error=$cust_pkg->replace($old_cust_pkg);
500 warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
503 $setup=sprintf("%.2f",$setup);
504 $recur=sprintf("%.2f",$recur);
505 my($cust_bill_pkg)=create FS::cust_bill_pkg ({
506 'pkgnum' => $cust_pkg->pkgnum,
510 'edate' => $cust_pkg->bill,
512 push @cust_bill_pkg, $cust_bill_pkg;
513 $total_setup += $setup;
514 $total_recur += $recur;
520 my($charged)=sprintf("%.2f",$total_setup + $total_recur);
522 return '' if scalar(@cust_bill_pkg) == 0;
524 unless ( $self->getfield('tax') eq 'Y' ||
525 $self->getfield('tax') eq 'y' ||
526 $self->getfield('payby') eq 'COMP'
528 my($cust_main_county) = qsearchs('cust_main_county',{
529 'county' => $self->getfield('county'),
530 'state' => $self->getfield('state'),
532 my($tax) = sprintf("%.2f",
533 $charged * ( $cust_main_county->getfield('tax') / 100 )
535 $charged = sprintf("%.2f",$charged+$tax);
537 my($cust_bill_pkg)=create FS::cust_bill_pkg ({
544 push @cust_bill_pkg, $cust_bill_pkg;
547 my($cust_bill) = create FS::cust_bill ( {
548 'custnum' => $self->getfield('custnum'),
550 'charged' => $charged,
552 $error=$cust_bill->insert;
553 #shouldn't happen, but how else to handle this? (wrap me in eval, to catch
555 die "Error creating cust_bill record: $error!\n",
556 "Check updated but unbilled packages for customer", $self->custnum, "\n"
559 my($invnum)=$cust_bill->invnum;
561 foreach $cust_bill_pkg ( @cust_bill_pkg ) {
562 $cust_bill_pkg->setfield('invnum',$invnum);
563 $error=$cust_bill_pkg->insert;
564 #shouldn't happen, but how else tohandle this?
565 die "Error creating cust_bill_pkg record: $error!\n",
566 "Check incomplete invoice ", $invnum, "\n"
573 =item collect OPTIONS
575 (Attempt to) collect money for this customer's outstanding invoices (see
576 L<FS::cust_bill>). Usually used after the bill method.
578 Depending on the value of `payby', this may print an invoice (`BILL'), charge
579 a credit card (`CARD'), or just add any necessary (pseudo-)payment (`COMP').
581 If there is an error, returns the error, otherwise returns false.
583 Currently available options are:
585 invoice_time - Use this time when deciding when to print invoices and
586 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>
587 for conversion functions.
589 batch_card - Set this true to batch cards (see L<cust_pay_batch>). By
590 default, cards are processed immediately, which will generate an error if
591 CyberCash is not installed.
593 report_badcard - Set this true if you want bad card transactions to
594 return an error. By default, they don't.
599 my($self,%options)=@_;
600 my($invoice_time) = $options{'invoice_time'} || $^T;
602 my($total_owed) = $self->balance;
603 return '' unless $total_owed > 0; #redundant?????
606 local $SIG{HUP} = 'IGNORE';
607 local $SIG{INT} = 'IGNORE';
608 local $SIG{QUIT} = 'IGNORE';
609 local $SIG{TERM} = 'IGNORE';
610 local $SIG{TSTP} = 'IGNORE';
612 foreach my $cust_bill ( qsearch('cust_bill', {
613 'custnum' => $self->getfield('custnum'),
616 bless($cust_bill,"FS::cust_bill");
618 #this has to be before next's
619 my($amount) = sprintf("%.2f", $total_owed < $cust_bill->owed
623 $total_owed = sprintf("%.2f",$total_owed-$amount);
625 next unless $cust_bill->owed > 0;
627 next if qsearchs('cust_pay_batch',{'invnum'=> $cust_bill->invnum });
629 #warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)";
631 next unless $amount > 0;
633 if ( $self->getfield('payby') eq 'BILL' ) {
636 my($since)=$invoice_time - ( $cust_bill->_date || 0 );
637 #warn "$invoice_time ", $cust_bill->_date, " $since";
638 if ( $since >= 0 #don't print future invoices
639 && ( $cust_bill->printed * 2592000 ) <= $since
642 open(LPR,"|$lpr") or die "Can't open $lpr: $!";
643 print LPR $cust_bill->print_text; #( date )
645 or die $! ? "Error closing $lpr: $!"
646 : "Exit status $? from $lpr";
648 my(%hash)=$cust_bill->hash;
650 my($new_cust_bill)=create FS::cust_bill(\%hash);
651 my($error)=$new_cust_bill->replace($cust_bill);
653 warn "Error updating $cust_bill->printed: $error";
658 } elsif ( $self->getfield('payby') eq 'COMP' ) {
659 my($cust_pay) = create FS::cust_pay ( {
660 'invnum' => $cust_bill->getfield('invnum'),
664 'payinfo' => $self->getfield('payinfo'),
667 my($error)=$cust_pay->insert;
668 return 'Error COMPing invnum #' . $cust_bill->getfield('invnum') .
669 ':' . $error if $error;
670 } elsif ( $self->getfield('payby') eq 'CARD' ) {
672 if ( $options{'batch_card'} ne 'yes' ) {
674 return "Real time card processing not enabled!" unless $processor;
676 if ( $processor =~ /cybercash/ ) {
678 #fix exp. date for cybercash
679 $self->getfield('paydate') =~ /^(\d+)\/\d*(\d{2})$/;
682 my($paybatch)= $cust_bill->getfield('invnum') .
683 '-' . time2str("%y%m%d%H%M%S",time);
685 my($payname)= $self->getfield('payname') ||
686 $self->getfield('first') . ' ' .$self->getfield('last');
688 my($address)= $self->getfield('address1');
689 $address .= ", " . $self->getfield('address2')
690 if $self->getfield('address2');
692 my($country) = $self->getfield('country') eq 'US' ?
693 'USA' : $self->getfield('country');
695 my(@full_xaction)=($xaction,
696 'Order-ID' => $paybatch,
697 'Amount' => "usd $amount",
698 'Card-Number' => $self->getfield('payinfo'),
699 'Card-Name' => $payname,
700 'Card-Address' => $address,
701 'Card-City' => $self->getfield('city'),
702 'Card-State' => $self->getfield('state'),
703 'Card-Zip' => $self->getfield('zip'),
704 'Card-Country' => $country,
709 if ( $processor eq 'cybercash2' ) {
710 $^W=0; #CCLib isn't -w safe, ugh!
711 %result = &CCLib::sendmserver(@full_xaction);
713 } elsif ( $processor eq 'cybercash3.2' ) {
714 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
716 return "Unkonwn real-time processor $processor\n";
719 #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
720 #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
721 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
722 my($cust_pay) = create FS::cust_pay ( {
723 'invnum' => $cust_bill->getfield('invnum'),
727 'payinfo' => $self->getfield('payinfo'),
728 'paybatch' => "$processor:$paybatch",
730 my($error)=$cust_pay->insert;
731 return 'Error applying payment, invnum #' .
732 $cust_bill->getfield('invnum') . ':' . $error if $error;
733 } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
734 || $options{'report_badcard'} ) {
735 return 'Cybercash error, invnum #' .
736 $cust_bill->getfield('invnum') . ':' . $result{'MErrMsg'};
742 return "Unkonwn real-time processor $processor\n";
747 # my($cust_pay_batch) = create FS::cust_pay_batch ( {
748 my($cust_pay_batch) = new FS::Record ('cust_pay_batch', {
749 'invnum' => $cust_bill->getfield('invnum'),
750 'custnum' => $self->getfield('custnum'),
751 'last' => $self->getfield('last'),
752 'first' => $self->getfield('first'),
753 'address1' => $self->getfield('address1'),
754 'address2' => $self->getfield('address2'),
755 'city' => $self->getfield('city'),
756 'state' => $self->getfield('state'),
757 'zip' => $self->getfield('zip'),
758 'country' => $self->getfield('country'),
760 'cardnum' => $self->getfield('payinfo'),
761 'exp' => $self->getfield('paydate'),
762 'payname' => $self->getfield('payname'),
765 # my($error)=$cust_pay_batch->insert;
766 my($error)=$cust_pay_batch->add;
767 return "Error adding to cust_pay_batch: $error" if $error;
772 return "Unknown payment type ".$self->getfield('payby');
782 Returns the total owed for this customer on all invoices
783 (see L<FS::cust_bill>).
791 foreach $cust_bill ( qsearch('cust_bill', {
792 'custnum' => $self->getfield('custnum'),
794 $total_bill += $cust_bill->getfield('owed');
796 sprintf("%.2f",$total_bill);
801 Returns the total credits (see L<FS::cust_credit>) for this customer.
807 my($total_credit) = 0;
809 foreach $cust_credit ( qsearch('cust_credit', {
810 'custnum' => $self->getfield('custnum'),
812 $total_credit += $cust_credit->getfield('credited');
814 sprintf("%.2f",$total_credit);
819 Returns the balance for this customer (total owed minus total credited).
825 sprintf("%.2f",$self->total_owed - $self->total_credited);
828 =item invoicing_list [ ITEM, ITEM, ... ]
830 If arguements are given, sets these email addresses as invoice recipients
831 (see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
832 (except as warnings), so use check_invoicing_list first.
834 Returns a list of email addresses (with svcnum entries expanded).
839 my($self, @addresses) = @_;
841 my @cust_main_invoice =
842 qsearch('cust_main_invoice', { 'custnum' => $self->custnum } );
843 foreach my $cust_main_invoice ( @cust_main_invoice ) {
844 unless ( grep { $cust_main_invoice->address eq $_ } @addresses ) {
845 $cust_main_invoice->delete;
849 qsearch('cust_main_invoice', { 'custnum' => $self->custnum } );
850 foreach my $address ( @addresses ) {
851 unless ( grep { $address eq $_->address } @cust_main_invoice ) {
852 my $cust_main_invoice = create FS::cust_main_invoice (
853 'custnum' => $self->custnum,
856 my $error = $cust_main_invoice->insert;
857 warn $error if $error;
862 qsearch('cust_main_invoice', { 'custnum' => $self->custnum } );
865 =item check_invoicing_list ITEM, ITEM
867 Checks these arguements as valid input for the invoicing_list method. If there
868 is an error, returns the error, otherwise returns false.
872 sub check_invoicing_list {
873 my($self, @addresses) = @_;
874 foreach my $address ( @addresses ) {
875 my $cust_main_invoice = create FS::cust_main_invoice (
876 'custnum' => $self->custnum,
879 my $error = $cust_main_invoice->check;
880 return $error if $error;
891 It doesn't properly override FS::Record yet.
893 hfields should be removed.
895 Bill and collect options should probably be passed as references instead of a
898 CyberCash v2 forces us to define some variables in package main.
902 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
903 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
904 L<FS::cust_main_county>, L<FS::cust_main_invoice>,
905 L<FS::UID>, schema.html from the base documentation.
909 ivan@voicenet.com 97-jul-28
911 Changed to standard Business::CreditCard
913 EXPORT_OK FS::Record's hfields
914 removed unique calls and locking (not needed here now)
915 wrapped the (now) optional fields in if statements in sub check (notyetdone!)
916 ivan@sisd.com 97-nov-12
918 updated paydate with SQL-type date info ivan@sisd.com 98-mar-5
920 Added export of datasrc from UID.pm for Pg6.3
921 changed 'day' to 'daytime' because Pg6.3 reserves the day word
922 bmccane@maxbaud.net 98-apr-3
924 in ->create, s/svc_acct/cust_main/, now it should actually eliminate the
925 warnings it was meant to ivan@sisd.com 98-jul-16
927 don't require a phone number and allow '/' in company names
928 ivan@sisd.com 98-jul-18
930 use ut_ and rewrite &check, &*_pkgs ivan@sisd.com 98-sep-5
932 pod, merge with FS::Bill (about time!), total_owed, total_credited and balance
933 methods, cleaned collect method, source modifications no longer necessary to
934 enable cybercash, cybercash v3 support, don't need to import
935 FS::UID::{datasrc,checkruid} ivan@sisd.com 98-sep-19-21
937 $Log: cust_main.pm,v $
938 Revision 1.7 1998-12-16 09:58:52 ivan
939 library support for editing email invoice destinations (not in sub collect yet)
941 Revision 1.6 1998/11/18 09:01:42 ivan
944 Revision 1.5 1998/11/15 11:23:14 ivan
945 use FS::table_name for all searches to eliminate warnings,
946 emit state/county when they don't match
948 Revision 1.4 1998/11/15 05:30:48 ivan
949 bugfix for new config layout
951 Revision 1.3 1998/11/13 09:56:54 ivan
952 change configuration file layout to support multiple distinct databases (with
953 own set of config files, export, etc.)
955 Revision 1.2 1998/11/07 10:24:25 ivan
956 don't use depriciated FS::Bill and FS::Invoice, other miscellania