two-sided printing of invoice batches, #29927
[freeside.git] / FS / FS / bill_batch.pm
1 package FS::bill_batch;
2
3 use strict;
4 use vars qw( @ISA $me $DEBUG );
5 use CAM::PDF;
6 use FS::Conf;
7 use FS::Record qw( qsearch qsearchs dbh );
8 use FS::agent;
9 use FS::cust_bill_batch;
10
11 @ISA = qw( FS::Record );
12 $me = '[ FS::bill_batch ]';
13 $DEBUG=0;
14
15 sub table { 'bill_batch' }
16
17 sub nohistory_fields { 'pdf' }
18
19 =head1 NAME
20
21 FS::bill_batch - Object methods for bill_batch records
22
23 =head1 SYNOPSIS
24
25   use FS::bill_batch;
26
27   $open_batch = FS::bill_batch->get_open_batch;
28   
29   my $pdf = $open_batch->print_pdf;
30   
31   $error = $open_batch->close;
32   
33 =head1 DESCRIPTION
34
35 An FS::bill_batch object represents a batch of invoices.  FS::bill_batch 
36 inherits from FS::Record.  The following fields are currently supported:
37
38 =over 4
39
40 =item batchnum - primary key
41
42 =item agentnum - empty for global batches or agent (see L<FS::agent>)
43
44 =item status - either 'O' (open) or 'R' (resolved/closed).
45
46 =item pdf - blob field for temporarily storing the invoice as a PDF.
47
48 =back
49
50 =head1 METHODS
51
52 =over 4
53
54 =item print_pdf
55
56 Typeset the entire batch as a PDF file.  Returns the PDF as a string.
57
58 =cut
59
60 sub print_pdf {
61   my $self = shift;
62   my $job = shift;
63   $job->update_statustext(0) if $job;
64   my @invoices = sort { $a->invnum <=> $b->invnum }
65                  qsearch('cust_bill_batch', { batchnum => $self->batchnum });
66   return "No invoices in batch ".$self->batchnum.'.' if !@invoices;
67
68   my $duplex = FS::Conf->exists('invoice_print_pdf-duplex');
69
70   my $pdf_out;
71   my $num = 0;
72   foreach my $invoice (@invoices) {
73     my $part = $invoice->cust_bill->print_pdf({$invoice->options});
74     die 'Failed creating PDF from invoice '.$invoice->invnum.'\n' if !$part;
75
76     if($pdf_out) {
77       $pdf_out->appendPDF(CAM::PDF->new($part));
78     }
79     else {
80       $pdf_out = CAM::PDF->new($part);
81     }
82     if ( $duplex ) {
83       my $n = $pdf_out->numPages;
84       if ( $n % 2 == 1 ) {
85         # then insert a blank page so we end on an even number
86         $pdf_out->duplicatePage($n, 1);
87       }
88     }
89     if($job) {
90       # update progressbar
91       $num++;
92       my $error = $job->update_statustext(int(100 * $num/scalar(@invoices)));
93       die $error if $error;
94     }
95   }
96   $job->update_statustext(100, 'Combining invoices') if $job;
97
98   return $pdf_out->toPDF;
99 }
100
101 =item close
102
103 Set the status of the batch to 'R' (resolved).
104
105 =cut
106
107 sub close {
108   my $self = shift;
109   $self->status('R');
110   return $self->replace;
111 }
112
113 sub check {
114   my $self = shift;
115
116   my $error =
117        $self->ut_numbern('batchnum')
118     || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
119     || $self->ut_enum('status', [ 'O', 'R' ] )
120   ;
121   return $error if $error;
122
123   $self->SUPER::check;
124 }
125
126 =item agent
127
128 Returns the agent (see L<FS::agent>) for this invoice batch.
129
130 =cut
131
132 sub agent {
133   my $self = shift;
134   qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
135 }
136
137 =back
138
139 =head1 SUBROUTINES
140
141 =item process_print_pdf
142
143 =cut
144
145 use Storable 'thaw';
146 use Data::Dumper;
147 use MIME::Base64;
148
149 sub process_print_pdf {
150   my $job = shift;
151   my $param = thaw(decode_base64(shift));
152   warn Dumper($param) if $DEBUG;
153   die "no batchnum specified!\n" if ! exists($param->{batchnum});
154   my $batch = FS::bill_batch->by_key($param->{batchnum});
155   die "batch '$param->{batchnum}' not found!\n" if !$batch;
156
157   if ( $param->{'close'} ) {
158     my $error = $batch->close;
159     die $error if $error;
160   }
161
162   my $pdf = $batch->print_pdf($job);
163   $batch->pdf($pdf);
164   my $error = $batch->replace;
165   die $error if $error;
166 }
167
168 =back
169
170 =head1 BUGS
171
172 =head1 SEE ALSO
173
174 L<FS::Record>, schema.html from the base documentation.
175
176 =cut
177
178 1;
179