use HTML::Entities;
use Locale::Country;
use Storable qw( freeze thaw );
+use GD::Barcode;
use FS::UID qw( datasrc );
use FS::Misc qw( send_email send_fax generate_ps generate_pdf do_print );
use FS::Record qw( qsearch qsearchs dbh );
use FS::payby;
use FS::bill_batch;
use FS::cust_bill_batch;
+use Cwd;
@ISA = qw( FS::cust_main_Mixin FS::Record );
'Filename' => 'logo.png',
'Content-ID' => "<$content_id>",
;
+
+ my $barcode;
+ if($conf->exists('invoice-barcode')){
+ my $barcode_content_id = join('.', rand()*(2**32), $$, time). "\@$from";
+ $barcode = build MIME::Entity
+ 'Type' => 'image/png',
+ 'Encoding' => 'base64',
+ 'Data' => $self->invoice_barcode(0),
+ 'Filename' => 'barcode.png',
+ 'Content-ID' => "<$barcode_content_id>",
+ ;
+ $opt{'barcode_cid'} = $barcode_content_id;
+ }
$alternative->attach(
'Type' => 'text/html',
# image/png
$return{'content-type'} = 'multipart/related';
- $return{'mimeparts'} = [ $alternative, $image, @otherparts ];
+ if($conf->exists('invoice-barcode')){
+ $return{'mimeparts'} = [ $alternative, $image, $barcode, @otherparts ];
+ }
+ else {
+ $return{'mimeparts'} = [ $alternative, $image, @otherparts ];
+ }
$return{'type'} = 'multipart/alternative'; #Content-Type of first part...
#$return{'disposition'} = 'inline';
close $lh;
$params{'logo_file'} = $lh->filename;
+ if($conf->exists('invoice-barcode')){
+ my $png_file = $self->invoice_barcode($dir);
+ my $eps_file = $png_file;
+ $eps_file =~ s/\.png$/.eps/g;
+ $png_file =~ /(barcode.*png)/;
+ $png_file = $1;
+ $eps_file =~ /(barcode.*eps)/;
+ $eps_file = $1;
+
+ my $curr_dir = cwd();
+ chdir($dir);
+ # after painfuly long experimentation, it was determined that sam2p won't
+ # accept : and other chars in the path, no matter how hard I tried to
+ # escape them, hence the chdir (and chdir back, just to be safe)
+ system('sam2p', '-j:quiet', $png_file, 'EPS:', $eps_file ) == 0
+ or die "sam2p failed: $!\n";
+ unlink($png_file);
+ chdir($curr_dir);
+
+ $params{'barcode_file'} = $eps_file;
+ }
+
my @filled_in = $self->print_generic( %params );
my $fh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX',
close $fh;
$fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
- return ($1, $params{'logo_file'});
+ return ($1, $params{'logo_file'}, $params{'barcode_file'});
}
+=item invoice_barcode DIR_OR_FALSE
+
+Generates an invoice barcode PNG. If DIR_OR_FALSE is a true value,
+it is taken as the temp directory where the PNG file will be generated and the
+PNG file name is returned. Otherwise, the PNG image itself is returned.
+
+=cut
+
+sub invoice_barcode {
+ my ($self, $dir) = (shift,shift);
+
+ my $gdbar = new GD::Barcode('Code39',$self->invnum);
+ die "can't create barcode: " . $GD::Barcode::errStr unless $gdbar;
+ my $gd = $gdbar->plot(Height => 30);
+
+ if($dir) {
+ my $bh = new File::Temp( TEMPLATE => 'barcode.'. $self->invnum. '.XXXXXXXX',
+ DIR => $dir,
+ SUFFIX => '.png',
+ UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+ print $bh $gd->png or die "cannot write barcode to file: $!\n";
+ my $png_file = $bh->filename;
+ close $bh;
+ return $png_file;
+ }
+ return $gd->png;
+}
+
=item print_generic OPTION => VALUE ...
Internal method - returns a filled-in template for this invoice as a scalar.
$invoice_data{'logo_file'} = $params{'logo_file'}
if $params{'logo_file'};
+ $invoice_data{'barcode_file'} = $params{'barcode_file'}
+ if $params{'barcode_file'};
+ $invoice_data{'barcode_img'} = $params{'barcode_img'}
+ if $params{'barcode_img'};
+ $invoice_data{'barcode_cid'} = $params{'barcode_cid'}
+ if $params{'barcode_cid'};
my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
sub print_ps {
my $self = shift;
- my ($file, $lfile) = $self->print_latex(@_);
+ my ($file, $logofile, $barcodefile) = $self->print_latex(@_);
my $ps = generate_ps($file);
- unlink($lfile);
+ unlink($logofile);
+ unlink($barcodefile);
$ps;
}
sub print_pdf {
my $self = shift;
- my ($file, $lfile) = $self->print_latex(@_);
+ my ($file, $logofile, $barcodefile) = $self->print_latex(@_);
my $pdf = generate_pdf($file);
- unlink($lfile);
+ unlink($logofile);
+ unlink($barcodefile);
$pdf;
}
}
$params{'format'} = 'html';
-
+
$self->print_generic( %params );
}
sub _items_cust_bill_pkg {
my $self = shift;
- my $cust_bill_pkg = shift;
+ my $cust_bill_pkgs = shift;
my %opt = @_;
my $format = $opt{format} || '';
my @b = ();
my ($s, $r, $u) = ( undef, undef, undef );
- foreach my $cust_bill_pkg ( @$cust_bill_pkg )
+ foreach my $cust_bill_pkg ( @$cust_bill_pkgs )
{
+ warn "$me _items_cust_bill_pkg considering cust_bill_pkg $cust_bill_pkg\n"
+ if $DEBUG > 1;
+
$discount_show_always = ($cust_bill_pkg->cust_bill_pkg_discount
- && $conf->exists('discount-show-always'));
+ && $conf->exists('discount-show-always'));
foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) {
if ( $_ && !$cust_bill_pkg->hidden ) {
)
{
+ warn "$me _items_cust_bill_pkg considering display item $display\n"
+ if $DEBUG > 1;
+
my $type = $display->type;
my $desc = $cust_bill_pkg->desc;
if ( $cust_bill_pkg->pkgnum > 0 ) {
+ warn "$me _items_cust_bill_pkg cust_bill_pkg is non-tax\n"
+ if $DEBUG > 1;
+
my $cust_pkg = $cust_bill_pkg->cust_pkg;
if ( $cust_bill_pkg->setup != 0 && (!$type || $type eq 'S') ) {
+ warn "$me _items_cust_bill_pkg adding setup\n"
+ if $DEBUG > 1;
+
my $description = $desc;
$description .= ' Setup' if $cust_bill_pkg->recur != 0;
)
{
+ warn "$me _items_cust_bill_pkg adding recur/usage\n"
+ if $DEBUG > 1;
+
my $is_summary = $display->summary;
my $description = ($is_summary && $type && $type eq 'U')
? "Usage charges" : $desc;
- unless ( $conf->exists('disable_line_item_date_ranges') ) {
- $description .= " (" . time2str($date_format, $cust_bill_pkg->sdate).
- " - ". time2str($date_format, $cust_bill_pkg->edate). ")";
- }
+ $description .= " (" . time2str($date_format, $cust_bill_pkg->sdate).
+ " - ". time2str($date_format, $cust_bill_pkg->edate).
+ ")"
+ unless $conf->exists('disable_line_item_date_ranges');
my @d = ();
|| $is_summary && $type && $type eq 'U' )
{
+ warn "$me _items_cust_bill_pkg adding service details\n"
+ if $DEBUG > 1;
+
push @d, map &{$escape_function}($_),
$cust_pkg->h_labels_short(@dates, 'I')
#$cust_bill_pkg->edate,
#$cust_bill_pkg->sdate)
unless $cust_bill_pkg->pkgpart_override; #don't redisplay services
+ warn "$me _items_cust_bill_pkg done adding service details\n"
+ if $DEBUG > 1;
+
if ( $multilocation ) {
my $loc = $cust_pkg->location_label;
$loc = substr($loc, 0, 50). '...'
}
+ warn "$me _items_cust_bill_pkg adding details\n"
+ if $DEBUG > 1;
+
push @d, $cust_bill_pkg->details(%details_opt)
unless ($is_summary || $type && $type eq 'R');
+
+ warn "$me _items_cust_bill_pkg calculating amount\n"
+ if $DEBUG > 1;
my $amount = 0;
if (!$type) {
if ( !$type || $type eq 'R' ) {
+ warn "$me _items_cust_bill_pkg adding recur\n"
+ if $DEBUG > 1;
+
if ( $cust_bill_pkg->hidden ) {
$r->{amount} += $amount;
$r->{unit_amount} += $cust_bill_pkg->unitrecur;
} else { # $type eq 'U'
+ warn "$me _items_cust_bill_pkg adding usage\n"
+ if $DEBUG > 1;
+
if ( $cust_bill_pkg->hidden ) {
$u->{amount} += $amount;
$u->{unit_amount} += $cust_bill_pkg->unitrecur;
} else { #pkgnum tax or one-shot line item (??)
+ warn "$me _items_cust_bill_pkg cust_bill_pkg is tax\n"
+ if $DEBUG > 1;
+
if ( $cust_bill_pkg->setup != 0 ) {
push @b, {
'description' => $desc,
}
+ warn "$me _items_cust_bill_pkg done considering cust_bill_pkgs\n"
+ if $DEBUG > 1;
+
foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) {
if ( $_ ) {
$_->{amount} = sprintf( "%.2f", $_->{amount} ),
$_->{amount} =~ s/^\-0\.00$/0.00/;
$_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ),
push @b, { %$_ }
- unless ( $_->{amount} == 0 && !$discount_show_always );
+ unless ( $_->{amount} == 0 && !$discount_show_always );
}
}