for prepaid, display the time period triggered by a 0 recurring with freq in the...
[freeside.git] / FS / FS / TemplateItem_Mixin.pm
1 package FS::TemplateItem_Mixin;
2
3 use strict;
4 use vars qw( $DEBUG $me $conf $date_format );
5 use Carp;
6 use Date::Format;
7 use FS::UID;
8 use FS::Record qw( qsearch qsearchs dbh );
9 use FS::Conf;
10 use FS::part_pkg;
11 use FS::cust_pkg;
12
13 $DEBUG = 0;
14 $me = '[FS::TemplateItem_Mixin]';
15 FS::UID->install_callback( sub { 
16   $conf = new FS::Conf;
17   $date_format      = $conf->config('date_format')      || '%x'; #/YY
18 } );
19
20 =item cust_pkg
21
22 Returns the package (see L<FS::cust_pkg>) for this invoice line item.
23
24 =cut
25
26 sub cust_pkg {
27   my $self = shift;
28   carp "$me $self -> cust_pkg" if $DEBUG;
29   qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
30 }
31
32 =item part_pkg
33
34 Returns the package definition for this invoice line item.
35
36 =cut
37
38 sub part_pkg {
39   my $self = shift;
40   if ( $self->pkgpart_override ) {
41     qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart_override } );
42   } else {
43     my $part_pkg;
44     my $cust_pkg = $self->cust_pkg;
45     $part_pkg = $cust_pkg->part_pkg if $cust_pkg;
46     $part_pkg;
47   }
48
49 }
50
51 =item desc
52
53 Returns a description for this line item.  For typical line items, this is the
54 I<pkg> field of the corresponding B<FS::part_pkg> object (see L<FS::part_pkg>).
55 For one-shot line items and named taxes, it is the I<itemdesc> field of this
56 line item, and for generic taxes, simply returns "Tax".
57
58 =cut
59
60 sub desc {
61   my( $self, $locale ) = @_;
62
63   if ( $self->pkgnum > 0 ) {
64     $self->itemdesc || $self->part_pkg->pkg_locale($locale);
65   } else {
66     my $desc = $self->itemdesc || 'Tax';
67     $desc .= ' '. $self->itemcomment if $self->itemcomment =~ /\S/;
68     $desc;
69   }
70 }
71
72 =item time_period_pretty PART_PKG, AGENTNUM
73
74 Returns a formatted time period for this line item.
75
76 =cut
77
78 sub time_period_pretty {
79   my( $self, $part_pkg, $agentnum ) = @_;
80
81   #more efficient to look some of this conf stuff up outside the
82   # invoice/template display loop we're called from
83   # (Template_Mixin::_invoice_cust_bill_pkg) and pass them in as options
84
85   return '' if $conf->exists('disable_line_item_date_ranges')
86             || $part_pkg->option('disable_line_item_date_ranges',1)
87             || ! $self->sdate
88             || ! $self->edate;
89
90   my $date_style = '';
91   $date_style = $conf->config( 'cust_bill-line_item-date_style-non_monhtly',
92                                $agentnum
93                              )
94     if $part_pkg && $part_pkg->freq !~ /^1m?$/;
95   $date_style ||= $conf->config( 'cust_bill-line_item-date_style',
96                                   $agentnum
97                                );
98
99   my $time_period;
100   if ( defined($date_style) && $date_style eq 'month_of' ) {
101     $time_period = time2str('The month of %B', $self->sdate);
102   } elsif ( defined($date_style) && $date_style eq 'X_month' ) {
103     my $desc = $conf->config( 'cust_bill-line_item-date_description',
104                                $agentnum
105                             );
106     $desc .= ' ' unless $desc =~ /\s$/;
107     $time_period = $desc. time2str('%B', $self->sdate);
108   } else {
109     $time_period =      time2str($date_format, $self->sdate).
110                  " - ". time2str($date_format, $self->edate);
111   }
112
113   " ($time_period)";
114
115 }
116
117 =item details [ OPTION => VALUE ... ]
118
119 Returns an array of detail information for the invoice line item.
120
121 Currently available options are: I<format>, I<escape_function> and
122 I<format_function>.
123
124 If I<format> is set to html or latex then the array members are improved
125 for tabular appearance in those environments if possible.
126
127 If I<escape_function> is set then the array members are processed by this
128 function before being returned.
129
130 I<format_function> overrides the normal HTML or LaTeX function for returning
131 formatted CDRs.  It can be set to a subroutine which returns an empty list
132 to skip usage detail:
133
134   'format_function' => sub { () },
135
136 =cut
137
138 sub details {
139   my ( $self, %opt ) = @_;
140   my $escape_function = $opt{escape_function} || sub { shift };
141
142   my $csv = new Text::CSV_XS;
143
144   if ( $opt{format_function} ) {
145
146     #this still expects to be passed a cust_bill_pkg_detail object as the
147     #second argument, which is expensive
148     carp "deprecated format_function passed to cust_bill_pkg->details";
149     my $format_sub = $opt{format_function} if $opt{format_function};
150
151     map { ( $_->format eq 'C'
152               ? &{$format_sub}( $_->detail, $_ )
153               : &{$escape_function}( $_->detail )
154           )
155         }
156       qsearch ({ 'table'    => $self->detail_table,
157                  'hashref'  => { 'billpkgnum' => $self->billpkgnum },
158                  'order_by' => 'ORDER BY detailnum',
159               });
160
161   } elsif ( $opt{'no_usage'} ) {
162
163     my $sql = "SELECT detail FROM ". $self->detail_table.
164               "  WHERE billpkgnum = ". $self->billpkgnum.
165               "    AND ( format IS NULL OR format != 'C' ) ".
166               "  ORDER BY detailnum";
167     my $sth = dbh->prepare($sql) or die dbh->errstr;
168     $sth->execute or die $sth->errstr;
169
170     map &{$escape_function}( $_->[0] ), @{ $sth->fetchall_arrayref };
171
172   } else {
173
174     my $format_sub;
175     my $format = $opt{format} || '';
176     if ( $format eq 'html' ) {
177
178       $format_sub = sub { my $detail = shift;
179                           $csv->parse($detail) or return "can't parse $detail";
180                           join('</TD><TD>', map { &$escape_function($_) }
181                                             $csv->fields
182                               );
183                         };
184
185     } elsif ( $format eq 'latex' ) {
186
187       $format_sub = sub {
188         my $detail = shift;
189         $csv->parse($detail) or return "can't parse $detail";
190         #join(' & ', map { '\small{'. &$escape_function($_). '}' }
191         #            $csv->fields );
192         my $result = '';
193         my $column = 1;
194         foreach ($csv->fields) {
195           $result .= ' & ' if $column > 1;
196           if ($column > 6) {                     # KLUDGE ALERT!
197             $result .= '\multicolumn{1}{l}{\scriptsize{'.
198                        &$escape_function($_). '}}';
199           }else{
200             $result .= '\scriptsize{'.  &$escape_function($_). '}';
201           }
202           $column++;
203         }
204         $result;
205       };
206
207     } else {
208
209       $format_sub = sub { my $detail = shift;
210                           $csv->parse($detail) or return "can't parse $detail";
211                           join(' - ', map { &$escape_function($_) }
212                                       $csv->fields
213                               );
214                         };
215
216     }
217
218     my $sql = "SELECT format, detail FROM ". $self->detail_table.
219               "  WHERE billpkgnum = ". $self->billpkgnum.
220               "  ORDER BY detailnum";
221     my $sth = dbh->prepare($sql) or die dbh->errstr;
222     $sth->execute or die $sth->errstr;
223
224     #avoid the fetchall_arrayref and loop for less memory usage?
225
226     map { (defined($_->[0]) && $_->[0] eq 'C')
227             ? &{$format_sub}(      $_->[1] )
228             : &{$escape_function}( $_->[1] );
229         }
230       @{ $sth->fetchall_arrayref };
231
232   }
233
234 }
235
236 =item details_header [ OPTION => VALUE ... ]
237
238 Returns a list representing an invoice line item detail header, if any.
239 This relies on the behavior of voip_cdr in that it expects the header
240 to be the first CSV formatted detail (as is expected by invoice generation
241 routines).  Returns the empty list otherwise.
242
243 =cut
244
245 sub details_header {
246   my $self = shift;
247
248   my $csv = new Text::CSV_XS;
249
250   my @detail = 
251     qsearch ({ 'table'    => $self->detail_table,
252                'hashref'  => { 'billpkgnum' => $self->billpkgnum,
253                                'format'     => 'C',
254                              },
255                'order_by' => 'ORDER BY detailnum LIMIT 1',
256             });
257   return() unless scalar(@detail);
258   $csv->parse($detail[0]->detail) or return ();
259   $csv->fields;
260 }
261
262 =item quantity
263
264 =cut
265
266 sub quantity {
267   my( $self, $value ) = @_;
268   if ( defined($value) ) {
269     $self->setfield('quantity', $value);
270   }
271   $self->getfield('quantity') || 1;
272 }
273
274 =item unitsetup
275
276 =cut
277
278 sub unitsetup {
279   my( $self, $value ) = @_;
280   if ( defined($value) ) {
281     $self->setfield('unitsetup', $value);
282   }
283   $self->getfield('unitsetup') eq ''
284     ? $self->getfield('setup')
285     : $self->getfield('unitsetup');
286 }
287
288 =item unitrecur
289
290 =cut
291
292 sub unitrecur {
293   my( $self, $value ) = @_;
294   if ( defined($value) ) {
295     $self->setfield('unitrecur', $value);
296   }
297   $self->getfield('unitrecur') eq ''
298     ? $self->getfield('recur')
299     : $self->getfield('unitrecur');
300 }
301
302 =item cust_bill_pkg_display [ type => TYPE ]
303
304 Returns an array of display information for the invoice line item optionally
305 limited to 'TYPE'.
306
307 =cut
308
309 sub cust_bill_pkg_display {
310   my ( $self, %opt ) = @_;
311
312   my $class = 'FS::'. $self->display_table;
313
314   my $default = $class->new( { billpkgnum =>$self->billpkgnum } );
315
316   my $type = $opt{type} if exists $opt{type};
317   my @result;
318
319   if ( $self->get('display') ) {
320     @result = grep { defined($type) ? ($type eq $_->type) : 1 }
321               @{ $self->get('display') };
322   } else {
323     my $hashref = { 'billpkgnum' => $self->billpkgnum };
324     $hashref->{type} = $type if defined($type);
325
326     my $order_by = $self->display_table_orderby || 'billpkgdisplaynum';
327     
328     @result = qsearch ({ 'table'    => $self->display_table,
329                          'hashref'  => $hashref,
330                          'order_by' => "ORDER BY $order_by",
331                       });
332   }
333
334   push @result, $default unless ( scalar(@result) || $type );
335
336   @result;
337
338 }
339
340 =item cust_bill_pkg_detail [ CLASSNUM ]
341
342 Returns the list of associated cust_bill_pkg_detail objects
343 The optional CLASSNUM argument will limit the details to the specified usage
344 class.
345
346 =cut
347
348 sub cust_bill_pkg_detail {
349   my $self = shift;
350   my $classnum = shift || '';
351
352   my %hash = ( 'billpkgnum' => $self->billpkgnum );
353   $hash{classnum} = $classnum if $classnum;
354
355   qsearch( $self->detail_table, \%hash ),
356
357 }
358
359 =item cust_bill_pkg_discount 
360
361 Returns the list of associated cust_bill_pkg_discount objects.
362
363 =cut
364
365 sub cust_bill_pkg_discount {
366   my $self = shift;
367   qsearch( $self->discount_table, { 'billpkgnum' => $self->billpkgnum } );
368 }
369
370 1;