Commit e8489dcc authored by Nigel Kukard's avatar Nigel Kukard

Merge branch 'pdf' into 'master'

Pdf

See merge request !44
parents d07fc7d3 99ab5af5
Pipeline #4100 passed with stages
in 2 minutes and 46 seconds
image: idmslinux/stretch
# Stages we need to progress through
stages:
- build
......
......@@ -52,6 +52,9 @@ plugins=YearEnd
[templates]
path = ../contrib/templates
# Docplates data dir for pdf invoices
docplates_data = ../awit-docplates/data
# Debug, 0 or 1
#debug = 1
......
# Commandline user interface
# Copyright (C) 2009-2014, AllWorldIT
# Copyright (C) 2009-2019, AllWorldIT
# Copyright (C) 2008, LinuxRulz
# Copyright (C) 2005-2007 Nigel Kukard <nkukard@lbsd.net>
#
......@@ -325,6 +325,7 @@ sub cmd_connect
# Template engine config
my @template_params = (
'path',
'docplates_data',
'debug',
'global_header',
'global_footer',
......
# Invoicing functions
# Copyright (C) 2009-2014, AllWorldIT
# Copyright (C) 2009-2019, AllWorldIT
# Copyright (C) 2008, LinuxRulz
# Copyright (C) 2007 Nigel Kukard <nkukard@lbsd.net>
#
......@@ -77,7 +77,7 @@ our $pluginInfo = {
MenuItem => "Send",
Regex => "send",
Desc => "Send client invoice",
Help => 'send invoice="<invoice number>" sendto="<email[:addy1,addy2...] or file:filename>"',
Help => 'send invoice="<invoice number>" sendto="<email[:addy1,addy2...] or file:filename>" [proforma="yes"] [format="<html> or <pdf>"]',
Function => \&send,
},
{
......@@ -345,6 +345,8 @@ sub send
my $detail;
$detail->{'Number'} = $parms->{'invoice'};
$detail->{'SendTo'} = $parms->{'sendto'};
$detail->{'Format'} = $parms->{'format'};
$detail->{'Proforma'} = $parms->{'proforma'};
my $res = soapCall($OUT,"wiaflos/server/api/Invoicing","sendInvoice",$detail);
if ($res->{'Result'} != RES_OK) {
soapDebug($OUT,$res);
......
# SOAP interface to Invoicing module
# Copyright (C) 2009-2014, AllWorldIT
# Copyright (C) 2009-2019, AllWorldIT
# Copyright (C) 2008, LinuxRulz
# Copyright (C) 2007 Nigel Kukard <nkukard@lbsd.net>
#
......@@ -344,12 +344,22 @@ sub sendInvoice {
return SOAPResponse(RES_ERROR,ERR_S_PARAM,"Parameter 'SendTo' invalid");
}
if (defined($data->{'Proforma'}) && ($data->{'Proforma'} ne "yes" && $data->{'Proforma'} ne "no")) {
return SOAPResponse(RES_ERROR,ERR_S_PARAM,"Parameter 'Proforma' invalid");
}
if (defined($data->{'Format'}) && ($data->{'Format'} ne "html" && $data->{'Format'} ne "pdf")) {
return SOAPResponse(RES_ERROR,ERR_S_PARAM,"Parameter 'Format' invalid");
}
my $authInfo = wiaflos::server::api::auth::sessionGetData();
# Do transaction
my $detail;
$detail->{'Number'} = $data->{'Number'};
$detail->{'SendTo'} = $data->{'SendTo'};
$detail->{'Proforma'} = $data->{'Proforma'};
$detail->{'Format'} = $data->{'Format'};
my $res = wiaflos::server::core::Invoicing::sendInvoice($detail);
if ($res < 0) {
return SOAPResponse(RES_ERROR,$res,wiaflos::server::core::Invoicing::Error());
......
# Invoicing functions
# Copyright (C) 2009-2014, AllWorldIT
# Copyright (C) 2009-2019, AllWorldIT
# Copyright (C) 2008, LinuxRulz
# Copyright (C) 2007 Nigel Kukard <nkukard@lbsd.net>
#
......@@ -35,15 +35,16 @@ use wiaflos::server::core::GL;
use wiaflos::server::core::Inventory;
use wiaflos::server::core::Clients;
use wiaflos::server::core::Tax;
use wiaflos::server::core::Tax;
# Whole money transactions, precision is two
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;
......@@ -1085,6 +1086,12 @@ sub sendInvoice
return ERR_PARAM;
}
# Default to PDF invoices
my $format = $detail->{'Format'} // "pdf";
# Default to not using proforma invoices
my $proforma = $detail->{'Proforma'} // "no";
# Pull in config
my $config = wiaflos::server::core::config::getConfig();
......@@ -1146,10 +1153,18 @@ sub sendInvoice
}
# If no billing address, use shipping address
$billAddr = defined($billAddr) ? $billAddr : $shipAddr;
$billAddr =~ s/\|/<br \/>/g;
# Setup shipping address from invoice
$shipAddr = $invoice->{'ShippingAddress'};
$shipAddr =~ s/\|/<br \/>/g;
# If we're using HTML, we need to add newlines
if ($format eq "html") {
$billAddr =~ s/\|/<br \/>/g;
$shipAddr =~ s/\|/<br \/>/g;
# If we're PDF, generate an arrayref instead
} elsif ($format eq "pdf") {
$billAddr = [ split(/\|/,$billAddr) ];
$shipAddr = [ split(/\|/,$shipAddr) ];
}
# Parse in email addresses
my $billEmailAddr;
......@@ -1168,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
......@@ -1210,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
......@@ -1236,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 = "";
......@@ -1257,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
......
......@@ -48,11 +48,11 @@ our (
);
$APPNAME = "Wiaflos Accounting";
$APPURL = "http://www.wiaflos.org";
$COPYRIGHT = "Copyright (c) 2005-2014, AllWorldIT";
$APPURL = "https://www.wiaflos.org";
$COPYRIGHT = "Copyright (c) 2005-2019, AllWorldIT";
$VERSION = "0.1.x";
$APPSTRING = "$APPNAME - %s v$VERSION, $COPYRIGHT";
$GENSTRING = "Document electronically generatd by $APPNAME ($APPURL)";
$GENSTRING = "This document was electronically generatd by $APPNAME ($APPURL)";
1;
# vim: ts=4
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