first try
This commit is contained in:
164
.gitignore
vendored
164
.gitignore
vendored
@@ -1,162 +1,2 @@
|
|||||||
# ---> Python
|
.idea/
|
||||||
# Byte-compiled / optimized / DLL files
|
.code/
|
||||||
__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/
|
|
||||||
|
|
||||||
10
.idea/freelance_invoice.iml
generated
Normal file
10
.idea/freelance_invoice.iml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.10 (freelance_invoice)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (freelance_invoice)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/freelance_invoice.iml" filepath="$PROJECT_DIR$/.idea/freelance_invoice.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
132
.idea/workspace.xml
generated
Normal file
132
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="7672063a-a324-4a9c-a8d3-c38955c8d763" name="Changes" comment="">
|
||||||
|
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/freelance_invoice.iml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/setup.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/invoice_generator/__init__.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/invoice_generator/html_generator.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/main.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/test_data/RG004711.yaml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/test_data/envelope.yaml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/test_data/templates/invoice.html" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangesViewManager">
|
||||||
|
<option name="groupingKeys">
|
||||||
|
<option value="directory" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Python Script" />
|
||||||
|
<option value="HTML File" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="MarkdownSettingsMigration">
|
||||||
|
<option name="stateVersion" value="1" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo"><![CDATA[{
|
||||||
|
"associatedIndex": 1
|
||||||
|
}]]></component>
|
||||||
|
<component name="ProjectId" id="2c21ZczAqxeYNrytEGCIucesn9A" />
|
||||||
|
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"DefaultHtmlFileTemplate": "HTML File",
|
||||||
|
"Python.main.executor": "Run",
|
||||||
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"last_opened_file_path": "/home/torsten/PycharmProjects/freelance_invoice/test_data",
|
||||||
|
"node.js.detected.package.eslint": "true",
|
||||||
|
"node.js.detected.package.tslint": "true",
|
||||||
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
|
"settings.editor.selected.configurable": "preferences.pluginManager",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/test_data" />
|
||||||
|
</key>
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/src" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="main" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
|
||||||
|
<module name="freelance_invoice" />
|
||||||
|
<option name="ENV_FILES" value="" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/main.py" />
|
||||||
|
<option name="PARAMETERS" value="--invoice RG004711 --base test_data/ --template ./test_data/templates/" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-python-sdk-5a2391486177-2887949eec09-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-233.13763.11" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="7672063a-a324-4a9c-a8d3-c38955c8d763" name="Changes" comment="" />
|
||||||
|
<created>1707294915620</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1707294915620</updated>
|
||||||
|
<workItem from="1707294917219" duration="20842000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||||
|
<SUITE FILE_PATH="coverage/freelance_invoice$main.coverage" NAME="main Coverage Results" MODIFIED="1707321806053" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
35
requirements.txt
Normal file
35
requirements.txt
Normal file
@@ -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
|
||||||
30
setup.py
Normal file
30
setup.py
Normal file
@@ -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
|
||||||
|
],
|
||||||
|
)
|
||||||
0
src/invoice_generator/__init__.py
Normal file
0
src/invoice_generator/__init__.py
Normal file
38
src/invoice_generator/html_generator.py
Normal file
38
src/invoice_generator/html_generator.py
Normal file
@@ -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
|
||||||
104
src/main.py
Normal file
104
src/main.py
Normal file
@@ -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()
|
||||||
21
test_data/RG004711.yaml
Normal file
21
test_data/RG004711.yaml
Normal file
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
50
test_data/envelope.yaml
Normal file
50
test_data/envelope.yaml
Normal file
@@ -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
|
||||||
107
test_data/templates/invoice.html
Normal file
107
test_data/templates/invoice.html
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
size: a4 portrait;
|
||||||
|
margin: 1cm 1cm 1cm 2cm;
|
||||||
|
font-size: 12pt;
|
||||||
|
font-family: "Barlow Semi Condensed";
|
||||||
|
@frame address_frame {
|
||||||
|
/* Static Frame */
|
||||||
|
-pdf-frame-content: address_frame_content;
|
||||||
|
left: 2.5cm;
|
||||||
|
width: 8.5cm;
|
||||||
|
top: 5.5cm;
|
||||||
|
height: 4cm;
|
||||||
|
-pdf-frame-border: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@frame letter_head_frame {
|
||||||
|
/* Another static Frame */
|
||||||
|
-pdf-frame-content: letter_head_content;
|
||||||
|
left: 15.5cm;
|
||||||
|
width: 4cm;
|
||||||
|
top: 1cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@frame content_first_page_frame {
|
||||||
|
/* Content Frame */
|
||||||
|
left: 2cm;
|
||||||
|
width: 13cm;
|
||||||
|
top: 11cm;
|
||||||
|
bottom: 1cm;
|
||||||
|
-pdf-frame-border: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#address_frame_content {
|
||||||
|
background-color: lightcoral;
|
||||||
|
}
|
||||||
|
|
||||||
|
#letter_head_content {
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Content for Static Frame 'header_frame' -->
|
||||||
|
<div id="address_frame_content">
|
||||||
|
<p>Absender</p>
|
||||||
|
<address>Anschrift
|
||||||
|
in
|
||||||
|
mehreren
|
||||||
|
Zeilen
|
||||||
|
</address>
|
||||||
|
</div>
|
||||||
|
<div id="letter_head_content">
|
||||||
|
{% for address in envelope.AddressContent.Contents %}
|
||||||
|
{% if address.Head %}
|
||||||
|
<p><strong>{{ address.Head }}</strong></p>
|
||||||
|
{% endif %}
|
||||||
|
<p>{{ address.Text | replace('\n', '<br>') }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HTML Content -->
|
||||||
|
To PDF or not to PDF
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>About Us</h2>
|
||||||
|
<p>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.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Our Services</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Service 1</li>
|
||||||
|
<li>Service 2</li>
|
||||||
|
<li>Service 3</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>About Us</h2>
|
||||||
|
<p>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.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Our Services</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Service 1</li>
|
||||||
|
<li>Service 2</li>
|
||||||
|
<li>Service 3</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user