Commit 892e792b authored by Nigel Kukard's avatar Nigel Kukard

Added PDF invoicing

parent ec61910a
......@@ -40,9 +40,11 @@ use wiaflos::server::core::Tax;
use Math::BigFloat;
Math::BigFloat::precision(-2);
use Crypt::GPG;
use Date::Parse;
use DateTime;
use Crypt::GPG;
use File::Copy;
use File::Temp;
use MIME::Lite;
......@@ -1181,28 +1183,31 @@ sub sendInvoice
# Build array of stuff we can use
my $vars = {
'WiaflosString' => $GENSTRING,
'WiaflosString' => $GENSTRING,
# Document type
'Title' => $proforma eq "yes" ? "PROFORMA INVOICE" : "TAX INVOICE COPY",
# Client
'ClientName' => $client->{'Name'},
'ClientCode' => $client->{'Code'},
'ClientBillingAddress' => $billAddr,
'ClientName' => $client->{'Name'},
'ClientCode' => $client->{'Code'},
'ClientBillingAddress' => $billAddr,
'ClientShippingAddress' => $shipAddr,
# Invoice
'ClientTaxReference' => defined($invoice->{'TaxReference'}) ? $invoice->{'TaxReference'} : "",
'ClientTaxReference' => defined($invoice->{'TaxReference'}) ? $invoice->{'TaxReference'} : "",
'InvoiceNumber' => $invoice->{'Number'},
'InvoiceOrderNumber' => defined($invoice->{'OrderNumber'}) ? $invoice->{'OrderNumber'} : "",
'InvoiceIssueDate' => $invoice->{'IssueDate'},
'InvoiceDueDate' => $invoice->{'DueDate'},
'InvoiceNote' => defined($invoice->{'Note'}) ? $invoice->{'Note'} : "",
'InvoiceNumber' => $invoice->{'Number'},
'InvoiceOrderNumber' => defined($invoice->{'OrderNumber'}) ? $invoice->{'OrderNumber'} : "",
'InvoiceIssueDate' => $invoice->{'IssueDate'},
'InvoiceDueDate' => $invoice->{'DueDate'},
'InvoiceNote' => defined($invoice->{'Note'}) ? $invoice->{'Note'} : "",
'InvoiceTotalExcl' => sprintf('%.2f',$invoice->{'SubTotal'}),
'InvoiceDiscountTotal' => (defined($invoice->{'DiscountTotal'}) && $invoice->{'DiscountTotal'} ne "0")
'InvoiceTotalExcl' => sprintf('%.2f',$invoice->{'SubTotal'}),
'InvoiceDiscountTotal' => (defined($invoice->{'DiscountTotal'}) && $invoice->{'DiscountTotal'} ne "0")
? sprintf('%.2f',$invoice->{'DiscountTotal'}) : "",
'InvoiceTotalTaxAmount' => sprintf('%.2f',$invoice->{'TaxTotal'}),
'InvoiceTotalIncl' => sprintf('%.2f',$invoice->{'Total'}),
'InvoiceTotalIncl' => sprintf('%.2f',$invoice->{'Total'}),
};
# Load invoice line items
......@@ -1223,20 +1228,46 @@ sub sendInvoice
push(@{$vars->{'LineItems'}},$titem);
}
my $tempDir = File::Temp->newdir();
# Invoice filename
my $invoiceFilename = sprintf('%s.%s', $invoice->{'Number'}, $format);
$invoiceFilename =~ s,/,-,g;
# Path of the invoice file we're generating
my $invoicePath = sprintf('%s/%s',$tempDir,$invoiceFilename);
# Get invoice template file
my $template = defined($config->{'invoicing'}{'email_template'}) ? $config->{'invoicing'}{'email_template'} : "invoices/invoice1.tt2";
#
# Generate invoice
#
# Load template
my $res = loadTemplate2($template,$vars,$invoicePath,$format);
if (!$res) {
setError("Failed to load template '$template': ".wiaflos::server::core::templating::Error());
return ERR_SRVTEMPLATE;
}
#
# SendTo file
#
# Check where invoice must go
if ($detail->{'SendTo'} =~ /^file:(\S+)/i) {
my $filename = $1;
my $sendToPath = $1;
# Load template
my $res = loadTemplate($template,$vars,$filename);
if (!$res) {
setError("Failed to load template '$template': ".wiaflos::server::core::templating::Error());
return ERR_SRVTEMPLATE;
if (!copy($invoicePath,$sendToPath)) {
setError("Failed to copy '$invoicePath' to '$sendToPath': $!");
return;
}
#
# SendTo email
#
# Write out using email
} elsif ($detail->{'SendTo'} =~ /^email(?:\:(\S+))?/i) {
# Pull email addr
......@@ -1249,11 +1280,13 @@ sub sendInvoice
return ERR_PARAM;
}
# Invoice filename
(my $invoiceFilename = $invoice->{'Number'} . ".html") =~ s,/,-,g;
# Invoice signature filename
my $invSignFilename = $invoiceFilename . ".asc";
#
# EMAIL BODY
#
# If we must, pull in email body
my $message_template = $config->{'invoicing'}{'email_message_template'};
my $emailBody = "";
......@@ -1270,72 +1303,80 @@ sub sendInvoice
return ERR_SRVTEMPLATE;
}
$emailBody =~ s/(?<!\r)\n/\r\n/sg; # Sanitize eol for crypt-gpg
# $emailBody =~ s/(?<!\r)\n/\r\n/sg; # Sanitize eol for crypt-gpg
}
# Load template
my $invoiceData = "";
my $res = loadTemplate($template,$vars,\$invoiceData);
if (!$res) {
setError("Failed to load template '$template': ".wiaflos::server::core::templating::Error());
return ERR_SRVTEMPLATE;
}
$invoiceData =~ s/(?<!\r)\n/\r\n/sg; # Sanitize eol, needed to fix bug in crypt-gpg where it mangles \n
# See if we must use GPG
my $use_gpg_key = $config->{'invoicing'}{'use_gpg_key'};
my $sign;
if (defined($use_gpg_key) && $use_gpg_key ne "") {
# Setup GnuPG
my $gpg = new Crypt::GPG;
$gpg->gpgbin("/usr/bin/gpg");
$gpg->secretkey($use_gpg_key);
$gpg->armor(1);
# Sign invoice
$sign = $gpg->sign($invoiceData);
if (!defined($sign)) {
setError("Failed to sign invoice '".$invoice->{'Number'}."'");
return ERR_SRVEXEC;
}
}
# # Load template
# my $invoiceData = "";
# my $res = loadTemplate($template,$vars,\$invoiceData);
# if (!$res) {
# setError("Failed to load template '$template': ".wiaflos::server::core::templating::Error());
# return ERR_SRVTEMPLATE;
# }
# $invoiceData =~ s/(?<!\r)\n/\r\n/sg; # Sanitize eol, needed to fix bug in crypt-gpg where it mangles \n
#
# # See if we must use GPG
# my $use_gpg_key = $config->{'invoicing'}{'use_gpg_key'};
# my $sign;
# if (defined($use_gpg_key) && $use_gpg_key ne "") {
# # Setup GnuPG
# my $gpg = new Crypt::GPG;
# $gpg->gpgbin("/usr/bin/gpg");
# $gpg->secretkey($use_gpg_key);
# $gpg->armor(1);
# # Sign invoice
# $sign = $gpg->sign($invoiceData);
# if (!defined($sign)) {
# setError("Failed to sign invoice '".$invoice->{'Number'}."'");
# return ERR_SRVEXEC;
# }
# }
# Create message
my $msg = MIME::Lite->new(
From => $config->{'invoicing'}{'email_from'},
To => $emailAddy,
Bcc => $config->{'invoicing'}{'email_bcc'},
'Reply-To' => $config->{'invoicing'}{'email_reply_to'},
Subject => "Invoice: ".$invoice->{'Number'},
Type => 'multipart/mixed'
From => $config->{'invoicing'}{'email_from'},
To => $emailAddy,
Bcc => $config->{'invoicing'}{'email_bcc'},
'Reply-To' => $config->{'invoicing'}{'email_reply_to'},
Subject => "Invoice: ".$invoice->{'Number'},
Type => 'multipart/mixed'
);
# Attach body
$msg->attach(
Type => 'TEXT',
Encoding => '8bit',
Data => $emailBody,
Type => 'TEXT',
Encoding => '8bit',
Data => $emailBody,
);
# Work out mail encoding
my $attachmentType;
if ($format eq "html") {
$attachmentType = "text/html";
} elsif ($format eq "pdf") {
$attachmentType = "application/pdf";
}
# Attach invoice
$msg->attach(
Type => 'text/html',
Type => $attachmentType,
Encoding => 'base64',
Data => $invoiceData,
Path => $invoicePath,
Disposition => 'attachment',
Filename => $invoiceFilename
);
# If we have signature, sign invoice
if (defined($sign)) {
# Attach signature
$msg->attach(
Type => 'text/plain',
Encoding => 'base64',
Data => $sign,
Disposition => 'attachment',
Filename => $invSignFilename
);
}
# if (defined($sign)) {
# # Attach signature
# $msg->attach(
# Type => 'text/plain',
# Encoding => 'base64',
# Data => $sign,
# Disposition => 'attachment',
# Filename => $invSignFilename
# );
# }
# Send email
my $server = $config->{'mail'}{'server'};
......
# Templating functions
# Copyright (C) 2009-2014, AllWorldIT
# Copyright (C) 2009-2019, AllWorldIT
# Copyright (C) 2008, LinuxRulz
# Copyright (C) 2005-2007 Nigel Kukard <nkukard@lbsd.net>
#
......@@ -26,9 +26,17 @@ use strict;
use warnings;
use Cwd qw(abs_path);
use File::Basename;
use File::Copy;
use Template;
use Template::Exception;
# Check if we have PDF support using awit-docplates
my $SUPPORTS_PDF = 0;
if (eval {require AWIT::Docplate; require AWIT::PDFLatex; 1;}) {
$SUPPORTS_PDF = 1;
}
# Exporter stuff
require Exporter;
......@@ -36,6 +44,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT = qw(
loadTemplate
loadTemplate2
);
@EXPORT_OK = qw(
);
......@@ -83,6 +92,89 @@ sub Init
};
# New version of loadTemplate that uses AWIT Docplates
sub loadTemplate2
{
my ($templateName,$vars,$outputFile,$format) = @_;
# Make sure $outputFile is not a reference
if (ref($outputFile)) {
setError("CODE ERROR: \$outputFile should never be a ref");
return;
}
# If this is HTML templating return the old loadTemplate() results
if ($format eq "html") {
return loadTemplate($templateName,$vars,$outputFile);
}
# Make sure this is for pdf...
if ($format ne "pdf") {
setError("Unsupported format '$format'");
return;
}
# Make sure this is for pdf...
if (!$SUPPORTS_PDF) {
setError("AWIT-Docplates couldn't be loaded");
return;
}
# Create our docplate object
my $docplate = AWIT::Docplate->new();
# Loop with each of them
foreach my $var (keys %{$vars}) {
# Add variable
$docplate->addVariable($var,$vars->{$var});
}
# Add data dir
$docplate->addDataPath($config->{'docplates_data'});
# Work out the TOPDIR and add it
my $docplatesTOPDIR = dirname($config->{'docplates_data'});
$docplate->addVariable("TOPDIR",$docplatesTOPDIR);
# Create temporary dir and files we'll be using
my $tempDir = File::Temp->newdir();
my $texFile = sprintf('%s/file.tex',$tempDir);
my $pdfFile = sprintf('%s/file.pdf',$tempDir);
# Add the CURDIR, which in this case is the temporary directory
$docplate->addVariable("CURDIR",$tempDir);
# Add pre and post process templates from docplates
$docplate->addPreProcessTemplate("bits/start.tt2");
$docplate->addPostProcessTemplate("bits/end.tt2");
# Work out the DOC_ID
my $docid = sprintf('%s - Account %s - Invoice %s', $vars->{'ClientName'}, $vars->{'ClientCode'}, $vars->{'InvoiceNumber'});
# Process template
if (!$docplate->process($templateName,{'DOC_ID' => $docid}, $texFile)) {
setError($docplate->error);
return;
}
# Create a pdf latex object to handle outputting the PDF
my $pdflatex = AWIT::PDFLatex->new();
$pdflatex->setOutputDirectory($tempDir);
if (my $output = $pdflatex->process($texFile)) {
setError($output);
return;
}
# Copy PDF to output file
if (!copy($pdfFile,$outputFile)) {
setError("Failed to copy '$pdfFile' to '$outputFile': $!");
return;
}
return $outputFile;
}
# Create a template object
# Args: template_name params
sub loadTemplate
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment