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;
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".
61 my( $self, $locale ) = @_;
63 if ( $self->pkgnum > 0 ) {
64 $self->itemdesc || $self->part_pkg->pkg_locale($locale);
66 my $desc = $self->itemdesc || 'Tax';
67 $desc .= ' '. $self->itemcomment if $self->itemcomment =~ /\S/;
72 =item time_period_pretty PART_PKG, AGENTNUM
74 Returns a formatted time period for this line item.
78 sub time_period_pretty {
79 my( $self, $part_pkg, $agentnum ) = @_;
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
85 return '' if $conf->exists('disable_line_item_date_ranges')
86 || $part_pkg->option('disable_line_item_date_ranges',1)
91 $date_style = $conf->config( 'cust_bill-line_item-date_style-non_monhtly',
94 if $part_pkg && $part_pkg->freq !~ /^1m?$/;
95 $date_style ||= $conf->config( 'cust_bill-line_item-date_style',
100 if ( defined($date_style) && $date_style eq 'month_of' ) {
101 # (now watch, someone's going to make us do Chinese)
102 $time_period = $self->mt('The month of [_1]',
103 $self->time2str_local('%B', $self->sdate)
105 } elsif ( defined($date_style) && $date_style eq 'X_month' ) {
106 my $desc = $conf->config( 'cust_bill-line_item-date_description',
109 $desc .= ' ' unless $desc =~ /\s$/;
110 $time_period = $desc. $self->time2str_local('%B', $self->sdate);
112 $time_period = $self->time2str_local($date_format, $self->sdate).
113 " - ". $self->time2str_local($date_format, $self->edate);
120 =item details [ OPTION => VALUE ... ]
122 Returns an array of detail information for the invoice line item.
124 Currently available options are: I<format>, I<escape_function> and
127 If I<format> is set to html or latex then the array members are improved
128 for tabular appearance in those environments if possible.
130 If I<escape_function> is set then the array members are processed by this
131 function before being returned.
133 I<format_function> overrides the normal HTML or LaTeX function for returning
134 formatted CDRs. It can be set to a subroutine which returns an empty list
135 to skip usage detail:
137 'format_function' => sub { () },
142 my ( $self, %opt ) = @_;
143 my $escape_function = $opt{escape_function} || sub { shift };
145 my $csv = new Text::CSV_XS;
147 if ( $opt{format_function} ) {
149 #this still expects to be passed a cust_bill_pkg_detail object as the
150 #second argument, which is expensive
151 carp "deprecated format_function passed to cust_bill_pkg->details";
152 my $format_sub = $opt{format_function} if $opt{format_function};
154 map { ( $_->format eq 'C'
155 ? &{$format_sub}( $_->detail, $_ )
156 : &{$escape_function}( $_->detail )
159 qsearch ({ 'table' => $self->detail_table,
160 'hashref' => { 'billpkgnum' => $self->billpkgnum },
161 'order_by' => 'ORDER BY detailnum',
164 } elsif ( $opt{'no_usage'} ) {
166 my $sql = "SELECT detail FROM ". $self->detail_table.
167 " WHERE billpkgnum = ". $self->billpkgnum.
168 " AND ( format IS NULL OR format != 'C' ) ".
169 " ORDER BY detailnum";
170 my $sth = dbh->prepare($sql) or die dbh->errstr;
171 $sth->execute or die $sth->errstr;
173 map &{$escape_function}( $_->[0] ), @{ $sth->fetchall_arrayref };
178 my $format = $opt{format} || '';
179 if ( $format eq 'html' ) {
181 $format_sub = sub { my $detail = shift;
182 $csv->parse($detail) or return "can't parse $detail";
183 join('</TD><TD>', map { &$escape_function($_) }
188 } elsif ( $format eq 'latex' ) {
192 $csv->parse($detail) or return "can't parse $detail";
193 #join(' & ', map { '\small{'. &$escape_function($_). '}' }
197 foreach ($csv->fields) {
198 $result .= ' & ' if $column > 1;
199 if ($column > 6) { # KLUDGE ALERT!
200 $result .= '\multicolumn{1}{l}{\scriptsize{'.
201 &$escape_function($_). '}}';
203 $result .= '\scriptsize{'. &$escape_function($_). '}';
212 $format_sub = sub { my $detail = shift;
213 $csv->parse($detail) or return "can't parse $detail";
214 join(' - ', map { &$escape_function($_) }
221 my $sql = "SELECT format, detail FROM ". $self->detail_table.
222 " WHERE billpkgnum = ". $self->billpkgnum.
223 " ORDER BY detailnum";
224 my $sth = dbh->prepare($sql) or die dbh->errstr;
225 $sth->execute or die $sth->errstr;
227 #avoid the fetchall_arrayref and loop for less memory usage?
229 map { (defined($_->[0]) && $_->[0] eq 'C')
230 ? &{$format_sub}( $_->[1] )
231 : &{$escape_function}( $_->[1] );
233 @{ $sth->fetchall_arrayref };
239 =item details_header [ OPTION => VALUE ... ]
241 Returns a list representing an invoice line item detail header, if any.
242 This relies on the behavior of voip_cdr in that it expects the header
243 to be the first CSV formatted detail (as is expected by invoice generation
244 routines). Returns the empty list otherwise.
251 my $csv = new Text::CSV_XS;
254 qsearch ({ 'table' => $self->detail_table,
255 'hashref' => { 'billpkgnum' => $self->billpkgnum,
258 'order_by' => 'ORDER BY detailnum LIMIT 1',
260 return() unless scalar(@detail);
261 $csv->parse($detail[0]->detail) or return ();
270 my( $self, $value ) = @_;
271 if ( defined($value) ) {
272 $self->setfield('quantity', $value);
274 $self->getfield('quantity') || 1;
282 my( $self, $value ) = @_;
283 if ( defined($value) ) {
284 $self->setfield('unitsetup', $value);
286 $self->getfield('unitsetup') eq ''
287 ? $self->getfield('setup')
288 : $self->getfield('unitsetup');
296 my( $self, $value ) = @_;
297 if ( defined($value) ) {
298 $self->setfield('unitrecur', $value);
300 $self->getfield('unitrecur') eq ''
301 ? $self->getfield('recur')
302 : $self->getfield('unitrecur');
305 =item cust_bill_pkg_display [ type => TYPE ]
307 Returns an array of display information for the invoice line item optionally
312 sub cust_bill_pkg_display {
313 my ( $self, %opt ) = @_;
315 my $class = 'FS::'. $self->display_table;
317 my $default = $class->new( { billpkgnum =>$self->billpkgnum } );
319 my $type = $opt{type} if exists $opt{type};
322 if ( $self->get('display') ) {
323 @result = grep { defined($type) ? ($type eq $_->type) : 1 }
324 @{ $self->get('display') };
326 my $hashref = { 'billpkgnum' => $self->billpkgnum };
327 $hashref->{type} = $type if defined($type);
329 my $order_by = $self->display_table_orderby || 'billpkgdisplaynum';
331 @result = qsearch ({ 'table' => $self->display_table,
332 'hashref' => $hashref,
333 'order_by' => "ORDER BY $order_by",
337 push @result, $default unless ( scalar(@result) || $type );
343 =item cust_bill_pkg_detail [ CLASSNUM ]
345 Returns the list of associated cust_bill_pkg_detail objects
346 The optional CLASSNUM argument will limit the details to the specified usage
351 sub cust_bill_pkg_detail {
353 my $classnum = shift || '';
355 my %hash = ( 'billpkgnum' => $self->billpkgnum );
356 $hash{classnum} = $classnum if $classnum;
358 qsearch( $self->detail_table, \%hash ),
362 =item cust_bill_pkg_discount
364 Returns the list of associated cust_bill_pkg_discount objects.
368 sub cust_bill_pkg_discount {
370 qsearch( $self->discount_table, { 'billpkgnum' => $self->billpkgnum } );