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!)
269 my($description,$amount);
273 my($l,@address)=(0,'','','','','','','');
276 ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
277 ? " (P.O. #". $cust_main->payinfo. ")"
281 $address[$l++]=$cust_main->company if $cust_main->company;
282 $address[$l++]=$cust_main->address1;
283 $address[$l++]=$cust_main->address2 if $cust_main->address2;
284 $address[$l++]=$cust_main->city. ", ". $cust_main->state. " ".
286 $address[$l++]=$cust_main->country unless $cust_main->country eq 'US';
289 foreach ( @pr_cust_bill ) {
291 "Previous Balance, Invoice #". $_->invnum.
292 " (". time2str("%x",$_->_date). ")",
293 '$'. sprintf("%10.2f",$_->owed)
297 push @buf,('','-----------');
298 push @buf,('Total Previous Balance','$' . sprintf("%10.2f",$pr_total ) );
303 foreach ( $self->cust_bill_pkg ) {
307 my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
308 my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
309 my($pkg)=$part_pkg->pkg;
311 if ( $_->setup != 0 ) {
312 push @buf, ( "$pkg Setup",'$' . sprintf("%10.2f",$_->setup) );
313 push @buf, map { " ". $_->[0]. ": ". $_->[1], '' } $cust_pkg->labels;
316 if ( $_->recur != 0 ) {
318 "$pkg (" . time2str("%x",$_->sdate) . " - " .
319 time2str("%x",$_->edate) . ")",
320 '$' . sprintf("%10.2f",$_->recur)
322 push @buf, map { " ". $_->[0]. ": ". $_->[1], '' } $cust_pkg->labels;
326 push @buf,("Tax",'$' . sprintf("%10.2f",$_->setup) )
331 push @buf,('','-----------');
332 push @buf,('Total New Charges',
333 '$' . sprintf("%10.2f",$self->charged) );
336 push @buf,('','-----------');
337 push @buf,('Total Charges',
338 '$' . sprintf("%10.2f",$self->charged + $pr_total) );
342 foreach ( @cr_cust_credit ) {
344 "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
345 '$' . sprintf("%10.2f",$_->credited)
349 #get & print payments
350 foreach ( $self->cust_pay ) {
352 "Payment received ". time2str("%x",$_->_date ),
353 '$' . sprintf("%10.2f",$_->paid )
358 push @buf,('','-----------');
359 push @buf,('Balance Due','$' .
360 sprintf("%10.2f",$self->owed + $pr_total - $cr_total ) );
364 my $tot_lines = 50; #should be configurable
366 my $tot_pages = int( scalar(@buf) / ( 2 * ( $tot_lines - 17 ) ) );
367 $tot_pages++ if scalar(@buf) % ( 2 * ( $tot_lines - 17 ) );
373 my @header = &header(
374 $page, $tot_pages, $self->_date, $self->invnum, @address
376 push @collect, @header;
377 $lines -= scalar(@header);
379 while ( $lines-- && @buf ) {
380 $description=shift(@buf);
382 push @collect, myswrite($description, $amount);
387 push @collect, myswrite('', '');
392 sub header { #17 lines
393 my ( $page, $tot_pages, $date, $invnum, @address ) = @_ ;
394 push @address, '', '', '', '';
401 $i. substr("Page $page of $tot_pages".' 'x10, 0, 20).
402 time2str("%x", $date ). " FS-". $invnum,
410 splice @address, 0, 7;
412 return map $_. "\n", @return;
417 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<
420 formline( $format, @_ );
430 $Id: cust_bill.pm,v 1.7 1999-02-09 09:55:05 ivan Exp $
436 print_text formatting (and some logic :/) is in source, but needs to be
437 slurped in from a file. Also number of lines ($=).
439 missing print_ps for a nice postscript copy (maybe HylaFAX-cover-page-style
440 or something similar so the look can be completely customized?)
444 L<FS::Record>, L<FS::cust_main>, L<FS::cust_pay>, L<FS::cust_bill_pkg>,
445 L<FS::cust_credit>, schema.html from the base documentation.
449 ivan@voicenet.com 97-jul-1
451 small fix for new API ivan@sisd.com 98-mar-14
453 charges can be negative ivan@sisd.com 98-jul-13
455 pod, ingegrate with FS::Invoice ivan@sisd.com 98-sep-20
457 $Log: cust_bill.pm,v $
458 Revision 1.7 1999-02-09 09:55:05 ivan
459 invoices show line items for each service in a package (see the label method
462 Revision 1.6 1999/01/25 12:26:07 ivan
463 yet more mod_perl stuff
465 Revision 1.5 1999/01/18 21:58:03 ivan
466 esthetic: eq and ne were used in a few places instead of == and !=
468 Revision 1.4 1998/12/29 11:59:36 ivan
469 mostly properly OO, some work still to be done with svc_ stuff
471 Revision 1.3 1998/11/13 09:56:53 ivan
472 change configuration file layout to support multiple distinct databases (with
473 own set of config files, export, etc.)
475 Revision 1.2 1998/11/07 10:24:24 ivan
476 don't use depriciated FS::Bill and FS::Invoice, other miscellania