This commit was generated by cvs2svn to compensate for changes in r4888,
[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 details
196
197 Returns an array of detail information for the invoice line item.
198
199 =cut
200
201 sub details {
202   my $self = shift;
203   return () unless defined dbdef->table('cust_bill_pkg_detail');
204   map { $_->detail }
205     qsearch ( 'cust_bill_pkg_detail', { 'pkgnum' => $self->pkgnum,
206                                         'invnum' => $self->invnum, } );
207     #qsearch ( 'cust_bill_pkg_detail', { 'lineitemnum' => $self->lineitemnum });
208 }
209
210 =item desc
211
212 Returns a description for this line item.  For typical line items, this is the
213 I<pkg> field of the corresponding B<FS::part_pkg> object (see L<FS::part_pkg>).
214 For one-shot line items and named taxes, it is the I<itemdesc> field of this
215 line item, and for generic taxes, simply returns "Tax".
216
217 =cut
218
219 sub desc {
220   my $self = shift;
221
222   if ( $self->pkgnum > 0 ) {
223     $self->cust_pkg->part_pkg->pkg;
224   } else {
225     $self->itemdesc || 'Tax';
226   }
227 }
228
229 =item owed_setup
230
231 Returns the amount owed (still outstanding) on this line item's setup fee,
232 which is the amount of the line item minus all payment applications (see
233 L<FS::cust_bill_pay_pkg> and credit applications (see
234 L<FS::cust_credit_bill_pkg>).
235
236 =cut
237
238 sub owed_setup {
239   my $self = shift;
240   $self->owed('setup', @_);
241 }
242
243 =item owed_recur
244
245 Returns the amount owed (still outstanding) on this line item's recurring fee,
246 which is the amount of the line item minus all payment applications (see
247 L<FS::cust_bill_pay_pkg> and credit applications (see
248 L<FS::cust_credit_bill_pkg>).
249
250 =cut
251
252 sub owed_recur {
253   my $self = shift;
254   $self->owed('recur', @_);
255 }
256
257 # modeled after cust_bill::owed...
258 sub owed {
259   my( $self, $field ) = @_;
260   my $balance = $self->$field();
261   $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg($field) );
262   $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg($field) );
263   $balance = sprintf( '%.2f', $balance );
264   $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
265   $balance;
266 }
267
268 sub cust_bill_pay_pkg {
269   my( $self, $field ) = @_;
270   qsearch( 'cust_bill_pay_pkg', { 'billpkgnum' => $self->billpkgnum,
271                                   'setuprecur' => $field,
272                                 }
273          );
274 }
275
276 sub cust_credit_bill_pkg {
277   my( $self, $field ) = @_;
278   qsearch( 'cust_credit_bill_pkg', { 'billpkgnum' => $self->billpkgnum,
279                                      'setuprecur' => $field,
280                                    }
281          );
282 }
283
284 =back
285
286 =head1 BUGS
287
288 setup and recur shouldn't be separate fields.  There should be one "amount"
289 field and a flag to tell you if it is a setup/one-time fee or a recurring fee.
290
291 A line item with both should really be two separate records (preserving
292 sdate and edate for setup fees for recurring packages - that information may
293 be valuable later).  Invoice generation (cust_main::bill), invoice printing
294 (cust_bill), tax reports (report_tax.cgi) and line item reports 
295 (cust_bill_pkg.cgi) would need to be updated.
296
297 owed_setup and owed_recur could then be repaced by just owed, and
298 cust_bill::open_cust_bill_pkg and
299 cust_bill_ApplicationCommon::apply_to_lineitems could be simplified.
300
301 =head1 SEE ALSO
302
303 L<FS::Record>, L<FS::cust_bill>, L<FS::cust_pkg>, L<FS::cust_main>, schema.html
304 from the base documentation.
305
306 =cut
307
308 1;
309