diff --git a/.gitignore b/.gitignore
index 5d381cc..f25f1ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,162 +1,2 @@
-# ---> Python
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-cover/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-.pybuilder/
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-# For a library or package, you might want to ignore these files since the code is
-# intended to run in multiple environments; otherwise, check them in:
-# .python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# poetry
-# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
-# This is especially recommended for binary packages to ensure reproducibility, and is more
-# commonly ignored for libraries.
-# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
-#poetry.lock
-
-# pdm
-# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
-#pdm.lock
-# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
-# in version control.
-# https://pdm.fming.dev/#use-with-ide
-.pdm.toml
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# pytype static type analyzer
-.pytype/
-
-# Cython debug symbols
-cython_debug/
-
-# PyCharm
-# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
-# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
-# and can be added to the global gitignore or merged into this file. For a more nuclear
-# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
-
+.idea/
+.code/
\ No newline at end of file
diff --git a/.idea/freelance_invoice.iml b/.idea/freelance_invoice.iml
new file mode 100644
index 0000000..2c80e12
--- /dev/null
+++ b/.idea/freelance_invoice.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..5fee4e1
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..0bfdaa1
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..af53adb
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1707294915620
+
+
+ 1707294915620
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..7ffcd58
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,35 @@
+arabic-reshaper==3.0.0
+asn1crypto==1.5.1
+certifi==2024.2.2
+cffi==1.16.0
+chardet==5.2.0
+charset-normalizer==3.3.2
+click==8.1.7
+cryptography==42.0.2
+cssselect2==0.7.0
+html5lib==1.1
+idna==3.6
+Jinja2==3.1.3
+lxml==5.1.0
+MarkupSafe==2.1.5
+oscrypto==1.3.0
+pillow==10.2.0
+pycparser==2.21
+pyHanko==0.21.0
+pyhanko-certvalidator==0.26.3
+pypdf==4.0.1
+pypng==0.20220715.0
+python-bidi==0.4.2
+PyYAML==6.0.1
+qrcode==7.4.2
+reportlab==4.0.9
+requests==2.31.0
+six==1.16.0
+svglib==1.5.1
+tinycss2==1.2.1
+typing_extensions==4.9.0
+tzlocal==5.2
+uritools==4.0.2
+urllib3==2.2.0
+webencodings==0.5.1
+xhtml2pdf==0.2.14
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..506c257
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,30 @@
+from setuptools import setup, find_packages
+
+setup(
+ name='simple-invoice-generator',
+ version='0.1.0',
+ description='Simple invoice generator for freelancers and small businesses',
+ long_description=open('README.md').read(),
+ long_description_content_type='text/markdown',
+ author='Torsten Ueberschar',
+ author_email='tu@uesome.de',
+ url='https://git.uesome.de/torsten/simple-invoice-generator',
+ packages=find_packages('src'),
+ package_dir={'': 'src'},
+ install_requires=[
+ 'PyYAML',
+ 'Jinja2',
+ 'xhtml2pdf',
+ ],
+ extras_require={
+ 'dev': ['pytest', 'coverage'],
+ # Extra dependencies for development and testing
+ },
+ classifiers=[
+ 'Development Status :: 3 - Alpha',
+ 'Intended Audience :: Developers, Freelancers',
+ 'License :: OSI Approved :: MIT License',
+ 'Programming Language :: Python :: 3.10',
+ # Add more classifiers as needed
+ ],
+)
diff --git a/src/invoice_generator/__init__.py b/src/invoice_generator/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/invoice_generator/html_generator.py b/src/invoice_generator/html_generator.py
new file mode 100644
index 0000000..5dee2fa
--- /dev/null
+++ b/src/invoice_generator/html_generator.py
@@ -0,0 +1,38 @@
+from pathlib import Path
+from jinja2 import FileSystemLoader, TemplateNotFound, Environment
+
+from xhtml2pdf import pisa
+
+
+class HtmlTemplate:
+ def __init__(self, templates):
+ self.template_name = 'invoice.html'
+ if not Path(templates).exists():
+ raise FileNotFoundError(f'Inivalid path to template files: {templates}')
+ self.templates = templates
+
+ def prepare_template(self, invoice_data, envelope_data):
+ try:
+ loader = FileSystemLoader(self.templates)
+ env = Environment(loader=loader)
+
+ template = env.get_template(self.template_name)
+ return template.render(invoice=invoice_data, envelope=envelope_data)
+ except TemplateNotFound:
+ raise FileNotFoundError(f'Could not find template file: {self.template_name}')
+
+ @staticmethod
+ def convert_html_to_pdf(source_html, output_filename):
+ # open output file for writing (truncated binary)
+ result_file = open(output_filename, "w+b")
+
+ # convert HTML to PDF
+ pisa_status = pisa.CreatePDF(
+ source_html, # the HTML to convert
+ dest=result_file) # file handle to recieve result
+
+ # close output file
+ result_file.close() # close output file
+
+ # return False on success and True on errors
+ return pisa_status.err
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..9ac8e90
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,104 @@
+import argparse
+import yaml
+from pathlib import Path
+
+from invoice_generator import html_generator
+
+
+class DataObject:
+ def __init__(self, **kwargs):
+ for key, value in kwargs.items():
+ setattr(self, key, value)
+
+
+def parse_yaml_file(file_path):
+ with open(file_path, 'r') as file:
+ return yaml.safe_load(file)
+
+
+def check_file(file_path):
+ try_out_extensions = ['.yaml', '.yml']
+
+ for ext in try_out_extensions:
+ if file_path.exists():
+ return file_path
+ file_path = file_path.with_suffix(ext)
+ return None
+
+
+# Utility function
+
+
+def main():
+ print('Simple invoice generator for freelancers and small businesses by Torsten Ueberschar')
+ print()
+ parser = argparse.ArgumentParser(description='Read invoice and envelope data from yaml file')
+ parser.add_argument('-b', '--base', type=str, required=True, help='base directory for invoice and envelope files')
+ parser.add_argument('-i', '--invoice', type=str, required=True, help='Invoice file name')
+ parser.add_argument('-e', '--envelope', type=str, required=False, default='envelope.yaml',
+ help='Envelope file name')
+ parser.add_argument('-t', '--template', type=str, required=False, default='template',
+ help='directory for template files')
+
+ args = parser.parse_args()
+
+ invoice_file = args.invoice
+ envelope_file = args.envelope
+
+ print(f'Searching for invoice files in: {args.base}')
+
+ invoice_file = Path(args.base) / invoice_file
+ envelope_file = Path(args.base) / envelope_file
+
+ invoice_file = check_file(invoice_file)
+ envelope_file = check_file(envelope_file)
+
+ print(f'Found invoice file: {invoice_file}')
+ print(f'Found envelope file: {envelope_file}')
+
+ if invoice_file is None:
+ print('Error: No invoice file found')
+ return
+ if envelope_file is None:
+ print('Error: No envelope file found')
+ return
+
+ try:
+ invoice = parse_yaml_file(invoice_file)
+ envelope = parse_yaml_file(envelope_file)
+
+ print('Invoice data:')
+ print(invoice)
+
+ print(envelope)
+
+ print('Envelope data:')
+ envelope_data = DataObject(**envelope)
+ print(envelope_data.__dict__)
+
+ print('Invoice data:')
+ invoice_data = DataObject(**invoice)
+ print(invoice_data.__dict__)
+
+ except FileNotFoundError as e:
+ print(f'Error: {e}')
+ return
+ except yaml.YAMLError as e:
+ print(f'Error: {e}')
+ return
+ except Exception as e:
+ print(f'Error: {e}')
+ return
+
+ generator = html_generator.HtmlTemplate(args.template)
+ template = generator.prepare_template(invoice_data, envelope_data)
+
+ try:
+ html_generator.HtmlTemplate.convert_html_to_pdf(template, 'invoice.pdf')
+ except Exception as e:
+ print(f'Error: {e}')
+ return
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test_data/RG004711.yaml b/test_data/RG004711.yaml
new file mode 100644
index 0000000..4838742
--- /dev/null
+++ b/test_data/RG004711.yaml
@@ -0,0 +1,21 @@
+CustomerId: KD01234
+#InvoiceDate: 2023-12-23
+
+Positions:
+ - Title: "Zuckerwatte fressen"
+ SubTitle: "Leistungszeitraum: 11/2022"
+ PricePerUnit:
+ Quantity: 123.45
+
+ - Title: "Aschlecken"
+ SubTitle: "Leistungszeitraum: 11/2022"
+ PricePerUnit: 99.99
+ Quantity: 12
+
+ - Title: "Aschkriechen"
+ SubTitle: "Leistungszeitraum: 10/2022"
+ PricePerUnit: 77.88
+ Quantity: 3
+
+
+
diff --git a/test_data/envelope.yaml b/test_data/envelope.yaml
new file mode 100644
index 0000000..41450d4
--- /dev/null
+++ b/test_data/envelope.yaml
@@ -0,0 +1,50 @@
+Config:
+ DefaultFont: Barlow Semi Condensed
+ MonoFont: Barlow Semi Condensed
+
+AddressContent:
+ LogoFile: Images/logo.svg
+ AddressBoxSender: Torsten Ueberschar - Pfarrweg 1 - 57439 Attendorn
+ Contents:
+ - Head:
+ Text: |
+ Torsten Ueberschar
+ Pfarrweg 1
+ 57439 Attendorn
+ - Head: Kontakt
+ Text: |
+ tu@uesome.de
+ +49 2734 4239271
+ - Head: Steuern
+ Text: |
+ St.-Nr.: 02/178/51224
+ USt-IdNr.: DE313460724
+ - Head: Bankverbindung
+ Text: |
+ Torsten Ueberschar
+ DE67 1001 1001 2626 8627 86
+ NTSBDEB1XXX
+ N26 Bank GmbH
+
+
+Invoice:
+ Vat: 19.0
+ Salutation: Sehr geehrte Damen und Herren,
+
+ Introduction: >
+ für folgende in Ihrem Auftrag ausgeführten Leistungen erlaube ich mir zu berechnen
+
+ Footer: >
+ Bitte überweisen Sie den Rechnungsbetrag unter Angabe der Rechnungsnummer auf mein Konto bis zum {ZahlungsZiel}.
+
+
+ Mit freundlichen Grüßen
+
+Customers:
+ - Id: KD01234
+ PricePerUnit: 88.88
+ DueDate: 30
+ AddressField: |
+ Klaus Peter Klausen
+ Am Klausenhof 1
+ 04711 Klausenhausen
diff --git a/test_data/templates/invoice.html b/test_data/templates/invoice.html
new file mode 100644
index 0000000..96c06ea
--- /dev/null
+++ b/test_data/templates/invoice.html
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
Absender
+ Anschrift
+ in
+ mehreren
+ Zeilen
+
+
+
+ {% for address in envelope.AddressContent.Contents %}
+ {% if address.Head %}
+
{{ address.Head }}
+ {% endif %}
+
{{ address.Text | replace('\n', ' ') }}
+ {% endfor %}
+
+
+
+
+To PDF or not to PDF
+
+
+
About Us
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore
+ magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+ pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
+ laborum.
+
+
+
+
Our Services
+
+
Service 1
+
Service 2
+
Service 3
+
+
+
+
+
About Us
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore
+ magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+ pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
+ laborum.