first try
This commit is contained in:
164
.gitignore
vendored
164
.gitignore
vendored
@@ -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/
|
||||
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