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;
15 @ISA = qw( FS::Record );
17 #ask FS::UID to run this stuff for us later
18 $FS::UID::callback{'FS::cust_bill'} = sub {
22 $money_char = $conf->config('money_char') || '$';
24 my @invoice_template = $conf->config('invoice_template')
25 or die "cannot load config file invoice_template";
27 foreach ( grep /invoice_lines\(\d+\)/, @invoice_template ) { #kludgy
28 /invoice_lines\((\d+)\)/;
31 die "no invoice_lines() functions in template?" unless $invoice_lines;
32 $invoice_template = new Text::Template (
34 SOURCE => [ map "$_\n", @invoice_template ],
35 ) or die "can't create new Text::Template object: $Text::Template::ERROR";
36 $invoice_template->compile()
37 or die "can't compile template: $Text::Template::ERROR";
42 FS::cust_bill - Object methods for cust_bill records
48 $record = new FS::cust_bill \%hash;
49 $record = new FS::cust_bill { 'column' => 'value' };
51 $error = $record->insert;
53 $error = $new_record->replace($old_record);
55 $error = $record->delete;
57 $error = $record->check;
59 ( $total_previous_balance, @previous_cust_bill ) = $record->previous;
61 @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg;
63 ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit;
65 @cust_pay_objects = $cust_bill->cust_pay;
67 @lines = $cust_bill->print_text;
68 @lines = $cust_bill->print_text $time;
72 An FS::cust_bill object represents an invoice; a declaration that a customer
73 owes you money. The specific charges are itemized as B<cust_bill_pkg> records
74 (see L<FS::cust_bill_pkg>). FS::cust_bill inherits from FS::Record. The
75 following fields are currently supported:
79 =item invnum - primary key (assigned automatically for new invoices)
81 =item custnum - customer (see L<FS::cust_main>)
83 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
84 L<Time::Local> and L<Date::Parse> for conversion functions.
86 =item charged - amount of this invoice
88 =item printed - how many times this invoice has been printed automatically
89 (see L<FS::cust_main/"collect">).
99 Creates a new invoice. To add the invoice to the database, see L<"insert">.
100 Invoices are normally created by calling the bill method of a customer object
101 (see L<FS::cust_main>).
105 sub table { 'cust_bill'; }
109 Adds this invoice to the database ("Posts" the invoice). If there is an error,
110 returns the error, otherwise returns false.
114 Currently unimplemented. I don't remove invoices because there would then be
115 no record you ever posted this invoice (which is bad, no?)
120 return "Can't remove invoice!"
123 =item replace OLD_RECORD
125 Replaces the OLD_RECORD with this one in the database. If there is an error,
126 returns the error, otherwise returns false.
128 Only printed may be changed. printed is normally updated by calling the
129 collect method of a customer object (see L<FS::cust_main>).
134 my( $new, $old ) = ( shift, shift );
135 return "Can't change custnum!" unless $old->custnum == $new->custnum;
136 #return "Can't change _date!" unless $old->_date eq $new->_date;
137 return "Can't change _date!" unless $old->_date == $new->_date;
138 return "Can't change charged!" unless $old->charged == $new->charged;
140 $new->SUPER::replace($old);
145 Checks all fields to make sure this is a valid invoice. If there is an error,
146 returns the error, otherwise returns false. Called by the insert and replace
155 $self->ut_numbern('invnum')
156 || $self->ut_number('custnum')
157 || $self->ut_numbern('_date')
158 || $self->ut_money('charged')
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 } )
236 Returns the amount owed (still outstanding) on this invoice, which is charged
237 minus all payments (see L<FS::cust_pay>).
243 my $balance = $self->charged;
244 $balance -= $_->paid foreach ( $self->cust_pay );
248 =item print_text [TIME];
250 Returns an text invoice, as a list of lines.
252 TIME an optional value used to control the printing of overdue messages. The
253 default is now. It isn't the date of the invoice; that's the `_date' field.
254 It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
255 L<Time::Local> and L<Date::Parse> for conversion functions.
261 my( $self, $today ) = ( shift, shift );
263 # my $invnum = $self->invnum;
264 my $cust_main = qsearchs('cust_main', { 'custnum', $self->custnum } );
265 $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
266 unless $cust_main->payname;
268 my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
269 my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
270 my $balance_due = $self->owed + $pr_total - $cr_total;
275 #my($description,$amount);
279 foreach ( @pr_cust_bill ) {
281 "Previous Balance, Invoice #". $_->invnum.
282 " (". time2str("%x",$_->_date). ")",
283 $money_char. sprintf("%10.2f",$_->owed)
287 push @buf,['','-----------'];
288 push @buf,[ 'Total Previous Balance',
289 $money_char. 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", $money_char. 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 $money_char. sprintf("%10.2f",$_->recur)
315 map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
319 push @buf,["Tax", $money_char. sprintf("%10.2f",$_->setup) ]
324 push @buf,['','-----------'];
325 push @buf,['Total New Charges',
326 $money_char. sprintf("%10.2f",$self->charged) ];
329 push @buf,['','-----------'];
330 push @buf,['Total Charges',
331 $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
335 foreach ( @cr_cust_credit ) {
337 "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
338 $money_char. sprintf("%10.2f",$_->credited)
342 #get & print payments
343 foreach ( $self->cust_pay ) {
345 "Payment received ". time2str("%x",$_->_date ),
346 $money_char. sprintf("%10.2f",$_->paid )
351 push @buf,['','-----------'];
352 push @buf,['Balance Due', $money_char.
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.7 2001-04-09 23:05:15 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.