4 use vars qw( @ISA $conf $invoice_template );
5 use vars qw( $invoice_lines @buf ); #yuck
8 use FS::Record qw( qsearch qsearchs );
10 use FS::cust_bill_pkg;
15 @ISA = qw( FS::Record );
17 #ask FS::UID to run this stuff for us later
18 $FS::UID::callback{'FS::cust_bill'} = sub {
20 my @invoice_template = $conf->config('invoice_template')
21 or die "cannot load config file invoice_template";
23 foreach ( grep /invoice_lines\(\d+\)/, @invoice_template ) { #kludgy
24 /invoice_lines\((\d+)\)/;
27 die "no invoice_lines() functions in template?" unless $invoice_lines;
28 $invoice_template = new Text::Template (
30 SOURCE => [ map "$_\n", @invoice_template ],
31 ) or die "can't create new Text::Template object: $Text::Template::ERROR";
32 $invoice_template->compile()
33 or die "can't compile template: $Text::Template::ERROR";
38 FS::cust_bill - Object methods for cust_bill records
44 $record = new FS::cust_bill \%hash;
45 $record = new FS::cust_bill { 'column' => 'value' };
47 $error = $record->insert;
49 $error = $new_record->replace($old_record);
51 $error = $record->delete;
53 $error = $record->check;
55 ( $total_previous_balance, @previous_cust_bill ) = $record->previous;
57 @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg;
59 ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit;
61 @cust_pay_objects = $cust_bill->cust_pay;
63 @lines = $cust_bill->print_text;
64 @lines = $cust_bill->print_text $time;
68 An FS::cust_bill object represents an invoice; a declaration that a customer
69 owes you money. The specific charges are itemized as B<cust_bill_pkg> records
70 (see L<FS::cust_bill_pkg>). FS::cust_bill inherits from FS::Record. The
71 following fields are currently supported:
75 =item invnum - primary key (assigned automatically for new invoices)
77 =item custnum - customer (see L<FS::cust_main>)
79 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
80 L<Time::Local> and L<Date::Parse> for conversion functions.
82 =item charged - amount of this invoice
84 =item owed - amount still outstanding on this invoice, which is charged minus
85 all payments (see L<FS::cust_pay>).
87 =item printed - how many times this invoice has been printed automatically
88 (see L<FS::cust_main/"collect">).
98 Creates a new invoice. To add the invoice to the database, see L<"insert">.
99 Invoices are normally created by calling the bill method of a customer object
100 (see L<FS::cust_main>).
104 sub table { 'cust_bill'; }
108 Adds this invoice to the database ("Posts" the invoice). If there is an error,
109 returns the error, otherwise returns false.
111 When adding new invoices, owed must be charged (or null, in which case it is
112 automatically set to charged).
119 $self->owed( $self->charged ) if $self->owed eq '';
120 return "owed != charged!"
121 unless $self->owed == $self->charged;
123 $self->SUPER::insert;
128 Currently unimplemented. I don't remove invoices because there would then be
129 no record you ever posted this invoice (which is bad, no?)
134 return "Can't remove invoice!"
137 =item replace OLD_RECORD
139 Replaces the OLD_RECORD with this one in the database. If there is an error,
140 returns the error, otherwise returns false.
142 Only owed and printed may be changed. Owed is normally updated by creating and
143 inserting a payment (see L<FS::cust_pay>). Printed is normally updated by
144 calling the collect method of a customer object (see L<FS::cust_main>).
149 my( $new, $old ) = ( shift, shift );
150 return "Can't change custnum!" unless $old->custnum == $new->custnum;
151 #return "Can't change _date!" unless $old->_date eq $new->_date;
152 return "Can't change _date!" unless $old->_date == $new->_date;
153 return "Can't change charged!" unless $old->charged == $new->charged;
154 return "(New) owed can't be > (new) charged!" if $new->owed > $new->charged;
156 $new->SUPER::replace($old);
161 Checks all fields to make sure this is a valid invoice. If there is an error,
162 returns the error, otherwise returns false. Called by the insert and replace
171 $self->ut_numbern('invnum')
172 || $self->ut_number('custnum')
173 || $self->ut_numbern('_date')
174 || $self->ut_money('charged')
175 || $self->ut_money('owed')
176 || $self->ut_numbern('printed')
178 return $error if $error;
180 return "Unknown customer"
181 unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
183 $self->_date(time) unless $self->_date;
185 $self->printed(0) if $self->printed eq '';
192 Returns a list consisting of the total previous balance for this customer,
193 followed by the previous outstanding invoices (as FS::cust_bill objects also).
200 my @cust_bill = sort { $a->_date <=> $b->_date }
201 grep { $_->owed != 0 && $_->_date < $self->_date }
202 qsearch( 'cust_bill', { 'custnum' => $self->custnum } )
204 foreach ( @cust_bill ) { $total += $_->owed; }
210 Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
216 qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
221 Returns a list consisting of the total previous credited (see
222 L<FS::cust_credit>) for this customer, followed by the previous outstanding
223 credits (FS::cust_credit objects).
230 my @cust_credit = sort { $a->_date <=> $b->_date }
231 grep { $_->credited != 0 && $_->_date < $self->_date }
232 qsearch('cust_credit', { 'custnum' => $self->custnum } )
234 foreach (@cust_credit) { $total += $_->credited; }
235 $total, @cust_credit;
240 Returns all payments (see L<FS::cust_pay>) for this invoice.
246 sort { $a->_date <=> $b->_date }
247 qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
251 =item print_text [TIME];
253 Returns an text invoice, as a list of lines.
255 TIME an optional value used to control the printing of overdue messages. The
256 default is now. It isn't the date of the invoice; that's the `_date' field.
257 It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
258 L<Time::Local> and L<Date::Parse> for conversion functions.
264 my( $self, $today ) = ( shift, shift );
266 # my $invnum = $self->invnum;
267 my $cust_main = qsearchs('cust_main', { 'custnum', $self->custnum } );
268 $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
269 unless $cust_main->payname;
271 my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
272 my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
273 my $balance_due = $self->owed + $pr_total - $cr_total;
278 #my($description,$amount);
282 foreach ( @pr_cust_bill ) {
284 "Previous Balance, Invoice #". $_->invnum.
285 " (". time2str("%x",$_->_date). ")",
286 '$'. sprintf("%10.2f",$_->owed)
290 push @buf,['','-----------'];
291 push @buf,['Total Previous Balance','$' . sprintf("%10.2f",$pr_total ) ];
296 foreach ( $self->cust_bill_pkg ) {
300 my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
301 my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
302 my($pkg)=$part_pkg->pkg;
304 if ( $_->setup != 0 ) {
305 push @buf, [ "$pkg Setup",'$' . sprintf("%10.2f",$_->setup) ];
307 map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
310 if ( $_->recur != 0 ) {
312 "$pkg (" . time2str("%x",$_->sdate) . " - " .
313 time2str("%x",$_->edate) . ")",
314 '$' . sprintf("%10.2f",$_->recur)
317 map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
321 push @buf,["Tax",'$' . sprintf("%10.2f",$_->setup) ]
326 push @buf,['','-----------'];
327 push @buf,['Total New Charges',
328 '$' . sprintf("%10.2f",$self->charged) ];
331 push @buf,['','-----------'];
332 push @buf,['Total Charges',
333 '$' . sprintf("%10.2f",$self->charged + $pr_total) ];
337 foreach ( @cr_cust_credit ) {
339 "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
340 '$' . sprintf("%10.2f",$_->credited)
344 #get & print payments
345 foreach ( $self->cust_pay ) {
347 "Payment received ". time2str("%x",$_->_date ),
348 '$' . sprintf("%10.2f",$_->paid )
353 push @buf,['','-----------'];
354 push @buf,['Balance Due','$' .
355 sprintf("%10.2f",$self->owed + $pr_total - $cr_total ) ];
357 #setup template variables
359 package FS::cust_bill::_template; #!
360 use vars qw( $invnum $date $page $total_pages @address $overdue @buf );
362 $invnum = $self->invnum;
363 $date = $self->_date;
367 int( scalar(@FS::cust_bill::buf) / $FS::cust_bill::invoice_lines );
369 if scalar(@FS::cust_bill::buf) % $FS::cust_bill::invoice_lines;
372 #format address (variable for the template)
374 @address = ( '', '', '', '', '', '' );
375 package FS::cust_bill; #!
376 $FS::cust_bill::_template::address[$l++] =
378 ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
379 ? " (P.O. #". $cust_main->payinfo. ")"
383 $FS::cust_bill::_template::address[$l++] = $cust_main->company
384 if $cust_main->company;
385 $FS::cust_bill::_template::address[$l++] = $cust_main->address1;
386 $FS::cust_bill::_template::address[$l++] = $cust_main->address2
387 if $cust_main->address2;
388 $FS::cust_bill::_template::address[$l++] =
389 $cust_main->city. ", ". $cust_main->state. " ". $cust_main->zip;
390 $FS::cust_bill::_template::address[$l++] = $cust_main->country
391 unless $cust_main->country eq 'US';
393 #overdue? (variable for the template)
394 $FS::cust_bill::_template::overdue = (
396 && $today > $self->_date
397 # && $self->printed > 1
398 && $self->printed > 0
401 #and subroutine for the template
403 sub FS::cust_bill::_template::invoice_lines {
406 scalar(@buf) ? shift @buf : [ '', '' ];
411 $FS::cust_bill::_template::page = 1;
415 push @collect, split("\n",
416 $invoice_template->fill_in( PACKAGE => 'FS::cust_bill::_template' )
418 $FS::cust_bill::_template::page++;
421 map "$_\n", @collect;
429 $Id: cust_bill.pm,v 1.5 2001-02-11 17:17:39 ivan Exp $
435 print_text formatting (and some logic :/) is in source, but needs to be
436 slurped in from a file. Also number of lines ($=).
438 missing print_ps for a nice postscript copy (maybe HylaFAX-cover-page-style
439 or something similar so the look can be completely customized?)
443 L<FS::Record>, L<FS::cust_main>, L<FS::cust_pay>, L<FS::cust_bill_pkg>,
444 L<FS::cust_credit>, schema.html from the base documentation.