print customer statements on the fly, #15864
[freeside.git] / FS / FS / cust_statement.pm
1 package FS::cust_statement;
2
3 use strict;
4 use base qw( FS::cust_bill );
5 use FS::Record qw( dbh qsearch ); #qsearchs );
6 use FS::cust_main;
7 use FS::cust_bill;
8
9 =head1 NAME
10
11 FS::cust_statement - Object methods for cust_statement records
12
13 =head1 SYNOPSIS
14
15   use FS::cust_statement;
16
17   $record = new FS::cust_statement \%hash;
18   $record = new FS::cust_statement { 'column' => 'value' };
19
20   $error = $record->insert;
21
22   $error = $new_record->replace($old_record);
23
24   $error = $record->delete;
25
26   $error = $record->check;
27
28 =head1 DESCRIPTION
29
30 An FS::cust_statement object represents an informational statement which
31 aggregates one or more invoices.  FS::cust_statement inherits from
32 FS::cust_bill.
33
34 The following fields are currently supported:
35
36 =over 4
37
38 =item statementnum
39
40 primary key
41
42 =item custnum
43
44 customer
45
46 =item _date
47
48 date
49
50 =back
51
52 =head1 METHODS
53
54 =over 4
55
56 =item new HASHREF
57
58 Creates a new record.  To add the record to the database, see L<"insert">.
59
60 Note that this stores the hash reference, not a distinct copy of the hash it
61 points to.  You can ask the object for a copy with the I<hash> method.
62
63 Pass "statementnum => 'ALL'" to create a temporary statement that includes 
64 all of the customer's invoices.  This statement can't be inserted and won't
65 set the statementnum field on any invoices.
66
67 =cut
68
69 sub new { FS::Record::new(@_); }
70
71 sub table { 'cust_statement'; }
72
73 =item insert
74
75 Adds this record to the database.  If there is an error, returns the error,
76 otherwise returns false.
77
78 =cut
79
80 sub insert {
81   my $self = shift;
82
83   local $SIG{HUP} = 'IGNORE';
84   local $SIG{INT} = 'IGNORE';
85   local $SIG{QUIT} = 'IGNORE';
86   local $SIG{TERM} = 'IGNORE';
87   local $SIG{TSTP} = 'IGNORE';
88   local $SIG{PIPE} = 'IGNORE';
89
90   my $oldAutoCommit = $FS::UID::AutoCommit;
91   local $FS::UID::AutoCommit = 0;
92   my $dbh = dbh;
93
94   FS::Record::insert($self);
95
96   foreach my $cust_bill (
97                           qsearch({
98                             'table'     => 'cust_bill',
99                             'hashref'   => { 'custnum'      => $self->custnum,
100                                              'statementnum' => '',
101                                            },
102                             'extra_sql' => 'FOR UPDATE' ,
103                           })
104                         )
105   {
106     $cust_bill->statementnum( $self->statementnum );
107     my $error = $cust_bill->replace;
108     if ( $error ) {
109       $dbh->rollback if $oldAutoCommit;
110       return "Error associating invoice: $error";
111     }
112   }
113
114   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
115   ''; #no error
116
117 }
118
119 =item delete
120
121 Delete this record from the database.
122
123 =cut
124
125 sub delete { FS::Record::delete(@_); }
126
127 =item replace OLD_RECORD
128
129 Replaces the OLD_RECORD with this one in the database.  If there is an error,
130 returns the error, otherwise returns false.
131
132 =cut
133
134 sub replace { FS::Record::replace(@_); }
135
136 sub replace_check { ''; }
137
138 =item check
139
140 Checks all fields to make sure this is a valid record.  If there is
141 an error, returns the error, otherwise returns false.  Called by the insert
142 and replace methods.
143
144 =cut
145
146 sub check {
147   my $self = shift;
148
149   my $error = 
150     $self->ut_numbern('statementnum')
151     || $self->ut_foreign_key('custnum', 'cust_main', 'custnum' )
152     || $self->ut_numbern('_date')
153   ;
154   return $error if $error;
155
156   $self->_date(time) unless $self->_date;
157
158   #don't want to call cust_bill, and Record just checks virtual fields
159   #$self->SUPER::check;
160   '';
161
162 }
163
164 =item cust_bill
165
166 Returns the associated invoices (cust_bill records) for this statement.
167
168 =cut
169
170 sub cust_bill {
171   my $self = shift;
172   # we use it about a thousand times, let's cache it
173   $self->{Hash}->{cust_bill} ||= [
174     qsearch('cust_bill', { 
175         $self->statementnum eq 'ALL' ?
176           ('custnum' => $self->custnum) :
177           ('statementnum' => $self->statementnum)
178     } )
179   ];
180
181   @{ $self->{Hash}->{cust_bill} }
182 }
183
184 sub _aggregate {
185   my( $self, $method ) = ( shift, shift );
186
187   my @agg = ();
188
189   foreach my $cust_bill ( $self->cust_bill ) {
190     push @agg, $cust_bill->$method( @_ );
191   }
192
193   @agg;
194 }
195
196 sub _total {
197   my( $self, $method ) = ( shift, shift );
198
199   my $total = 0;
200
201   foreach my $cust_bill ( $self->cust_bill ) {
202     $total += $cust_bill->$method( @_ );
203   }
204
205   $total;
206 }
207
208 =item cust_bill_pkg
209
210 Returns the line items (see L<FS::cust_bill_pkg>) for all associated invoices.
211
212 =item cust_bill_pkg_pkgnum PKGNUM
213
214 Returns the line items (see L<FS::cust_bill_pkg>) for all associated invoices
215 and specified pkgnum.
216
217 =item cust_bill_pay
218
219 Returns all payment applications (see L<FS::cust_bill_pay>) for all associated
220 invoices.
221
222 =item cust_credited
223
224 Returns all applied credits (see L<FS::cust_credit_bill>) for all associated
225 invoices.
226
227 =item cust_bill_pay_pkgnum PKGNUM
228
229 Returns all payment applications (see L<FS::cust_bill_pay>) for all associated
230 invoices with matching pkgnum.
231
232 =item cust_credited_pkgnum PKGNUM
233
234 Returns all applied credits (see L<FS::cust_credit_bill>) for all associated
235 invoices with matching pkgnum.
236
237 =cut
238
239 sub cust_bill_pay        { shift->_aggregate('cust_bill_pay',        @_); }
240 sub cust_credited        { shift->_aggregate('cust_credited',        @_); }
241 sub cust_bill_pay_pkgnum { shift->_aggregate('cust_bill_pay_pkgnum', @_); }
242 sub cust_credited_pkgnum { shift->_aggregate('cust_credited_pkgnum', @_); }
243
244 sub cust_bill_pkg        { shift->_aggregate('cust_bill_pkg',        @_); }
245 sub cust_bill_pkg_pkgnum { shift->_aggregate('cust_bill_pkg_pkgnum', @_); }
246
247 =item tax
248
249 Returns the total tax amount for all assoicated invoices.0
250
251 =cut
252
253 =item charged
254
255 Returns the total amount charged for all associated invoices.
256
257 =cut
258
259 =item owed
260
261 Returns the total amount owed for all associated invoices.
262
263 =cut
264
265 sub tax     { shift->_total('tax',     @_); }
266 sub charged { shift->_total('charged', @_); }
267 sub owed    { shift->_total('owed',    @_); }
268
269 #don't show previous info
270 sub previous {
271   ( 0 ); # 0, empty list
272 }
273
274 =back
275
276 =head1 BUGS
277
278 =head1 SEE ALSO
279
280 L<FS::cust_bill>, L<FS::Record>, schema.html from the base documentation.
281
282 =cut
283
284 1;
285