1 package FS::TemplateItem_Mixin;
4 use vars qw( $DEBUG $me $conf $date_format );
8 use FS::Record qw( qsearch qsearchs dbh );
14 $me = '[FS::TemplateItem_Mixin]';
15 FS::UID->install_callback( sub {
17 $date_format = $conf->config('date_format') || '%x'; #/YY
22 Returns the package (see L<FS::cust_pkg>) for this invoice line item.
28 carp "$me $self -> cust_pkg" if $DEBUG;
29 qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
34 Returns the package definition for this invoice line item.
40 if ( $self->pkgpart_override ) {
41 qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart_override } );
44 my $cust_pkg = $self->cust_pkg;
45 $part_pkg = $cust_pkg->part_pkg if $cust_pkg;
52 Returns the fee definition for this line item, if there is one.
59 ? FS::part_fee->by_key($self->feepart)
65 Returns L</part_pkg> or L</part_fee>, whichever is applicable (or nothing,
66 if called on a tax line item).
72 $self->part_pkg || $self->part_fee;
77 Returns a description for this line item. For typical line items, this is the
78 I<pkg> field of the corresponding B<FS::part_pkg> object (see L<FS::part_pkg>).
79 For one-shot line items and named taxes, it is the I<itemdesc> field of this
80 line item, and for generic taxes, simply returns "Tax".
85 my( $self, $locale ) = @_;
87 if ( $self->pkgnum > 0 ) {
88 return $self->itemdesc if $self->itemdesc;
89 my $part_pkg = $self->part_pkg or return 'UNKNOWN';
90 return $part_pkg->pkg_locale($locale);
92 } elsif ( $self->feepart ) {
93 return $self->part_fee->itemdesc_locale($locale);
95 } else { # by the process of elimination it must be a tax
96 my $desc = $self->itemdesc || 'Tax';
97 $desc .= ' '. $self->itemcomment if $self->itemcomment =~ /\S/;
103 =item time_period_pretty PART_PKG, AGENTNUM
105 Returns a formatted time period for this line item.
109 sub time_period_pretty {
110 my( $self, $part_pkg, $agentnum, %opt ) = @_;
112 #more efficient to look some of this conf stuff up outside the
113 # invoice/template display loop we're called from
114 # (Template_Mixin::_invoice_cust_bill_pkg) and pass them in as options
116 return '' if $opt{'disable_line_item_date_ranges'}
121 $date_style = $conf->config( 'cust_bill-line_item-date_style-non_monhtly',
124 if $part_pkg && $part_pkg->freq !~ /^1m?$/;
125 $date_style ||= $conf->config( 'cust_bill-line_item-date_style',
130 if ( defined($date_style) && $date_style eq 'month_of' ) {
131 # (now watch, someone's going to make us do Chinese)
132 $time_period = $self->mt('The month of [_1]',
133 $self->time2str_local('%B', $self->sdate)
135 } elsif ( defined($date_style) && $date_style eq 'X_month' ) {
136 my $desc = $conf->config( 'cust_bill-line_item-date_description',
139 $desc .= ' ' unless $desc =~ /\s$/;
140 $time_period = $desc. $self->time2str_local('%B', $self->sdate);
142 $time_period = $self->time2str_local($date_format, $self->sdate).
143 " - ". $self->time2str_local($date_format, $self->edate);
150 =item details [ OPTION => VALUE ... ]
152 Returns an array of detail information for the invoice line item.
156 I<format>: set to 'html' or 'latex' to have the detail lines formatted for
157 inclusion in an HTML table (wrapped in <tr> and <td> elements) or LaTeX table
158 (delimited with & and \\ operators).
160 I<escape_function>: if present, then the array elements are processed by this
161 function before being returned.
163 I<format_function>: overrides the normal HTML or LaTeX function for returning
166 I<no_usage>: excludes call detail records. The method will still return
167 some special-case records like prorate details, and manually created package
173 my ( $self, %opt ) = @_;
174 my $escape_function = $opt{escape_function} || sub { shift };
176 my $csv = new Text::CSV_XS;
177 my $key = $self->primary_key;
179 if ( $opt{format_function} ) {
181 #this still expects to be passed a cust_bill_pkg_detail object as the
182 #second argument, which is expensive
183 carp "deprecated format_function passed to cust_bill_pkg->details";
184 my $format_sub = $opt{format_function} if $opt{format_function};
186 map { ( $_->format eq 'C'
187 ? &{$format_sub}( $_->detail, $_ )
188 : &{$escape_function}( $_->detail )
191 qsearch ({ 'table' => $self->detail_table,
192 'hashref' => { $key => $self->get($key) },
193 'order_by' => 'ORDER BY detailnum',
196 } elsif ( $opt{'no_usage'} ) {
198 my $sql = "SELECT detail FROM ". $self->detail_table.
199 " WHERE " . $key . " = ". $self->get($key).
200 " AND ( format IS NULL OR format != 'C' ) ".
201 " ORDER BY detailnum";
202 my $sth = dbh->prepare($sql) or die dbh->errstr;
203 $sth->execute or die $sth->errstr;
205 map &{$escape_function}( $_->[0] ), @{ $sth->fetchall_arrayref };
210 my $format = $opt{format} || '';
211 if ( $format eq 'html' ) {
213 $format_sub = sub { my $detail = shift;
214 $csv->parse($detail) or return "can't parse $detail";
215 join('</TD><TD>', map { &$escape_function($_) }
220 } elsif ( $format eq 'latex' ) {
224 $csv->parse($detail) or return "can't parse $detail";
225 #join(' & ', map { '\small{'. &$escape_function($_). '}' }
229 foreach ($csv->fields) {
230 $result .= ' & ' if $column > 1;
231 if ($column > 6) { # KLUDGE ALERT!
232 $result .= '\multicolumn{1}{l}{\scriptsize{'.
233 &$escape_function($_). '}}';
235 $result .= '\scriptsize{'. &$escape_function($_). '}';
244 $format_sub = sub { my $detail = shift;
245 $csv->parse($detail) or return "can't parse $detail";
246 join(' - ', map { &$escape_function($_) }
253 my $sql = "SELECT format, detail FROM ". $self->detail_table.
254 " WHERE " . $key . " = ". $self->get($key).
255 " ORDER BY detailnum";
256 my $sth = dbh->prepare($sql) or die dbh->errstr;
257 $sth->execute or die $sth->errstr;
259 #avoid the fetchall_arrayref and loop for less memory usage?
260 # probably should use a cursor...
266 if (defined($row->[0]) and $row->[0] eq 'C') {
268 # first CSV row = the format header; localize it but not the others
269 $row->[1] = $self->mt($row->[1]);
272 &{$format_sub}($row->[1]);
274 &{$escape_function}($row->[1]);
276 } @{ $sth->fetchall_arrayref };
278 } #!$opt{format_function}
282 =item details_header [ OPTION => VALUE ... ]
284 Returns a list representing an invoice line item detail header, if any.
285 This relies on the behavior of voip_cdr in that it expects the header
286 to be the first CSV formatted detail (as is expected by invoice generation
287 routines). Returns the empty list otherwise.
294 my $csv = new Text::CSV_XS;
297 qsearch ({ 'table' => $self->detail_table,
298 'hashref' => { 'billpkgnum' => $self->billpkgnum,
301 'order_by' => 'ORDER BY detailnum LIMIT 1',
303 return() unless scalar(@detail);
304 $csv->parse($detail[0]->detail) or return ();
313 my( $self, $value ) = @_;
314 if ( defined($value) ) {
315 $self->setfield('quantity', $value);
317 $self->getfield('quantity') || 1;
325 my( $self, $value ) = @_;
326 if ( defined($value) ) {
327 $self->setfield('unitsetup', $value);
329 $self->getfield('unitsetup') eq ''
330 ? $self->getfield('setup')
331 : $self->getfield('unitsetup');
339 my( $self, $value ) = @_;
340 if ( defined($value) ) {
341 $self->setfield('unitrecur', $value);
343 $self->getfield('unitrecur') eq ''
344 ? $self->getfield('recur')
345 : $self->getfield('unitrecur');
348 =item cust_bill_pkg_display [ type => TYPE ]
350 Returns an array of display information for the invoice line item optionally
355 sub cust_bill_pkg_display {
356 my ( $self, %opt ) = @_;
358 my $class = 'FS::'. $self->display_table;
360 my $default = $class->new( { billpkgnum =>$self->billpkgnum } );
362 my $type = $opt{type} if exists $opt{type};
365 if ( $self->get('display') ) {
366 @result = grep { defined($type) ? ($type eq $_->type) : 1 }
367 @{ $self->get('display') };
369 my $hashref = { 'billpkgnum' => $self->billpkgnum };
370 $hashref->{type} = $type if defined($type);
372 my $order_by = $self->display_table_orderby || 'billpkgdisplaynum';
374 @result = qsearch ({ 'table' => $self->display_table,
375 'hashref' => $hashref,
376 'order_by' => "ORDER BY $order_by",
380 push @result, $default unless ( scalar(@result) || $type );
386 =item cust_bill_pkg_detail [ CLASSNUM ]
388 Returns the list of associated cust_bill_pkg_detail objects
389 The optional CLASSNUM argument will limit the details to the specified usage
394 sub cust_bill_pkg_detail {
396 my $classnum = shift || '';
398 my %hash = ( 'billpkgnum' => $self->billpkgnum );
399 $hash{classnum} = $classnum if $classnum;
401 qsearch( $self->detail_table, \%hash ),
407 Returns the list of associated cust_bill_pkg_discount or
408 quotation_pkg_discount objects.
414 my $pkey = $self->primary_key;
415 qsearch( $self->discount_table, { $pkey => $self->get($pkey) } );