This commit was generated by cvs2svn to compensate for changes in r6255,
[freeside.git] / FS / FS / cust_bill_pkg.pm
1 package FS::cust_bill_pkg;
2
3 use strict;
4 use vars qw( @ISA );
5 use FS::Record qw( qsearch qsearchs dbdef dbh );
6 use FS::cust_main_Mixin;
7 use FS::cust_pkg;
8 use FS::cust_bill;
9 use FS::cust_bill_pkg_detail;
10 use FS::cust_bill_pay_pkg;
11 use FS::cust_credit_bill_pkg;
12
13 @ISA = qw( FS::cust_main_Mixin FS::Record );
14
15 =head1 NAME
16
17 FS::cust_bill_pkg - Object methods for cust_bill_pkg records
18
19 =head1 SYNOPSIS
20
21   use FS::cust_bill_pkg;
22
23   $record = new FS::cust_bill_pkg \%hash;
24   $record = new FS::cust_bill_pkg { 'column' => 'value' };
25
26   $error = $record->insert;
27
28   $error = $new_record->replace($old_record);
29
30   $error = $record->delete;
31
32   $error = $record->check;
33
34 =head1 DESCRIPTION
35
36 An FS::cust_bill_pkg object represents an invoice line item.
37 FS::cust_bill_pkg inherits from FS::Record.  The following fields are currently
38 supported:
39
40 =over 4
41
42 =item billpkgnum - primary key
43
44 =item invnum - invoice (see L<FS::cust_bill>)
45
46 =item pkgnum - package (see L<FS::cust_pkg>) or 0 for the special virtual sales tax package, or -1 for the virtual line item (itemdesc is used for the line)
47
48 =item setup - setup fee
49
50 =item recur - recurring fee
51
52 =item sdate - starting date of recurring fee
53
54 =item edate - ending date of recurring fee
55
56 =item itemdesc - Line item description (currentlty used only when pkgnum is 0 or -1)
57
58 =back
59
60 sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">.  Also
61 see L<Time::Local> and L<Date::Parse> for conversion functions.
62
63 =head1 METHODS
64
65 =over 4
66
67 =item new HASHREF
68
69 Creates a new line item.  To add the line item to the database, see
70 L<"insert">.  Line items are normally created by calling the bill method of a
71 customer object (see L<FS::cust_main>).
72
73 =cut
74
75 sub table { 'cust_bill_pkg'; }
76
77 =item insert
78
79 Adds this line item to the database.  If there is an error, returns the error,
80 otherwise returns false.
81
82 =cut
83
84 sub insert {
85   my $self = shift;
86
87   local $SIG{HUP} = 'IGNORE';
88   local $SIG{INT} = 'IGNORE';
89   local $SIG{QUIT} = 'IGNORE';
90   local $SIG{TERM} = 'IGNORE';
91   local $SIG{TSTP} = 'IGNORE';
92   local $SIG{PIPE} = 'IGNORE';
93
94   my $oldAutoCommit = $FS::UID::AutoCommit;
95   local $FS::UID::AutoCommit = 0;
96   my $dbh = dbh;
97
98   my $error = $self->SUPER::insert;
99   if ( $error ) {
100     $dbh->rollback if $oldAutoCommit;
101     return $error;
102   }
103
104   unless ( defined dbdef->table('cust_bill_pkg_detail') && $self->get('details') ) {
105     $dbh->commit or die $dbh->errstr if $oldAutoCommit;
106     return '';
107   }
108
109   foreach my $detail ( @{$self->get('details')} ) {
110     my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail {
111       'pkgnum' => $self->pkgnum,
112       'invnum' => $self->invnum,
113       'detail' => $detail,
114     };
115     $error = $cust_bill_pkg_detail->insert;
116     if ( $error ) {
117       $dbh->rollback if $oldAutoCommit;
118       return $error;
119     }
120   }
121
122   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
123   '';
124
125 }
126
127 =item delete
128
129 Currently unimplemented.  I don't remove line items because there would then be
130 no record the items ever existed (which is bad, no?)
131
132 =cut
133
134 sub delete {
135   return "Can't delete cust_bill_pkg records!";
136 }
137
138 =item replace OLD_RECORD
139
140 Currently unimplemented.  This would be even more of an accounting nightmare
141 than deleteing the items.  Just don't do it.
142
143 =cut
144
145 sub replace {
146   return "Can't modify cust_bill_pkg records!";
147 }
148
149 =item check
150
151 Checks all fields to make sure this is a valid line item.  If there is an
152 error, returns the error, otherwise returns false.  Called by the insert
153 method.
154
155 =cut
156
157 sub check {
158   my $self = shift;
159
160   my $error =
161          $self->ut_numbern('billpkgnum')
162       || $self->ut_snumber('pkgnum')
163       || $self->ut_number('invnum')
164       || $self->ut_money('setup')
165       || $self->ut_money('recur')
166       || $self->ut_numbern('sdate')
167       || $self->ut_numbern('edate')
168       || $self->ut_textn('itemdesc')
169   ;
170   return $error if $error;
171
172   #if ( $self->pkgnum != 0 ) { #allow unchecked pkgnum 0 for tax! (add to part_pkg?)
173   if ( $self->pkgnum > 0 ) { #allow -1 for non-pkg line items and 0 for tax (add to part_pkg?)
174     return "Unknown pkgnum ". $self->pkgnum
175       unless qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
176   }
177
178   return "Unknown invnum"
179     unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
180
181   $self->SUPER::check;
182 }
183
184 =item cust_pkg
185
186 Returns the package (see L<FS::cust_pkg>) for this invoice line item.
187
188 =cut
189
190 sub cust_pkg {
191   my $self = shift;
192   qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
193 }
194
195 =item cust_bill
196
197 Returns the invoice (see L<FS::cust_bill>) for this invoice line item.
198
199 =cut
200
201 sub cust_bill {
202   my $self = shift;
203   qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
204 }
205
206 =item details
207
208 Returns an array of detail information for the invoice line item.
209
210 =cut
211
212 sub details {
213   my $self = shift;
214   return () unless defined dbdef->table('cust_bill_pkg_detail');
215   map { $_->detail }
216     qsearch ( 'cust_bill_pkg_detail', { 'pkgnum' => $self->pkgnum,
217                                         'invnum' => $self->invnum, } );
218     #qsearch ( 'cust_bill_pkg_detail', { 'lineitemnum' => $self->lineitemnum });
219 }
220
221 =item desc
222
223 Returns a description for this line item.  For typical line items, this is the
224 I<pkg> field of the corresponding B<FS::part_pkg> object (see L<FS::part_pkg>).
225 For one-shot line items and named taxes, it is the I<itemdesc> field of this
226 line item, and for generic taxes, simply returns "Tax".
227
228 =cut
229
230 sub desc {
231   my $self = shift;
232
233   if ( $self->pkgnum > 0 ) {
234     $self->cust_pkg->part_pkg->pkg;
235   } else {
236     $self->itemdesc || 'Tax';
237   }
238 }
239
240 =item owed_setup
241
242 Returns the amount owed (still outstanding) on this line item's setup fee,
243 which is the amount of the line item minus all payment applications (see
244 L<FS::cust_bill_pay_pkg> and credit applications (see
245 L<FS::cust_credit_bill_pkg>).
246
247 =cut
248
249 sub owed_setup {
250   my $self = shift;
251   $self->owed('setup', @_);
252 }
253
254 =item owed_recur
255
256 Returns the amount owed (still outstanding) on this line item's recurring fee,
257 which is the amount of the line item minus all payment applications (see
258 L<FS::cust_bill_pay_pkg> and credit applications (see
259 L<FS::cust_credit_bill_pkg>).
260
261 =cut
262
263 sub owed_recur {
264   my $self = shift;
265   $self->owed('recur', @_);
266 }
267
268 # modeled after cust_bill::owed...
269 sub owed {
270   my( $self, $field ) = @_;
271   my $balance = $self->$field();
272   $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg($field) );
273   $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg($field) );
274   $balance = sprintf( '%.2f', $balance );
275   $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
276   $balance;
277 }
278
279 sub cust_bill_pay_pkg {
280   my( $self, $field ) = @_;
281   qsearch( 'cust_bill_pay_pkg', { 'billpkgnum' => $self->billpkgnum,
282                                   'setuprecur' => $field,
283                                 }
284          );
285 }
286
287 sub cust_credit_bill_pkg {
288   my( $self, $field ) = @_;
289   qsearch( 'cust_credit_bill_pkg', { 'billpkgnum' => $self->billpkgnum,
290                                      'setuprecur' => $field,
291                                    }
292          );
293 }
294
295 =back
296
297 =head1 BUGS
298
299 setup and recur shouldn't be separate fields.  There should be one "amount"
300 field and a flag to tell you if it is a setup/one-time fee or a recurring fee.
301
302 A line item with both should really be two separate records (preserving
303 sdate and edate for setup fees for recurring packages - that information may
304 be valuable later).  Invoice generation (cust_main::bill), invoice printing
305 (cust_bill), tax reports (report_tax.cgi) and line item reports 
306 (cust_bill_pkg.cgi) would need to be updated.
307
308 owed_setup and owed_recur could then be repaced by just owed, and
309 cust_bill::open_cust_bill_pkg and
310 cust_bill_ApplicationCommon::apply_to_lineitems could be simplified.
311
312 =head1 SEE ALSO
313
314 L<FS::Record>, L<FS::cust_bill>, L<FS::cust_pkg>, L<FS::cust_main>, schema.html
315 from the base documentation.
316
317 =cut
318
319 1;
320