4 use vars qw(@ISA $conf $add1 $add2 $add3 $add4);
7 use FS::Record qw(fields qsearch qsearchs);
9 @ISA = qw(FS::Record Exporter);
11 #ask FS::UID to run this stuff for us later
12 $FS::UID::callback{'FS::cust_bill'} = sub {
14 ( $add1, $add2, $add3, $add4 ) = $conf->config('address');
19 FS::cust_bill - Object methods for cust_bill records
25 $record = create FS::cust_bill \%hash;
26 $record = create FS::cust_bill { 'column' => 'value' };
28 $error = $record->insert;
30 $error = $new_record->replace($old_record);
32 $error = $record->delete;
34 $error = $record->check;
36 ( $total_previous_balance, @previous_cust_bill ) = $record->previous;
38 @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg;
40 ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit;
42 @cust_pay_objects = $cust_bill->cust_pay;
44 @lines = $cust_bill->print_text;
45 @lines = $cust_bill->print_text $time;
49 An FS::cust_bill object represents an invoice. FS::cust_bill inherits from
50 FS::Record. The following fields are currently supported:
54 =item invnum - primary key (assigned automatically for new invoices)
56 =item custnum - customer (see L<FS::cust_main>)
58 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
59 L<Time::Local> and L<Date::Parse> for conversion functions.
61 =item charged - amount of this invoice
63 =item owed - amount still outstanding on this invoice, which is charged minus
64 all payments (see L<FS::cust_pay>).
66 =item printed - how many times this invoice has been printed automatically
67 (see L<FS::cust_main/"collect">).
77 Creates a new invoice. To add the invoice to the database, see L<"insert">.
78 Invoices are normally created by calling the bill method of a customer object
79 (see L<FS::cust_main>).
84 my($proto,$hashref)=@_;
86 #now in FS::Record::new
88 #foreach $field (fields('cust_bill')) {
89 # $hashref->{$field}='' unless defined $hashref->{$field};
92 $proto->new('cust_bill',$hashref);
97 Adds this invoice to the database ("Posts" the invoice). If there is an error,
98 returns the error, otherwise returns false.
100 When adding new invoices, owed must be charged (or null, in which case it is
101 automatically set to charged).
108 $self->setfield('owed',$self->charged) if $self->owed eq '';
109 return "owed != charged!"
110 unless $self->owed == $self->charged;
118 Currently unimplemented. I don't remove invoices because there would then be
119 no record you ever posted this invoice (which is bad, no?)
124 return "Can't remove invoice!"
129 =item replace OLD_RECORD
131 Replaces the OLD_RECORD with this one in the database. If there is an error,
132 returns the error, otherwise returns false.
134 Only owed and printed may be changed. Owed is normally updated by creating and
135 inserting a payment (see L<FS::cust_pay>). Printed is normally updated by
136 calling the collect method of a customer object (see L<FS::cust_main>).
142 return "(Old) Not a cust_bill record!" unless $old->table eq "cust_bill";
143 return "Can't change invnum!"
144 unless $old->getfield('invnum') eq $new->getfield('invnum');
145 return "Can't change custnum!"
146 unless $old->getfield('custnum') eq $new->getfield('custnum');
147 return "Can't change _date!"
148 unless $old->getfield('_date') eq $new->getfield('_date');
149 return "Can't change charged!"
150 unless $old->getfield('charged') eq $new->getfield('charged');
151 return "(New) owed can't be > (new) charged!"
152 if $new->getfield('owed') > $new->getfield('charged');
160 Checks all fields to make sure this is a valid invoice. If there is an error,
161 returns the error, otherwise returns false. Called by the insert and replace
168 return "Not a cust_bill record!" unless $self->table eq "cust_bill";
169 my($recref) = $self->hashref;
171 $recref->{invnum} =~ /^(\d*)$/ or return "Illegal invnum";
172 $recref->{invnum} = $1;
174 $recref->{custnum} =~ /^(\d+)$/ or return "Illegal custnum";
175 $recref->{custnum} = $1;
176 return "Unknown customer"
177 unless qsearchs('cust_main',{'custnum'=>$recref->{custnum}});
179 $recref->{_date} =~ /^(\d*)$/ or return "Illegal date";
180 $recref->{_date} = $recref->{_date} ? $1 : time;
182 #$recref->{charged} =~ /^(\d+(\.\d\d)?)$/ or return "Illegal charged";
183 $recref->{charged} =~ /^(\-?\d+(\.\d\d)?)$/ or return "Illegal charged";
184 $recref->{charged} = $1;
186 $recref->{owed} =~ /^(\-?\d+(\.\d\d)?)$/ or return "Illegal owed";
187 $recref->{owed} = $1;
189 $recref->{printed} =~ /^(\d*)$/ or return "Illegal printed";
190 $recref->{printed} = $1 || '0';
197 Returns a list consisting of the total previous balance for this customer,
198 followed by the previous outstanding invoices (as FS::cust_bill objects also).
205 my(@cust_bill) = sort { $a->_date <=> $b->_date }
206 grep { $_->owed != 0 && $_->_date < $self->_date }
207 qsearch('cust_bill',{ 'custnum' => $self->custnum } )
209 foreach (@cust_bill) { $total += $_->owed; }
215 Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
221 qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
226 Returns a list consisting of the total previous credited (see
227 L<FS::cust_credit>) for this customer, followed by the previous outstanding
228 credits (FS::cust_credit objects).
235 my(@cust_credit) = sort { $a->_date <=> $b->date }
236 grep { $_->credited != 0 && $_->_date < $self->_date }
237 qsearch('cust_credit', { 'custnum' => $self->custnum } )
239 foreach (@cust_credit) { $total += $_->credited; }
240 $total, @cust_credit;
245 Returns all payments (see L<FS::cust_pay>) for this invoice.
251 sort { $a->_date <=> $b->date }
252 qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
256 =item print_text [TIME];
258 Returns an ASCII invoice, as a list of lines.
260 TIME an optional value used to control the printing of overdue messages. The
261 default is now. It isn't the date of the invoice; that's the `_date' field.
262 It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
263 L<Time::Local> and L<Date::Parse> for conversion functions.
271 my($invnum)=$self->invnum;
272 my($cust_main) = qsearchs('cust_main',
273 { 'custnum', $self->custnum } );
274 $cust_main->setfield('payname',
275 $cust_main->first. ' '. $cust_main->getfield('last')
276 ) unless $cust_main->payname;
278 my($pr_total,@pr_cust_bill) = $self->previous; #previous balance
279 my($cr_total,@cr_cust_credit) = $self->cust_credit; #credits
280 my($balance_due) = $self->owed + $pr_total - $cr_total;
285 && $today > $self->_date
286 && $self->printed > 1
291 local($SIG{CHLD}) = sub { wait() };
293 my($pid)=open(CHILD,"-|");
294 die "Can't fork: $!" unless defined($pid);
297 my(@collect)=<CHILD>;
302 my($description,$amount);
319 my($l,@address)=(0,'','','','','');
320 $address[$l++]=$cust_main->company if $cust_main->company;
321 $address[$l++]=$cust_main->address1;
322 $address[$l++]=$cust_main->address2 if $cust_main->address2;
323 $address[$l++]=$cust_main->city. ", ". $cust_main->state. " ".
325 $address[$l++]=$cust_main->country unless $cust_main->country eq 'US';
328 foreach ( @pr_cust_bill ) {
330 "Previous Balance, Invoice #". $_->invnum.
331 " (". time2str("%x",$_->_date). ")",
332 '$'. sprintf("%10.2f",$_->owed)
336 push @buf,('','-----------');
337 push @buf,('Total Previous Balance','$' . sprintf("%10.2f",$pr_total ) );
342 foreach ( $self->cust_bill_pkg ) {
346 my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
347 my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
348 my($pkg)=$part_pkg->pkg;
350 push @buf, ( "$pkg Setup",'$' . sprintf("%10.2f",$_->setup) )
353 "$pkg (" . time2str("%x",$_->sdate) . " - " .
354 time2str("%x",$_->edate) . ")",
355 '$' . sprintf("%10.2f",$_->recur)
359 push @buf,("Tax",'$' . sprintf("%10.2f",$_->setup) )
364 push @buf,('','-----------');
365 push @buf,('Total New Charges',
366 '$' . sprintf("%10.2f",$self->charged) );
369 push @buf,('','-----------');
370 push @buf,('Total Charges',
371 '$' . sprintf("%10.2f",$self->charged + $pr_total) );
375 foreach ( @cr_cust_credit ) {
377 "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
378 '$' . sprintf("%10.2f",$_->credited)
382 #get & print payments
383 foreach ( $self->cust_pay ) {
385 "Payment received ". time2str("%x",$_->_date ),
386 '$' . sprintf("%10.2f",$_->paid )
391 push @buf,('','-----------');
392 push @buf,('Balance Due','$' .
393 sprintf("%10.2f",$self->owed + $pr_total - $cr_total ) );
397 my($tot_pages)=int(scalar(@buf)/30); #15 lines, 2 values per line
398 $tot_pages++ if scalar(@buf) % 30;
401 $description=shift(@buf);
405 ($description,$amount)=('','');
415 @||||||||||||||||||| @<<<<<<< @<<<<<<<<<<<
417 ( $tot_pages != 1 ) ? "Page $% of $tot_pages" : '',
418 time2str("%x",( $self->_date )), "FS-$invnum"
422 @>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
424 @>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
426 @>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
428 @>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
431 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
432 { $cust_main->payname,
433 ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo )
434 ? "P.O. #". $cust_main->payinfo : ''
436 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
438 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
439 $address[1],$overdue ? "* This invoice is now PAST DUE! *" : ''
440 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
441 $address[2],$overdue ? " Please forward payment promptly " : ''
442 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
443 $address[3],$overdue ? "to avoid interruption of service." : ''
444 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
452 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<
466 It doesn't properly override FS::Record yet.
468 print_text formatting (and some logic :/) is in source as a format declaration,
469 which needs to be slurped in from a file. the fork is rather kludgy as well.
470 It could be cleaned with swrite from man perlform, and the picture could be
471 put in a /var/spool/freeside/conf file. Also number of lines ($=).
473 missing print_ps for a nice postscript copy (maybe HylaFAX-cover-page-style
474 or something similar so the look can be completely customized?)
476 There is an off-by-one error in print_text which causes a visual error: "Page 1
477 of 2" printed on some single-page invoices?
481 L<FS::Record>, L<FS::cust_main>, L<FS::cust_pay>, L<FS::cust_bill_pkg>,
482 L<FS::cust_credit>, schema.html from the base documentation.
486 ivan@voicenet.com 97-jul-1
488 small fix for new API ivan@sisd.com 98-mar-14
490 charges can be negative ivan@sisd.com 98-jul-13
492 pod, ingegrate with FS::Invoice ivan@sisd.com 98-sep-20
494 $Log: cust_bill.pm,v $
495 Revision 1.3 1998-11-13 09:56:53 ivan
496 change configuration file layout to support multiple distinct databases (with
497 own set of config files, export, etc.)
499 Revision 1.2 1998/11/07 10:24:24 ivan
500 don't use depriciated FS::Bill and FS::Invoice, other miscellania