4 use vars qw( @ISA $conf $invoice_template $money_char );
5 use vars qw( $invoice_lines @buf ); #yuck
8 use FS::Record qw( qsearch qsearchs );
10 use FS::cust_bill_pkg;
14 use FS::cust_credit_bill;
16 @ISA = qw( FS::Record );
18 #ask FS::UID to run this stuff for us later
19 $FS::UID::callback{'FS::cust_bill'} = sub {
23 $money_char = $conf->config('money_char') || '$';
25 my @invoice_template = $conf->config('invoice_template')
26 or die "cannot load config file invoice_template";
28 foreach ( grep /invoice_lines\(\d+\)/, @invoice_template ) { #kludgy
29 /invoice_lines\((\d+)\)/;
32 die "no invoice_lines() functions in template?" unless $invoice_lines;
33 $invoice_template = new Text::Template (
35 SOURCE => [ map "$_\n", @invoice_template ],
36 ) or die "can't create new Text::Template object: $Text::Template::ERROR";
37 $invoice_template->compile()
38 or die "can't compile template: $Text::Template::ERROR";
43 FS::cust_bill - Object methods for cust_bill records
49 $record = new FS::cust_bill \%hash;
50 $record = new FS::cust_bill { 'column' => 'value' };
52 $error = $record->insert;
54 $error = $new_record->replace($old_record);
56 $error = $record->delete;
58 $error = $record->check;
60 ( $total_previous_balance, @previous_cust_bill ) = $record->previous;
62 @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg;
64 ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit;
66 @cust_pay_objects = $cust_bill->cust_pay;
68 $tax_amount = $record->tax;
70 @lines = $cust_bill->print_text;
71 @lines = $cust_bill->print_text $time;
75 An FS::cust_bill object represents an invoice; a declaration that a customer
76 owes you money. The specific charges are itemized as B<cust_bill_pkg> records
77 (see L<FS::cust_bill_pkg>). FS::cust_bill inherits from FS::Record. The
78 following fields are currently supported:
82 =item invnum - primary key (assigned automatically for new invoices)
84 =item custnum - customer (see L<FS::cust_main>)
86 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
87 L<Time::Local> and L<Date::Parse> for conversion functions.
89 =item charged - amount of this invoice
91 =item printed - how many times this invoice has been printed automatically
92 (see L<FS::cust_main/"collect">).
94 =item closed - books closed flag, empty or `Y'
104 Creates a new invoice. To add the invoice to the database, see L<"insert">.
105 Invoices are normally created by calling the bill method of a customer object
106 (see L<FS::cust_main>).
110 sub table { 'cust_bill'; }
114 Adds this invoice to the database ("Posts" the invoice). If there is an error,
115 returns the error, otherwise returns false.
119 Currently unimplemented. I don't remove invoices because there would then be
120 no record you ever posted this invoice (which is bad, no?)
126 return "Can't delete closed invoice" if $self->closed =~ /^Y/i;
127 $self->SUPER::delete(@_);
130 =item replace OLD_RECORD
132 Replaces the OLD_RECORD with this one in the database. If there is an error,
133 returns the error, otherwise returns false.
135 Only printed may be changed. printed is normally updated by calling the
136 collect method of a customer object (see L<FS::cust_main>).
141 my( $new, $old ) = ( shift, shift );
142 return "Can't change custnum!" unless $old->custnum == $new->custnum;
143 #return "Can't change _date!" unless $old->_date eq $new->_date;
144 return "Can't change _date!" unless $old->_date == $new->_date;
145 return "Can't change charged!" unless $old->charged == $new->charged;
147 $new->SUPER::replace($old);
152 Checks all fields to make sure this is a valid invoice. If there is an error,
153 returns the error, otherwise returns false. Called by the insert and replace
162 $self->ut_numbern('invnum')
163 || $self->ut_number('custnum')
164 || $self->ut_numbern('_date')
165 || $self->ut_money('charged')
166 || $self->ut_numbern('printed')
167 || $self->ut_enum('closed', [ '', 'Y' ])
169 return $error if $error;
171 return "Unknown customer"
172 unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
174 $self->_date(time) unless $self->_date;
176 $self->printed(0) if $self->printed eq '';
183 Returns a list consisting of the total previous balance for this customer,
184 followed by the previous outstanding invoices (as FS::cust_bill objects also).
191 my @cust_bill = sort { $a->_date <=> $b->_date }
192 grep { $_->owed != 0 && $_->_date < $self->_date }
193 qsearch( 'cust_bill', { 'custnum' => $self->custnum } )
195 foreach ( @cust_bill ) { $total += $_->owed; }
201 Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
207 qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
212 Depreciated. See the cust_credited method.
214 #Returns a list consisting of the total previous credited (see
215 #L<FS::cust_credit>) and unapplied for this customer, followed by the previous
216 #outstanding credits (FS::cust_credit objects).
222 croak "FS::cust_bill->cust_credit depreciated; see ".
223 "FS::cust_bill->cust_credit_bill";
226 #my @cust_credit = sort { $a->_date <=> $b->_date }
227 # grep { $_->credited != 0 && $_->_date < $self->_date }
228 # qsearch('cust_credit', { 'custnum' => $self->custnum } )
230 #foreach (@cust_credit) { $total += $_->credited; }
231 #$total, @cust_credit;
236 Depreciated. See the cust_bill_pay method.
238 #Returns all payments (see L<FS::cust_pay>) for this invoice.
244 croak "FS::cust_bill->cust_pay depreciated; see FS::cust_bill->cust_bill_pay";
246 #sort { $a->_date <=> $b->_date }
247 # qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
253 Returns all payment applications (see L<FS::cust_bill_pay>) for this invoice.
259 sort { $a->_date <=> $b->_date }
260 qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum } );
265 Returns all applied credits (see L<FS::cust_credit_bill>) for this invoice.
271 sort { $a->_date <=> $b->_date }
272 qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum } )
278 Returns the tax amount (see L<FS::cust_bill_pkg>) for this invoice.
285 my @taxlines = qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum ,
287 foreach (@taxlines) { $total += $_->setup; }
293 Returns the amount owed (still outstanding) on this invoice, which is charged
294 minus all payment applications (see L<FS::cust_bill_pay>) and credit
295 applications (see L<FS::cust_credit_bill>).
301 my $balance = $self->charged;
302 $balance -= $_->amount foreach ( $self->cust_bill_pay );
303 $balance -= $_->amount foreach ( $self->cust_credited );
304 $balance = sprintf( "%.2f", $balance);
305 $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
309 =item print_text [TIME];
311 Returns an text invoice, as a list of lines.
313 TIME an optional value used to control the printing of overdue messages. The
314 default is now. It isn't the date of the invoice; that's the `_date' field.
315 It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
316 L<Time::Local> and L<Date::Parse> for conversion functions.
322 my( $self, $today ) = ( shift, shift );
324 # my $invnum = $self->invnum;
325 my $cust_main = qsearchs('cust_main', { 'custnum', $self->custnum } );
326 $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
327 unless $cust_main->payname;
329 my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
330 # my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
331 #my $balance_due = $self->owed + $pr_total - $cr_total;
332 my $balance_due = $self->owed + $pr_total;
335 #my($description,$amount);
339 foreach ( @pr_cust_bill ) {
341 "Previous Balance, Invoice #". $_->invnum.
342 " (". time2str("%x",$_->_date). ")",
343 $money_char. sprintf("%10.2f",$_->owed)
347 push @buf,['','-----------'];
348 push @buf,[ 'Total Previous Balance',
349 $money_char. sprintf("%10.2f",$pr_total ) ];
354 foreach ( $self->cust_bill_pkg ) {
358 my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
359 my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
360 my($pkg)=$part_pkg->pkg;
362 if ( $_->setup != 0 ) {
363 push @buf, [ "$pkg Setup", $money_char. sprintf("%10.2f",$_->setup) ];
365 map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
368 if ( $_->recur != 0 ) {
370 "$pkg (" . time2str("%x",$_->sdate) . " - " .
371 time2str("%x",$_->edate) . ")",
372 $money_char. sprintf("%10.2f",$_->recur)
375 map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
379 push @buf,["Tax", $money_char. sprintf("%10.2f",$_->setup) ]
384 push @buf,['','-----------'];
385 push @buf,['Total New Charges',
386 $money_char. sprintf("%10.2f",$self->charged) ];
389 push @buf,['','-----------'];
390 push @buf,['Total Charges',
391 $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
395 foreach ( $self->cust_credited ) {
397 #something more elaborate if $_->amount ne $_->cust_credit->credited ?
399 my $reason = substr($_->cust_credit->reason,0,32);
400 $reason .= '...' if length($reason) < length($_->cust_credit->reason);
401 $reason = " ($reason) " if $reason;
403 "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")".
405 $money_char. sprintf("%10.2f",$_->amount)
408 #foreach ( @cr_cust_credit ) {
410 # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
411 # $money_char. sprintf("%10.2f",$_->credited)
415 #get & print payments
416 foreach ( $self->cust_bill_pay ) {
418 #something more elaborate if $_->amount ne ->cust_pay->paid ?
421 "Payment received ". time2str("%x",$_->cust_pay->_date ),
422 $money_char. sprintf("%10.2f",$_->amount )
427 push @buf,['','-----------'];
428 push @buf,['Balance Due', $money_char.
429 sprintf("%10.2f", $balance_due ) ];
431 #setup template variables
433 package FS::cust_bill::_template; #!
434 use vars qw( $invnum $date $page $total_pages @address $overdue @buf );
436 $invnum = $self->invnum;
437 $date = $self->_date;
441 int( scalar(@FS::cust_bill::buf) / $FS::cust_bill::invoice_lines );
443 if scalar(@FS::cust_bill::buf) % $FS::cust_bill::invoice_lines;
446 #format address (variable for the template)
448 @address = ( '', '', '', '', '', '' );
449 package FS::cust_bill; #!
450 $FS::cust_bill::_template::address[$l++] =
452 ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
453 ? " (P.O. #". $cust_main->payinfo. ")"
457 $FS::cust_bill::_template::address[$l++] = $cust_main->company
458 if $cust_main->company;
459 $FS::cust_bill::_template::address[$l++] = $cust_main->address1;
460 $FS::cust_bill::_template::address[$l++] = $cust_main->address2
461 if $cust_main->address2;
462 $FS::cust_bill::_template::address[$l++] =
463 $cust_main->city. ", ". $cust_main->state. " ". $cust_main->zip;
464 $FS::cust_bill::_template::address[$l++] = $cust_main->country
465 unless $cust_main->country eq 'US';
467 #overdue? (variable for the template)
468 $FS::cust_bill::_template::overdue = (
470 && $today > $self->_date
471 # && $self->printed > 1
472 && $self->printed > 0
475 #and subroutine for the template
477 sub FS::cust_bill::_template::invoice_lines {
480 scalar(@buf) ? shift @buf : [ '', '' ];
485 $FS::cust_bill::_template::page = 1;
489 push @collect, split("\n",
490 $invoice_template->fill_in( PACKAGE => 'FS::cust_bill::_template' )
492 $FS::cust_bill::_template::page++;
495 map "$_\n", @collect;
503 $Id: cust_bill.pm,v 1.15 2002-01-28 06:57:23 ivan Exp $
509 print_text formatting (and some logic :/) is in source, but needs to be
510 slurped in from a file. Also number of lines ($=).
512 missing print_ps for a nice postscript copy (maybe HylaFAX-cover-page-style
513 or something similar so the look can be completely customized?)
517 L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill_pay>, L<FS:;cust_pay>,
518 L<FS::cust_bill_pkg>, L<FS::cust_bill_credit>, schema.html from the base