From: ivan Date: Tue, 22 Sep 1998 02:57:14 +0000 (+0000) Subject: Initial revision X-Git-Tag: freeside_1_2_0~254 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=4478d24bd8f66aaff7e2f304d3c2fe2cc1d3bf04 Initial revision --- diff --git a/site_perl/agent.pm b/site_perl/agent.pm new file mode 100644 index 000000000..7fc370ed0 --- /dev/null +++ b/site_perl/agent.pm @@ -0,0 +1,166 @@ +package FS::agent; + +use strict; +use vars qw(@ISA @EXPORT_OK); +use Exporter; +use FS::Record qw(fields qsearch qsearchs); + +@ISA = qw(FS::Record Exporter); +@EXPORT_OK = qw(fields); + +=head1 NAME + +FS::agent - Object methods for agent records + +=head1 SYNOPSIS + + use FS::agent; + + $record = create FS::agent \%hash; + $record = create FS::agent { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::agent object represents an agent. Every customer has an agent. Agents +can be used to track things like resellers or salespeople. FS::agent inherits +from FS::Record. The following fields are currently supported: + +=over 4 + +=item agemtnum - primary key (assigned automatically for new agents) + +=item agent - Text name of this agent + +=item typenum - Agent type. See L + +=item prog - For future use. + +=item freq - For future use. + +=back + +=head1 METHODS + +=over 4 + +=item create HASHREF + +Creates a new agent. To add the agent to the database, see L<"insert">. + +=cut + +sub create { + my($proto,$hashref)=@_; + + #now in FS::Record::new + #my($field); + #foreach $field (fields('agent')) { + # $hashref->{$field}='' unless defined $hashref->{$field}; + #} + + $proto->new('agent',$hashref); +} + +=item insert + +Adds this agent to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +sub insert { + my($self)=@_; + + $self->check or + $self->add; +} + +=item delete + +Deletes this agent from the database. Only agents with no customers can be +deleted. If there is an error, returns the error, otherwise returns false. + +=cut + +sub delete { + my($self)=@_; + return "Can't delete an agent with customers!" + if qsearch('cust_main',{'agentnum' => $self->agentnum}); + $self->del; +} + +=item replace OLD_RECORD + +Replaces OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +sub replace { + my($new,$old)=@_; + return "(Old) Not an agent record!" unless $old->table eq "agent"; + return "Can't change agentnum!" + unless $old->getfield('agentnum') eq $new->getfield('agentnum'); + $new->check or + $new->rep($old); +} + +=item check + +Checks all fields to make sure this is a valid agent. If there is an error, +returns the error, otherwise returns false. Called by the insert and replace +methods. + +=cut + +sub check { + my($self)=@_; + return "Not a agent record!" unless $self->table eq "agent"; + + my($error)= + $self->ut_numbern('agentnum') + or $self->ut_text('agent') + or $self->ut_number('typenum') + or $self->ut_numbern('freq') + or $self->ut_textn('prog') + ; + return $error if $error; + + return "Unknown typenum!" + unless qsearchs('agent_type',{'typenum'=> $self->getfield('typenum') }); + + ''; + +} + +=back + +=head1 BUGS + +It doesn't properly override FS::Record yet. + +=head1 SEE ALSO + +L, L, L, schema.html from the base +documentation. + +=head1 HISTORY + +Class dealing with agent (resellers) + +ivan@sisd.com 97-nov-13, 97-dec-10 + +pod, added check in ->delete ivan@sisd.com 98-sep-22 + +=cut + +1; + diff --git a/site_perl/cust_bill.pm b/site_perl/cust_bill.pm new file mode 100644 index 000000000..00234519a --- /dev/null +++ b/site_perl/cust_bill.pm @@ -0,0 +1,495 @@ +package FS::cust_bill; + +use strict; +use vars qw(@ISA $conf $add1 $add2 $add3 $add4); +use Exporter; +use Date::Format; +use FS::Record qw(fields qsearch qsearchs); + +@ISA = qw(FS::Record Exporter); + +$conf = new FS::Conf; + +($add1,$add2,$add3,$add4) = $conf->config('address'); + +=head1 NAME + +FS::cust_bill - Object methods for cust_bill records + +=head1 SYNOPSIS + + use FS::cust_bill; + + $record = create FS::cust_bill \%hash; + $record = create FS::cust_bill { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + + ( $total_previous_balance, @previous_cust_bill ) = $record->previous; + + @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg; + + ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit; + + @cust_pay_objects = $cust_bill->cust_pay; + + @lines = $cust_bill->print_text; + @lines = $cust_bill->print_text $time; + +=head1 DESCRIPTION + +An FS::cust_bill object represents an invoice. FS::cust_bill inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item invnum - primary key (assigned automatically for new invoices) + +=item custnum - customer (see L) + +=item _date - specified as a UNIX timestamp; see L. Also see +L and L for conversion functions. + +=item charged - amount of this invoice + +=item owed - amount still outstanding on this invoice, which is charged minus +all payments (see L). + +=item printed - how many times this invoice has been printed automatically +(see L). + +=back + +=head1 METHODS + +=over 4 + +=item create HASHREF + +Creates a new invoice. To add the invoice to the database, see L<"insert">. +Invoices are normally created by calling the bill method of a customer object +(see L). + +=cut + +sub create { + my($proto,$hashref)=@_; + + #now in FS::Record::new + #my($field); + #foreach $field (fields('cust_bill')) { + # $hashref->{$field}='' unless defined $hashref->{$field}; + #} + + $proto->new('cust_bill',$hashref); +} + +=item insert + +Adds this invoice to the database ("Posts" the invoice). If there is an error, +returns the error, otherwise returns false. + +When adding new invoices, owed must be charged (or null, in which case it is +automatically set to charged). + +=cut + +sub insert { + my($self)=@_; + + $self->setfield('owed',$self->charged) if $self->owed eq ''; + return "owed != charged!" + unless $self->owed == $self->charged; + + $self->check or + $self->add; +} + +=item delete + +Currently unimplemented. I don't remove invoices because there would then be +no record you ever posted this invoice (which is bad, no?) + +=cut + +sub delete { + return "Can't remove invoice!" + #my($self)=@_; + #$self->del; +} + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +Only owed and printed may be changed. Owed is normally updated by creating and +inserting a payment (see L). Printed is normally updated by +calling the collect method of a customer object (see L). + +=cut + +sub replace { + my($new,$old)=@_; + return "(Old) Not a cust_bill record!" unless $old->table eq "cust_bill"; + return "Can't change invnum!" + unless $old->getfield('invnum') eq $new->getfield('invnum'); + return "Can't change custnum!" + unless $old->getfield('custnum') eq $new->getfield('custnum'); + return "Can't change _date!" + unless $old->getfield('_date') eq $new->getfield('_date'); + return "Can't change charged!" + unless $old->getfield('charged') eq $new->getfield('charged'); + return "(New) owed can't be > (new) charged!" + if $new->getfield('owed') > $new->getfield('charged'); + + $new->check or + $new->rep($old); +} + +=item check + +Checks all fields to make sure this is a valid invoice. If there is an error, +returns the error, otherwise returns false. Called by the insert and replace +methods. + +=cut + +sub check { + my($self)=@_; + return "Not a cust_bill record!" unless $self->table eq "cust_bill"; + my($recref) = $self->hashref; + + $recref->{invnum} =~ /^(\d*)$/ or return "Illegal invnum"; + $recref->{invnum} = $1; + + $recref->{custnum} =~ /^(\d+)$/ or return "Illegal custnum"; + $recref->{custnum} = $1; + return "Unknown customer" + unless qsearchs('cust_main',{'custnum'=>$recref->{custnum}}); + + $recref->{_date} =~ /^(\d*)$/ or return "Illegal date"; + $recref->{_date} = $recref->{_date} ? $1 : time; + + #$recref->{charged} =~ /^(\d+(\.\d\d)?)$/ or return "Illegal charged"; + $recref->{charged} =~ /^(\-?\d+(\.\d\d)?)$/ or return "Illegal charged"; + $recref->{charged} = $1; + + $recref->{owed} =~ /^(\-?\d+(\.\d\d)?)$/ or return "Illegal owed"; + $recref->{owed} = $1; + + $recref->{printed} =~ /^(\d*)$/ or return "Illegal printed"; + $recref->{printed} = $1 || '0'; + + ''; #no error +} + +=item previous + +Returns a list consisting of the total previous balance for this customer, +followed by the previous outstanding invoices (as FS::cust_bill objects also). + +=cut + +sub previous { + my($self)=@_; + my($total)=0; + my(@cust_bill) = sort { $a->_date <=> $b->_date } + grep { $_->owed != 0 && $_->_date < $self->_date } + qsearch('cust_bill',{ 'custnum' => $self->custnum } ) + ; + foreach (@cust_bill) { $total += $_->owed; } + $total, @cust_bill; +} + +=item cust_bill_pkg + +Returns the line items (see L) for this invoice. + +=cut + +sub cust_bill_pkg { + my($self)=@_; + qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } ); +} + +=item cust_credit + +Returns a list consisting of the total previous credited (see +L) for this customer, followed by the previous outstanding +credits (FS::cust_credit objects). + +=cut + +sub cust_credit { + my($self)=@_; + my($total)=0; + my(@cust_credit) = sort { $a->_date <=> $b->date } + grep { $_->credited != 0 && $_->_date < $self->_date } + qsearch('cust_credit', { 'custnum' => $self->custnum } ) + ; + foreach (@cust_credit) { $total += $_->credited; } + $total, @cust_credit; +} + +=item cust_pay + +Returns all payments (see L) for this invoice. + +=cut + +sub cust_pay { + my($self)=@_; + sort { $a->_date <=> $b->date } + qsearch( 'cust_pay', { 'invnum' => $self->invnum } ) + ; +} + +=item print_text [TIME]; + +Returns an ASCII invoice, as a list of lines. + +TIME an optional value used to control the printing of overdue messages. The +default is now. It isn't the date of the invoice; that's the `_date' field. +It is specified as a UNIX timestamp; see L. Also see +L and L for conversion functions. + +=cut + +sub print_text { + + my($self,$today)=@_; + $today ||= time; + my($invnum)=$self->invnum; + my($cust_main) = qsearchs('cust_main', + { 'custnum', $self->custnum } ); + $cust_main->setfield('payname', + $cust_main->first. ' '. $cust_main->getfield('last') + ) unless $cust_main->payname; + + my($pr_total,@pr_cust_bill) = $self->previous; #previous balance + my($cr_total,@cr_cust_credit) = $self->cust_credit; #credits + my($balance_due) = $self->owed + $pr_total - $cr_total; + + #overdue? + my($overdue) = ( + $balance_due > 0 + && $today > $self->_date + && $self->printed > 1 + ); + + #printing bits here + + local($SIG{CHLD}) = sub { wait() }; + $|=1; + my($pid)=open(CHILD,"-|"); + die "Can't fork: $!" unless defined($pid); + + if ($pid) { #parent + my(@collect)=; + close CHILD; + return @collect; + } else { #child + + my($description,$amount); + my(@buf); + + #define format stuff + $%=0; + $= = 35; + local($^L) = <company if $cust_main->company; + $address[$l++]=$cust_main->address1; + $address[$l++]=$cust_main->address2 if $cust_main->address2; + $address[$l++]=$cust_main->city. ", ". $cust_main->state. " ". + $cust_main->zip; + $address[$l++]=$cust_main->country unless $cust_main->country eq 'US'; + + #previous balance + foreach ( @pr_cust_bill ) { + push @buf, ( + "Previous Balance, Invoice #". $_->invnum. + " (". time2str("%x",$_->_date). ")", + '$'. sprintf("%10.2f",$_->owed) + ); + } + if (@pr_cust_bill) { + push @buf,('','-----------'); + push @buf,('Total Previous Balance','$' . sprintf("%10.2f",$pr_total ) ); + push @buf,('',''); + } + + #new charges + foreach ( $self->cust_bill_pkg ) { + + if ( $_->pkgnum ) { + + my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } ); + my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart}); + my($pkg)=$part_pkg->pkg; + + push @buf, ( "$pkg Setup",'$' . sprintf("%10.2f",$_->setup) ) + if $_->setup != 0; + push @buf, ( + "$pkg (" . time2str("%x",$_->sdate) . " - " . + time2str("%x",$_->edate) . ")", + '$' . sprintf("%10.2f",$_->recur) + ) if $_->recur != 0; + + } else { #pkgnum Tax + push @buf,("Tax",'$' . sprintf("%10.2f",$_->setup) ) + if $_->setup != 0; + } + } + + push @buf,('','-----------'); + push @buf,('Total New Charges', + '$' . sprintf("%10.2f",$self->charged) ); + push @buf,('',''); + + push @buf,('','-----------'); + push @buf,('Total Charges', + '$' . sprintf("%10.2f",$self->charged + $pr_total) ); + push @buf,('',''); + + #credits + foreach ( @cr_cust_credit ) { + push @buf,( + "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")", + '$' . sprintf("%10.2f",$_->credited) + ); + } + + #get & print payments + foreach ( $self->cust_pay ) { + push @buf,( + "Payment received ". time2str("%x",$_->_date ), + '$' . sprintf("%10.2f",$_->paid ) + ); + } + + #balance due + push @buf,('','-----------'); + push @buf,('Balance Due','$' . + sprintf("%10.2f",$self->owed + $pr_total - $cr_total ) ); + + #now print + + my($tot_pages)=int(scalar(@buf)/30); #15 lines, 2 values per line + $tot_pages++ if scalar(@buf) % 30; + + while (@buf) { + $description=shift(@buf); + $amount=shift(@buf); + write; + } + ($description,$amount)=('',''); + write while ( $- ); + print $^L; + + exit; #kid + + format STDOUT_TOP = + + @||||||||||||||||||| + "Invoice" + @||||||||||||||||||| @<<<<<<< @<<<<<<<<<<< +{ + ( $tot_pages != 1 ) ? "Page $% of $tot_pages" : '', + time2str("%x",( $self->_date )), "FS-$invnum" +} + + +@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +$add1 +@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +$add2 +@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +$add3 +@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +$add4 + + @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +{ $cust_main->payname, + ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo ) + ? "P.O. #". $cust_main->payinfo : '' +} + @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +$address[0],'' + @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +$address[1],$overdue ? "* This invoice is now PAST DUE! *" : '' + @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +$address[2],$overdue ? " Please forward payment promptly " : '' + @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +$address[3],$overdue ? "to avoid interruption of service." : '' + @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +$address[4],'' + + + +. + + format STDOUT = + @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<< + $description,$amount +. + + } #endchild + +} + +=back + +=head1 BUGS + +The delete method. + +It doesn't properly override FS::Record yet. + +print_text formatting (and some logic :/) is in source as a format declaration, +which needs to be slurped in from a file. the fork is rather kludgy as well. +It could be cleaned with swrite from man perlform, and the picture could be +put in a /var/spool/freeside/conf file. Also number of lines ($=). + +missing print_ps for a nice postscript copy (maybe HylaFAX-cover-page-style +or something similar so the look can be completely customized?) + +There is an off-by-one error in print_text which causes a visual error: "Page 1 +of 2" printed on some single-page invoices? + +=head1 SEE ALSO + +L, L, L, L, +L, schema.html from the base documentation. + +=head1 HISTORY + +ivan@voicenet.com 97-jul-1 + +small fix for new API ivan@sisd.com 98-mar-14 + +charges can be negative ivan@sisd.com 98-jul-13 + +pod, ingegrate with FS::Invoice ivan@sisd.com 98-sep-20 + +=cut + +1; +