Bug #651175 - Script to export invoices to LaTeX

Patch by Christoph Holtermann

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@21255 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Geert Janssens 2011-09-11 14:54:07 +00:00
parent acad985837
commit 6f5756c178
3 changed files with 461 additions and 0 deletions

View File

@ -86,6 +86,8 @@ endif
EXTRA_DIST = \
$(SWIG_FILES) \
example_scripts/Invoice.tex \
example_scripts/latex_invoices.py \
example_scripts/simple_book.py \
example_scripts/simple_session.py \
example_scripts/simple_test.py \

View File

@ -0,0 +1,154 @@
%---------------------------------------------------------------------------
% Invoice.tex v0.1 by Christoph Holtermann (c.holtermann@gmx.de)
%
% modified from
% scrlttr2.tex v0.3. (c) by Juergen Fenn <juergen.fenn@gmx.de>
% Template for a letter to be typeset with scrlttr2.cls from KOMA-Script.
% Latest version of the LaTeX Project Public License is applicable.
% File may not be modified and redistributed under the same name
% without the author's prior consent.
%---------------------------------------------------------------------------
\documentclass%%
%---------------------------------------------------------------------------
[fontsize=12pt,%% Schriftgroesse
%---------------------------------------------------------------------------
% Satzspiegel
paper=a4,%% Papierformat
enlargefirstpage=off,%% Erste Seite anders
pagenumber=headright,%% Seitenzahl oben mittig
%---------------------------------------------------------------------------
% Layout
headsepline=on,%% Linie unter der Seitenzahl
parskip=half,%% Abstand zwischen Absaetzen
%---------------------------------------------------------------------------
% Briefkopf und Anschrift
fromalign=right,%% Platzierung des Briefkopfs
fromphone=on,%% Telefonnummer im Absender
fromrule=off,%% Linie im Absender (aftername, afteraddress)
fromfax=on,%% Faxnummer
fromemail=on,%% Emailadresse
fromurl=off,%% Homepage
fromlogo=off,%% Firmenlogo
addrfield=on,%% Adressfeld fuer Fensterkuverts
backaddress=on,%% ...und Absender im Fenster
subject=beforeopening,%% Plazierung der Betreffzeile
locfield=narrow,%% zusaetzliches Feld fuer Absender
foldmarks=on,%% Faltmarken setzen
numericaldate=off,%% Datum numerisch ausgeben
refline=narrow,%% Geschaeftszeile im Satzspiegel
%---------------------------------------------------------------------------
% Formatierung
draft=off,%% Entwurfsmodus
version=last,
data%% data.lco ist die Datei aus der die Rechnungsdaten gelesen werden ( invoice-data to be read from data.lco )
]{scrlttr2}
%---------------------------------------------------------------------------
\usepackage[ngerman]{babel}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{url}
\usepackage{eurosym}
\usepackage{rechnung}
\usepackage{textcomp}
%---------------------------------------------------------------------------
% Fonts
\setkomafont{fromname}{\sffamily \LARGE}
\setkomafont{fromaddress}{\sffamily}%% statt \small
\setkomafont{pagenumber}{\sffamily}
\setkomafont{subject}{\mdseries}
\setkomafont{backaddress}{\mdseries}
\usepackage{mathptmx}%% Schrift Times
%\usepackage{mathpazo}%% Schrift Palatino
%\setkomafont{fromname}{\LARGE}
%---------------------------------------------------------------------------
\begin{document}
%---------------------------------------------------------------------------
% Briefstil und Position des Briefkopfs
\LoadLetterOption{DIN} %% oder: DINmtext, SN, SNleft, KOMAold.
\makeatletter
\@setplength{firstheadvpos}{20mm}
\@setplength{firstheadwidth}{\paperwidth}
\ifdim \useplength{toaddrhpos}>\z@
\@addtoplength[-2]{firstheadwidth}{\useplength{toaddrhpos}}
\else
\@addtoplength[2]{firstheadwidth}{\useplength{toaddrhpos}}
\fi
\@setplength{foldmarkhpos}{6.5mm}
%\setlength{\footskip}{1cm}
\makeatother
%---------------------------------------------------------------------------
% Absender
\setkomavar{fromname}{fromname}
\setkomavar{fromaddress}{fromaddress}
\setkomavar{fromphone}{fromphone}
\renewcommand{\phonename}{Telefon}
\setkomavar{fromfax}{fromfax}
\setkomavar{fromemail}{fromemail}
\setkomavar{backaddressseparator}{. }
\setkomavar{signature}{(signature)}
\setkomavar{frombank}{---NAME --- BANK ACCOUNT --- BANK NUMBER --- BANK NAME ---}
\setkomavar{location}{\\[8ex]\raggedleft{\footnotesize{\usekomavar{fromaddress}\\
Telefon: \usekomavar{fromphone}\\Mobil: mobile-number\\Fax: \usekomavar{fromfax}\\\usekomavar{fromemail}}}}%% Neben dem Adressfenster
%---------------------------------------------------------------------------
\firsthead{}
%---------------------------------------------------------------------------
% Fußzeile
\firstfoot{%
\parbox[b]{\linewidth}{%
\centering\def\\{, }\footnotesize\usekomavar{frombank}%
}%
}%
%---------------------------------------------------------------------------
% Geschaeftszeilenfelder
%\setkomavar{place}{Stadel}
%\setkomavar{placeseparator}{, den }
%\setkomavar{date}{\today}
%\setkomavar{yourmail}{1. 1. 2003}%% 'Ihr Schreiben...'
%\setkomavar{yourref} {abcdefg}%% 'Ihr Zeichen...'
%\setkomavar{myref}{}%% Unser Zeichen
\setkomavar{invoice}{\usekomavar{rechnungsnummer}}%% Rechnungsnummer
%\setkomavar{phoneseparator}{}
%---------------------------------------------------------------------------
% Versendungsart
%\setkomavar{specialmail}{Einschreiben mit R<>ckschein}
%---------------------------------------------------------------------------
% Anlage neu definieren
\renewcommand{\enclname}{Anlage}
\setkomavar{enclseparator}{: }
%---------------------------------------------------------------------------
% Seitenstil
\pagestyle{plain}%% keine Header in der Kopfzeile
%---------------------------------------------------------------------------
% Rechnungsoptionen
\Euro
%---------------------------------------------------------------------------
\begin{letter}{\usekomavar{toaddress2}}
%---------------------------------------------------------------------------
% Weitere Optionen
\KOMAoptions{%%
}
%---------------------------------------------------------------------------
%\setkomavar{subject}{Rechnungsnummer \usekomavar{rechnungsnummer}}
%---------------------------------------------------------------------------
\opening{Sehr geehrte Damen und Herren,}
Ich erlaube mir, Ihnen folgende Beträge in Rechnung zu stellen:
\begin{Rechnung}[N] %oder [N]
\Steuersatz{0}{0}
\usekomavar{entries}
\end{Rechnung}
Bitte zahlen Sie den Betrag von \Gesamtsumme \, bis zum \usekomavar{date_due} auf mein unten angegebenes Konto.
\closing{Mit bestem Dank und freundlichen Grüßen,}
%---------------------------------------------------------------------------
%\ps{PS:}
%\encl{}
%\cc{}
%---------------------------------------------------------------------------
\end{letter}
%---------------------------------------------------------------------------
\end{document}
%---------------------------------------------------------------------------

View File

@ -0,0 +1,305 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
##@file
# @ingroup python_bindings_examples
# @author Christoph Holtermann (c.holtermann (at) gmx.de)
# @date May 2011
# @brief Exports an invoice to lco-file for use with LaTeX
#
# The output file can be imported into KOMA-Script-letters.
# This works primarily for germany. Internationalization welcome!
#
# Additional files:
#
# - Invoice.tex\n
# Example template file. Should be modified according to personal needs.
# - rechnung.sty\n
# style file for invoices.\n
# This file is not part of the python-bindings!\n
# For an example where to get it see section credits below.
#
# Usage :
# \code latex_invoice file://testfile \endcode
# will create file data.lco.
# \code latex --output-format=pdf Invoice.tex \endcode
# should run latex on file Invoice.tex and result in Invoice.pdf. Invoice.tex includes data.lco.
#
# Additional information :
#
# - http://www.uweziegenhagen.de/latex/documents/rechnung/rechnungen.pdf (german)
#
# Credits to and ideas from
#
# - Main function as proposed by Guido van Rossum
# at http://www.artima.com/weblogs/viewpost.jsp?thread=4829
# - Invoice.tex is derived from\n
# scrlttr2.tex v0.3. (c) by Juergen Fenn <juergen.fenn@gmx.de>\n
# http://www.komascript.de/node/355\n
# english translation: ftp://ftp.dante.de/tex-archive/info/templates/fenn/scrlttr2en.tex
# - rechnung.sty\n
# from M G Berberich (berberic@fmi.uni-passau.de) and Ulrich Sibiller (uli42@web.de)
# Ver3.10 from http://www.forwiss.uni-passau.de/~berberic/TeX/Rechnung/index.html
#
# To Do:
#
# - get own contact data from gnucash
# - have own bank information in footline
# - nicer formatting of invoice date and date due
# - is there anything else missing in this invoice ?
try:
import sys
import getopt
import gnucash
import str_methods
from IPython.Shell import IPShellEmbed
from gnucash.gnucash_business import Customer, Employee, Vendor, Job, \
Address, Invoice, Entry, TaxTable, TaxTableEntry, GNC_AMT_TYPE_PERCENT, \
GNC_DISC_PRETAX
import locale
except ImportError as import_error:
print "Problem importing modules."
print import_error
sys.exit(2)
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
def get_all_lots(account):
"""Return all lots in account and descendants"""
ltotal=[]
descs = account.get_descendants()
for desc in descs:
if type(desc).__name__ == 'SwigPyObject':
desc = gnucash.Account(instance=desc)
ll=desc.GetLotList()
ltotal+=ll
return ltotal
def get_all_invoices_from_lots(account):
"""Return all invoices in account and descendants
This is based on lots. So invoices without lots will be missed."""
lot_list=get_all_lots(account)
invoice_list=[]
for lot in lot_list:
if type(lot).__name__ == 'SwigPyObject':
lot = gnucash.GncLot(instance=lot)
invoice=gnucash.gnucash_core_c.gncInvoiceGetInvoiceFromLot(lot.instance)
if invoice:
invoice_list.append(Invoice(instance=invoice))
return invoice_list
def invoice_to_lco(invoice):
"""returns a string which forms a lco-file for use with LaTeX"""
lco_out=u"\ProvidesFile{data.lco}[]\n"
def write_variable(ukey, uvalue, replace_linebreak=True):
outstr = u""
if uvalue.endswith("\n"):
uvalue=uvalue[0:len(uvalue)-1]
if not ukey in [u"fromaddress",u"toaddress",u"date"]:
outstr += u'\\newkomavar{'
outstr += ukey
outstr += u"}\n"
outstr += u"\\setkomavar{"
outstr += ukey
outstr += u"}{"
if replace_linebreak:
outstr += uvalue.replace(u"\n",u"\\\\")+"}"
return outstr
# Write owners address
add_str=u""
owner = invoice.GetOwner()
if owner.GetName() != "":
add_str += owner.GetName()+"\n"
addr = owner.GetAddr()
if addr.GetName() != "":
add_str += addr.GetName().decode("UTF-8")+"\n"
if addr.GetAddr1() != "":
add_str += addr.GetAddr1().decode("UTF-8")+"\n"
if addr.GetAddr2() != "":
add_str += addr.GetAddr2().decode("UTF-8")+"\n"
if addr.GetAddr3() != "":
add_str += addr.GetAddr3().decode("UTF-8")+"\n"
if addr.GetAddr4() != "":
add_str += addr.GetAddr4().decode("UTF-8")+"\n"
lco_out += write_variable("toaddress2",add_str)
# Invoice number
inr_str = invoice.GetID()
lco_out += write_variable("rechnungsnummer",inr_str)
# date
date = invoice.GetDatePosted()
udate = date.strftime("%d.%m.%Y")
lco_out += write_variable("date",udate)+"\n"
# date due
date_due = invoice.GetDateDue()
udate_due = date_due.strftime("%d.%m.%Y")
lco_out += write_variable("date_due",udate_due)+"\n"
# Write the entries
ent_str = u""
locale.setlocale(locale.LC_ALL,"de_DE")
for n,ent in enumerate(invoice.GetEntries()):
line_str = u""
if type(ent) != Entry:
ent=Entry(instance=ent) # Add to method_returns_list
descr = ent.GetDescription()
price = gnucash.GncNumeric(instance=ent.GetInvPrice()).to_double()
n = gnucash.GncNumeric(instance=ent.GetQuantity()) # change gncucash_core.py
uprice = locale.currency(price).rstrip(" EUR")
un = unicode(int(float(n.num())/n.denom())) # choose best way to format numbers according to locale
line_str = u"\Artikel{"
line_str += un
line_str += u"}{"
line_str += descr.decode("UTF-8")
line_str += u"}{"
line_str += uprice
line_str += u"}"
#print line_str
ent_str += line_str
lco_out += write_variable("entries",ent_str)
return lco_out
def main(argv=None):
if argv is None:
argv = sys.argv
try:
prog_name = argv[0]
with_ipshell = False
ignore_lock = False
no_latex_output = False
list_invoices = False
output_file_name = "data.lco"
try:
opts, args = getopt.getopt(argv[1:], "fhiln:po:", ["help"])
except getopt.error, msg:
raise Usage(msg)
for opt in opts:
if opt[0] in ["-f"]:
print "ignoring lock"
ignore_lock = True
if opt[0] in ["-h","--help"]:
raise Usage("Help:")
if opt[0] in ["-i"]:
print "Using ipshell"
with_ipshell = True
if opt[0] in ["-l"]:
print "listing all invoices"
list_invoices=True
if opt[0] in ["-n"]:
invoice_number = int(opt[1])
print "using invoice number", invoice_number
if opt[0] in ["-o"]:
output_file_name = opt[1]
print "using outpu file", output_file_name
if opt[0] in ["-p"]:
print "no latex output"
no_latex_output=True
if len(args)>1:
print "opts:",opts,"args:",args
raise Usage("Only one input can be accepted !")
if len(args)==0:
raise Usage("No input given !")
input_url = args[0]
except Usage, err:
if err.msg == "Help:":
retcode=0
else:
print >>sys.stderr, "Error:",err.msg
print >>sys.stderr, "for help use --help"
retcode=2
print "Prints out all invoices that have corresponding lots."
print
print "Usage:"
print
print "Invoke with",prog_name,"input."
print "where input is"
print " filename"
print "or file://filename"
print "or mysql://user:password@host/databasename"
print
print "-f force open = ignore lock"
print "-h or --help for this help"
print "-i for ipython shell"
print "-l list all invoices"
print "-n number use invoice number (no. from previous run -l)"
print "-o name use name as outputfile. default: data.lco"
print "-p pretend (=no) latex output"
return retcode
# Try to open the given input
try:
session = gnucash.Session(input_url,ignore_lock=ignore_lock)
except Exception as exception:
print "Problem opening input."
print exception
return 2
book = session.book
root_account = book.get_root_account()
comm_table = book.get_table()
EUR = comm_table.lookup("CURRENCY", "EUR")
invoice_list=get_all_invoices_from_lots(root_account)
if list_invoices:
for number,invoice in enumerate(invoice_list):
print str(number)+")"
print invoice
if not (no_latex_output):
if invoice_number == None:
print "Using the first invoice:"
invoice_number=0
invoice=invoice_list[invoice_number]
print "Using the following invoice:"
print invoice
lco_str=invoice_to_lco(invoice)
# Opening output file
f=open(output_file_name,"w")
lco_str=lco_str.encode("latin1")
f.write(lco_str)
f.close()
if with_ipshell:
ipshell= IPShellEmbed()
ipshell()
#session.save()
session.end()
if __name__ == "__main__":
sys.exit(main())