4 use vars qw( @ISA $conf $add1 $add2 $add3 $add4 );
6 use FS::Record qw( qsearch qsearchs );
13 @ISA = qw( FS::Record );
15 #ask FS::UID to run this stuff for us later
16 $FS::UID::callback{'FS::cust_bill'} = sub {
18 ( $add1, $add2, $add3, $add4 ) = $conf->config('address');
23 FS::cust_bill - Object methods for cust_bill records
29 $record = new FS::cust_bill \%hash;
30 $record = new FS::cust_bill { 'column' => 'value' };
32 $error = $record->insert;
34 $error = $new_record->replace($old_record);
36 $error = $record->delete;
38 $error = $record->check;
40 ( $total_previous_balance, @previous_cust_bill ) = $record->previous;
42 @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg;
44 ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit;
46 @cust_pay_objects = $cust_bill->cust_pay;
48 @lines = $cust_bill->print_text;
49 @lines = $cust_bill->print_text $time;
53 An FS::cust_bill object represents an invoice. FS::cust_bill inherits from
54 FS::Record. The following fields are currently supported:
58 =item invnum - primary key (assigned automatically for new invoices)
60 =item custnum - customer (see L<FS::cust_main>)
62 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
63 L<Time::Local> and L<Date::Parse> for conversion functions.
65 =item charged - amount of this invoice
67 =item owed - amount still outstanding on this invoice, which is charged minus
68 all payments (see L<FS::cust_pay>).
70 =item printed - how many times this invoice has been printed automatically
71 (see L<FS::cust_main/"collect">).
81 Creates a new invoice. To add the invoice to the database, see L<"insert">.
82 Invoices are normally created by calling the bill method of a customer object
83 (see L<FS::cust_main>).
87 sub table { 'cust_bill'; }
91 Adds this invoice to the database ("Posts" the invoice). If there is an error,
92 returns the error, otherwise returns false.
94 When adding new invoices, owed must be charged (or null, in which case it is
95 automatically set to charged).
102 $self->owed( $self->charged ) if $self->owed eq '';
103 return "owed != charged!"
104 unless $self->owed == $self->charged;
106 $self->SUPER::insert;
111 Currently unimplemented. I don't remove invoices because there would then be
112 no record you ever posted this invoice (which is bad, no?)
117 return "Can't remove invoice!"
120 =item replace OLD_RECORD
122 Replaces the OLD_RECORD with this one in the database. If there is an error,
123 returns the error, otherwise returns false.
125 Only owed and printed may be changed. Owed is normally updated by creating and
126 inserting a payment (see L<FS::cust_pay>). Printed is normally updated by
127 calling the collect method of a customer object (see L<FS::cust_main>).
132 my( $new, $old ) = ( shift, shift );
133 return "Can't change custnum!" unless $old->custnum == $new->custnum;
134 #return "Can't change _date!" unless $old->_date eq $new->_date;
135 return "Can't change _date!" unless $old->_date == $new->_date;
136 return "Can't change charged!" unless $old->charged == $new->charged;
137 return "(New) owed can't be > (new) charged!" if $new->owed > $new->charged;
139 $new->SUPER::replace($old);
144 Checks all fields to make sure this is a valid invoice. If there is an error,
145 returns the error, otherwise returns false. Called by the insert and replace
154 $self->ut_numbern('invnum')
155 || $self->ut_number('custnum')
156 || $self->ut_numbern('_date')
157 || $self->ut_money('charged')
158 || $self->ut_money('owed')
159 || $self->ut_numbern('printed')
161 return $error if $error;
163 return "Unknown customer"
164 unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
166 $self->_date(time) unless $self->_date;
168 $self->printed(0) if $self->printed eq '';
175 Returns a list consisting of the total previous balance for this customer,
176 followed by the previous outstanding invoices (as FS::cust_bill objects also).
183 my @cust_bill = sort { $a->_date <=> $b->_date }
184 grep { $_->owed != 0 && $_->_date < $self->_date }
185 qsearch( 'cust_bill', { 'custnum' => $self->custnum } )
187 foreach ( @cust_bill ) { $total += $_->owed; }
193 Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
199 qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
204 Returns a list consisting of the total previous credited (see
205 L<FS::cust_credit>) for this customer, followed by the previous outstanding
206 credits (FS::cust_credit objects).
213 my @cust_credit = sort { $a->_date <=> $b->date }
214 grep { $_->credited != 0 && $_->_date < $self->_date }
215 qsearch('cust_credit', { 'custnum' => $self->custnum } )
217 foreach (@cust_credit) { $total += $_->credited; }
218 $total, @cust_credit;
223 Returns all payments (see L<FS::cust_pay>) for this invoice.
229 sort { $a->_date <=> $b->date }
230 qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
234 =item print_text [TIME];
236 Returns an ASCII invoice, as a list of lines.
238 TIME an optional value used to control the printing of overdue messages. The
239 default is now. It isn't the date of the invoice; that's the `_date' field.
240 It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
241 L<Time::Local> and L<Date::Parse> for conversion functions.
247 my( $self, $today ) = ( shift, shift );
249 my $invnum = $self->invnum;
250 my $cust_main = qsearchs('cust_main', { 'custnum', $self->custnum } );
251 $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
252 unless $cust_main->payname;
254 my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
255 my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
256 my $balance_due = $self->owed + $pr_total - $cr_total;
261 && $today > $self->_date
262 && $self->printed > 1
265 #printing bits here (yuck!)
267 local($SIG{CHLD}) = sub { wait() };
269 my($pid)=open(CHILD,"-|");
270 die "Can't fork: $!" unless defined($pid);
273 my(@collect)=<CHILD>;
278 my($description,$amount);
295 my($l,@address)=(0,'','','','','');
296 $address[$l++]=$cust_main->company if $cust_main->company;
297 $address[$l++]=$cust_main->address1;
298 $address[$l++]=$cust_main->address2 if $cust_main->address2;
299 $address[$l++]=$cust_main->city. ", ". $cust_main->state. " ".
301 $address[$l++]=$cust_main->country unless $cust_main->country eq 'US';
304 foreach ( @pr_cust_bill ) {
306 "Previous Balance, Invoice #". $_->invnum.
307 " (". time2str("%x",$_->_date). ")",
308 '$'. sprintf("%10.2f",$_->owed)
312 push @buf,('','-----------');
313 push @buf,('Total Previous Balance','$' . sprintf("%10.2f",$pr_total ) );
318 foreach ( $self->cust_bill_pkg ) {
322 my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
323 my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
324 my($pkg)=$part_pkg->pkg;
326 push @buf, ( "$pkg Setup",'$' . sprintf("%10.2f",$_->setup) )
329 "$pkg (" . time2str("%x",$_->sdate) . " - " .
330 time2str("%x",$_->edate) . ")",
331 '$' . sprintf("%10.2f",$_->recur)
335 push @buf,("Tax",'$' . sprintf("%10.2f",$_->setup) )
340 push @buf,('','-----------');
341 push @buf,('Total New Charges',
342 '$' . sprintf("%10.2f",$self->charged) );
345 push @buf,('','-----------');
346 push @buf,('Total Charges',
347 '$' . sprintf("%10.2f",$self->charged + $pr_total) );
351 foreach ( @cr_cust_credit ) {
353 "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
354 '$' . sprintf("%10.2f",$_->credited)
358 #get & print payments
359 foreach ( $self->cust_pay ) {
361 "Payment received ". time2str("%x",$_->_date ),
362 '$' . sprintf("%10.2f",$_->paid )
367 push @buf,('','-----------');
368 push @buf,('Balance Due','$' .
369 sprintf("%10.2f",$self->owed + $pr_total - $cr_total ) );
373 my($tot_pages)=int(scalar(@buf)/30); #15 lines, 2 values per line
374 $tot_pages++ if scalar(@buf) % 30;
377 $description=shift(@buf);
381 ($description,$amount)=('','');
391 @||||||||||||||||||| @<<<<<<< @<<<<<<<<<<<
393 ( $tot_pages != 1 ) ? "Page $% of $tot_pages" : '',
394 time2str("%x",( $self->_date )), "FS-$invnum"
398 @>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
400 @>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
402 @>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
404 @>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
407 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
408 { $cust_main->payname,
409 ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo )
410 ? "P.O. #". $cust_main->payinfo : ''
412 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
414 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
415 $address[1],$overdue ? "* This invoice is now PAST DUE! *" : ''
416 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
417 $address[2],$overdue ? " Please forward payment promptly " : ''
418 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
419 $address[3],$overdue ? "to avoid interruption of service." : ''
420 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
428 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<
440 $Id: cust_bill.pm,v 1.5 1999-01-18 21:58:03 ivan Exp $
446 print_text formatting (and some logic :/) is in source as a format declaration,
447 which needs to be slurped in from a file. the fork is rather kludgy as well.
448 It could be cleaned with swrite from man perlform, and the picture could be
449 put in a /var/spool/freeside/conf file. Also number of lines ($=).
451 missing print_ps for a nice postscript copy (maybe HylaFAX-cover-page-style
452 or something similar so the look can be completely customized?)
454 There is an off-by-one error in print_text which causes a visual error: "Page 1
455 of 2" printed on some single-page invoices?
459 L<FS::Record>, L<FS::cust_main>, L<FS::cust_pay>, L<FS::cust_bill_pkg>,
460 L<FS::cust_credit>, schema.html from the base documentation.
464 ivan@voicenet.com 97-jul-1
466 small fix for new API ivan@sisd.com 98-mar-14
468 charges can be negative ivan@sisd.com 98-jul-13
470 pod, ingegrate with FS::Invoice ivan@sisd.com 98-sep-20
472 $Log: cust_bill.pm,v $
473 Revision 1.5 1999-01-18 21:58:03 ivan
474 esthetic: eq and ne were used in a few places instead of == and !=
476 Revision 1.4 1998/12/29 11:59:36 ivan
477 mostly properly OO, some work still to be done with svc_ stuff
479 Revision 1.3 1998/11/13 09:56:53 ivan
480 change configuration file layout to support multiple distinct databases (with
481 own set of config files, export, etc.)
483 Revision 1.2 1998/11/07 10:24:24 ivan
484 don't use depriciated FS::Bill and FS::Invoice, other miscellania