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. FS::cust_bill inherits from
69 FS::Record. The following fields are currently supported:
73 =item invnum - primary key (assigned automatically for new invoices)
75 =item custnum - customer (see L<FS::cust_main>)
77 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
78 L<Time::Local> and L<Date::Parse> for conversion functions.
80 =item charged - amount of this invoice
82 =item owed - amount still outstanding on this invoice, which is charged minus
83 all payments (see L<FS::cust_pay>).
85 =item printed - how many times this invoice has been printed automatically
86 (see L<FS::cust_main/"collect">).
96 Creates a new invoice. To add the invoice to the database, see L<"insert">.
97 Invoices are normally created by calling the bill method of a customer object
98 (see L<FS::cust_main>).
102 sub table { 'cust_bill'; }
106 Adds this invoice to the database ("Posts" the invoice). If there is an error,
107 returns the error, otherwise returns false.
109 When adding new invoices, owed must be charged (or null, in which case it is
110 automatically set to charged).
117 $self->owed( $self->charged ) if $self->owed eq '';
118 return "owed != charged!"
119 unless $self->owed == $self->charged;
121 $self->SUPER::insert;
126 Currently unimplemented. I don't remove invoices because there would then be
127 no record you ever posted this invoice (which is bad, no?)
132 return "Can't remove invoice!"
135 =item replace OLD_RECORD
137 Replaces the OLD_RECORD with this one in the database. If there is an error,
138 returns the error, otherwise returns false.
140 Only owed and printed may be changed. Owed is normally updated by creating and
141 inserting a payment (see L<FS::cust_pay>). Printed is normally updated by
142 calling the collect method of a customer object (see L<FS::cust_main>).
147 my( $new, $old ) = ( shift, shift );
148 return "Can't change custnum!" unless $old->custnum == $new->custnum;
149 #return "Can't change _date!" unless $old->_date eq $new->_date;
150 return "Can't change _date!" unless $old->_date == $new->_date;
151 return "Can't change charged!" unless $old->charged == $new->charged;
152 return "(New) owed can't be > (new) charged!" if $new->owed > $new->charged;
154 $new->SUPER::replace($old);
159 Checks all fields to make sure this is a valid invoice. If there is an error,
160 returns the error, otherwise returns false. Called by the insert and replace
169 $self->ut_numbern('invnum')
170 || $self->ut_number('custnum')
171 || $self->ut_numbern('_date')
172 || $self->ut_money('charged')
173 || $self->ut_money('owed')
174 || $self->ut_numbern('printed')
176 return $error if $error;
178 return "Unknown customer"
179 unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
181 $self->_date(time) unless $self->_date;
183 $self->printed(0) if $self->printed eq '';
190 Returns a list consisting of the total previous balance for this customer,
191 followed by the previous outstanding invoices (as FS::cust_bill objects also).
198 my @cust_bill = sort { $a->_date <=> $b->_date }
199 grep { $_->owed != 0 && $_->_date < $self->_date }
200 qsearch( 'cust_bill', { 'custnum' => $self->custnum } )
202 foreach ( @cust_bill ) { $total += $_->owed; }
208 Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
214 qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
219 Returns a list consisting of the total previous credited (see
220 L<FS::cust_credit>) for this customer, followed by the previous outstanding
221 credits (FS::cust_credit objects).
228 my @cust_credit = sort { $a->_date <=> $b->date }
229 grep { $_->credited != 0 && $_->_date < $self->_date }
230 qsearch('cust_credit', { 'custnum' => $self->custnum } )
232 foreach (@cust_credit) { $total += $_->credited; }
233 $total, @cust_credit;
238 Returns all payments (see L<FS::cust_pay>) for this invoice.
244 sort { $a->_date <=> $b->date }
245 qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
249 =item print_text [TIME];
251 Returns an text invoice, as a list of lines.
253 TIME an optional value used to control the printing of overdue messages. The
254 default is now. It isn't the date of the invoice; that's the `_date' field.
255 It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
256 L<Time::Local> and L<Date::Parse> for conversion functions.
262 my( $self, $today ) = ( shift, shift );
264 # my $invnum = $self->invnum;
265 my $cust_main = qsearchs('cust_main', { 'custnum', $self->custnum } );
266 $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
267 unless $cust_main->payname;
269 my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
270 my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
271 my $balance_due = $self->owed + $pr_total - $cr_total;
276 #my($description,$amount);
280 foreach ( @pr_cust_bill ) {
282 "Previous Balance, Invoice #". $_->invnum.
283 " (". time2str("%x",$_->_date). ")",
284 '$'. sprintf("%10.2f",$_->owed)
288 push @buf,['','-----------'];
289 push @buf,['Total Previous Balance','$' . sprintf("%10.2f",$pr_total ) ];
294 foreach ( $self->cust_bill_pkg ) {
298 my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
299 my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
300 my($pkg)=$part_pkg->pkg;
302 if ( $_->setup != 0 ) {
303 push @buf, [ "$pkg Setup",'$' . sprintf("%10.2f",$_->setup) ];
305 map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
308 if ( $_->recur != 0 ) {
310 "$pkg (" . time2str("%x",$_->sdate) . " - " .
311 time2str("%x",$_->edate) . ")",
312 '$' . sprintf("%10.2f",$_->recur)
315 map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
319 push @buf,["Tax",'$' . sprintf("%10.2f",$_->setup) ]
324 push @buf,['','-----------'];
325 push @buf,['Total New Charges',
326 '$' . sprintf("%10.2f",$self->charged) ];
329 push @buf,['','-----------'];
330 push @buf,['Total Charges',
331 '$' . sprintf("%10.2f",$self->charged + $pr_total) ];
335 foreach ( @cr_cust_credit ) {
337 "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
338 '$' . sprintf("%10.2f",$_->credited)
342 #get & print payments
343 foreach ( $self->cust_pay ) {
345 "Payment received ". time2str("%x",$_->_date ),
346 '$' . sprintf("%10.2f",$_->paid )
351 push @buf,['','-----------'];
352 push @buf,['Balance Due','$' .
353 sprintf("%10.2f",$self->owed + $pr_total - $cr_total ) ];
355 #setup template variables
357 package FS::cust_bill::_template; #!
358 use vars qw( $invnum $date $page $total_pages @address $overdue @buf );
360 $invnum = $self->invnum;
361 $date = $self->_date;
365 int( scalar(@FS::cust_bill::buf) / $FS::cust_bill::invoice_lines );
367 if scalar(@FS::cust_bill::buf) % $FS::cust_bill::invoice_lines;
370 #format address (variable for the template)
372 @address = ( '', '', '', '', '', '' );
373 package FS::cust_bill; #!
374 $FS::cust_bill::_template::address[$l++] =
376 ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
377 ? " (P.O. #". $cust_main->payinfo. ")"
381 $FS::cust_bill::_template::address[$l++] = $cust_main->company
382 if $cust_main->company;
383 $FS::cust_bill::_template::address[$l++] = $cust_main->address1;
384 $FS::cust_bill::_template::address[$l++] = $cust_main->address2
385 if $cust_main->address2;
386 $FS::cust_bill::_template::address[$l++] =
387 $cust_main->city. ", ". $cust_main->state. " ". $cust_main->zip;
388 $FS::cust_bill::_template::address[$l++] = $cust_main->country
389 unless $cust_main->country eq 'US';
391 #overdue? (variable for the template)
392 $FS::cust_bill::_template::overdue = (
394 && $today > $self->_date
395 # && $self->printed > 1
396 && $self->printed > 0
399 #and subroutine for the template
401 sub FS::cust_bill::_template::invoice_lines {
404 scalar(@buf) ? shift @buf : [ '', '' ];
409 $FS::cust_bill::_template::page = 1;
413 push @collect, split("\n",
414 $invoice_template->fill_in( PACKAGE => 'FS::cust_bill::_template' )
416 $FS::cust_bill::_template::page++;
419 map "$_\n", @collect;
427 $Id: cust_bill.pm,v 1.3 2000-09-20 10:35:21 ivan Exp $
433 print_text formatting (and some logic :/) is in source, but needs to be
434 slurped in from a file. Also number of lines ($=).
436 missing print_ps for a nice postscript copy (maybe HylaFAX-cover-page-style
437 or something similar so the look can be completely customized?)
441 L<FS::Record>, L<FS::cust_main>, L<FS::cust_pay>, L<FS::cust_bill_pkg>,
442 L<FS::cust_credit>, schema.html from the base documentation.