Merge pull request #2 from Eledio/feature/obtainPDFInvoice

Obtain PDF version of the invoice
This commit is contained in:
Richard Kubíček
2025-03-04 14:56:18 +01:00
committed by GitHub
11 changed files with 398 additions and 130 deletions

View File

@@ -4,31 +4,31 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html
import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
sys.path.insert(0, os.path.abspath("../.."))
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'SuperFaktura API client'
copyright = '2025, Richard Kubíček, Eledio s.r.o.'
author = 'Richard Kubíček'
project = "SuperFaktura API client"
copyright = "2025, Richard Kubíček, Eledio s.r.o."
author = "Richard Kubíček"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinx.ext.autosummary',
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinx.ext.autosummary",
]
templates_path = ['_templates']
templates_path = ["_templates"]
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]

19
docs/source/examples.rst Normal file
View File

@@ -0,0 +1,19 @@
Examples of SuperFaktura API client usage
====================
Add Invoice
---------------------------------
.. automodule:: examples.add_invoice
:members:
:undoc-members:
:show-inheritance:
Get country list
------------------------------------
.. automodule:: examples.get_country_list
:members:
:undoc-members:
:show-inheritance:

View File

@@ -6,3 +6,4 @@ Welcome to SuperFaktura Client's documentation
superfaktura
installation
examples

87
examples/add_invoice.py Normal file
View File

@@ -0,0 +1,87 @@
"""
Main script to add an invoice and save it as a PDF using the SuperFaktura API.
This script demonstrates how to create an invoice with multiple items,
retrieve the invoice as a PDF, and save the PDF to a file.
Usage:
Run this script directly to create and save an invoice PDF.
Dependencies:
- superfaktura.bank_account.BankAccount
- superfaktura.client_contacts.ClientContactModel
- superfaktura.enumerations.currency.Currencies
- superfaktura.enumerations.language.Language
- superfaktura.invoice.Invoice
- superfaktura.invoice.InvoiceModel
- superfaktura.invoice.InvoiceType
- superfaktura.invoice.InvoiceItem
- superfaktura.invoice.InvoiceSettings
- superfaktura.utils.data_types.Date
"""
from superfaktura.bank_account import BankAccount, NoDefaultBankAccountException
from superfaktura.client_contacts import ClientContactModel
from superfaktura.enumerations.currency import Currencies
from superfaktura.enumerations.language import Language
from superfaktura.invoice import (
Invoice,
InvoiceModel,
InvoiceType,
InvoiceItem,
InvoiceSettings,
)
from superfaktura.utils.data_types import Date
def main():
"""
Main function to add Invoice and save it as a pdf using the SuperFaktura API.
"""
invoice = Invoice()
bank = BankAccount()
try:
# Get default bank account
bank_account = bank.default().as_dict()
except NoDefaultBankAccountException as e:
print(f"Error getting default bank account: {e}")
bank_account = {}
resp = invoice.add(
invoice_model=InvoiceModel(
type=InvoiceType.INVOICE,
name="My First Invoice",
due=Date("2025-04-01"),
invoice_currency=Currencies.EUR,
header_comment="We invoice you for services",
bank_accounts=[bank_account],
),
items=[
InvoiceItem(
name="Website Development", unit_price=1000.0, quantity=1, tax=20
),
InvoiceItem(
name="Hosting Service (1 year)", unit_price=500.0, quantity=1, tax=20
),
],
contact=ClientContactModel(
name="John Doe",
email="john.doe@examle.com",
phone="+1 555-1234",
address="123 Main Street, New York",
ico="987654321",
update=True,
country_id=225,
),
invoice_settings=InvoiceSettings(language=Language.English),
)
try:
with open("invoice.pdf", "wb") as f:
invoice.get_pdf(invoice=resp, descriptor=f, language=Language.English)
print("Invoice saved to 'invoice.pdf'")
except Exception as e:
print(f"Error generating or saving invoice as PDF: {e}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,18 @@
"""
This script retrieves and prints the list of countries using the SuperFaktura API.
"""
from pprint import pprint
from superfaktura.utils.country import country_list
def main():
"""
Main function to retrieve and print the list of countries using the SuperFaktura API.
"""
pprint(country_list())
if __name__ == "__main__":
main()

5
ruff.toml Normal file
View File

@@ -0,0 +1,5 @@
[lint]
ignore = [
# Use a single `if` statement instead of nested `if` statements
"SIM102",
]

View File

@@ -1,15 +1,12 @@
from setuptools import setup, find_namespace_packages
setup(
name='superfaktura_client',
version='0.1.0',
name="superfaktura_client",
version="0.1.0",
packages=find_namespace_packages(),
url='https://github.com/eledio-helpers/superfaktura-client',
license='GNU GPLv3',
author='Richard Kubíček',
author_email='kubicekr@eledio.com',
install_requires=[
"requests",
"python-dotenv"
],
url="https://github.com/eledio-helpers/superfaktura-client",
license="MIT",
author="Richard Kubíček",
author_email="kubicekr@eledio.com",
install_requires=["requests", "python-dotenv"],
)

View File

@@ -15,20 +15,16 @@ Functions:
- (none)
Usage:
import superfaktura.bank_account
# Create an instance of BankAccount
bank = superfaktura.bank_account.BankAccount()
# Retrieve a list of bank accounts
accounts = bank.list()
# Get the default bank account
default_account = bank.default()
# Create or update a bank account
data = {"account": "1234567890", "bank_code": "1234567890", "default": True}
bank.post(data)
>>> import superfaktura.bank_account
>>> # Create an instance of BankAccount
>>> bank = superfaktura.bank_account.BankAccount()
>>> # Retrieve a list of bank accounts
>>> accounts = bank.list()
>>> # Get the default bank account
>>> default_account = bank.default()
>>> # Create or update a bank account
>>> data = {"account": "1234567890", "bank_code": "1234567890", "default": True}
>>> bank.post(data)
"""
from dataclasses import dataclass, asdict
@@ -83,11 +79,11 @@ class BankAccount(SuperFakturaAPI):
- post: Creates or updates a bank account.
Usage:
bank = BankAccount()
accounts = bank.list()
default_account = bank.default()
data = {"account": "1234567890", "bank_code": "1234567890", "default": True}
bank.post(data)
>>> bank = BankAccount()
>>> accounts = bank.list()
>>> default_account = bank.default()
>>> data = {"account": "1234567890", "bank_code": "1234567890", "default": True}
>>> bank.post(data)
"""
def __init__(self):

View File

@@ -0,0 +1,54 @@
"""
Language Enumeration.
This module provides an enumeration of languages that can be used in the SuperFaktura API.
Classes:
- Language: Enumeration of languages.
Usage:
from superfaktura.enumerations.language import Language
language = Language.Czech
"""
class Language:
"""
Language Enumeration.
This enumeration represents the different languages that can be used in the SuperFaktura API.
Values:
- Czech: Czech
- German: German
- English: English
- Croatian: Croatian
- Hungarian: Hungarian
- Italian: Italian
- Dutch: Dutch
- Polish: Polish
- Romanian: Romanian
- Russian: Russian
- Slovak: Slovak
- Slovene: Slovene
- Spanish: Spanish
- Ukrainian: Ukrainian
Usage:
language = Language.Czech
"""
Czech = "cze"
German = "deu"
English = "eng"
Croatian = "hrv"
Hungarian = "hun"
Italian = "ita"
Dutch = "nld"
Polish = "pol"
Romanian = "rom"
Russian = "rus"
Slovak = "slo"
Slovene = "slv"
Spanish = "spa"
Ukrainian = "ukr"

View File

@@ -16,49 +16,45 @@ Functions:
- (none)
Usage:
import superfaktura.invoice
# Create an instance of Invoice
invoice = superfaktura.invoice.Invoice()
# Create an invoice
invoice.add(
invoice=superfaktura.invoice.InvoiceModel(
type=superfaktura.invoice.InvoiceType.PROFORMA,
name="Invoice 3",
due=superfaktura.invoice.Date("2025-02-01"),
invoice_currency=superfaktura.invoice.Currencies.CZK,
>>> import superfaktura.invoice
>>> # Create an instance of Invoice
>>> invoice = superfaktura.invoice.Invoice()
>>> # Create an invoice
>>> invoice.add(
invoice_model=InvoiceModel(
type=InvoiceType.INVOICE,
name="My First Invoice",
due=Date("2025-04-01"),
invoice_currency=Currencies.EUR,
header_comment="We invoice you for services",
bank_accounts=[bank.default().as_dict()],
),
items=[
superfaktura.invoice.InvoiceItem(name="Services", unit_price=100, quantity=1,
unit="ks", tax=21),
superfaktura.invoice.InvoiceItem(name="SIM card", unit_price=50, quantity=1,
tax=21, unit="ks"),
superfaktura.invoice.InvoiceItem(
name="SIM card 2", unit_price=75, quantity=1, tax=21, unit="ks"
InvoiceItem(
name="Website Development", unit_price=1000.0, quantity=1, tax=20
),
InvoiceItem(
name="Hosting Service (1 year)", unit_price=500.0, quantity=1, tax=20
),
],
contact=superfaktura.client_contacts.ClientContactModel(
name="Richard Kubíček",
email="kubicekr@eledio.com",
phone="+420 123 456 789",
address="Jaroslava Foglara 861/1",
ico="123",
contact=ClientContactModel(
name="John Doe",
email="john.doe@examle.com",
phone="+1 555-1234",
address="123 Main Street, New York",
ico="987654321",
update=True,
country_id=57,
country_id=225,
),
)
"""
from dataclasses import dataclass, asdict
from typing import Optional, List
from typing import Optional, List, IO
import json
from superfaktura.bank_account import BankAccount
from superfaktura.client_contacts import ClientContactModel
from superfaktura.enumerations.currency import Currencies
from superfaktura.enumerations.language import Language
from superfaktura.superfaktura_api import SuperFakturaAPI
from superfaktura.utils.data_types import Date, DateEncoder
@@ -147,8 +143,51 @@ class InvoiceItem:
return data
@dataclass
class InvoiceRespModel:
"""
This dataclass represents the response model for an invoice in the SuperFaktura API.
Attributes:
- error (int): The error code.
- error_message (str): The error message.
- invoice_id (Optional[int]): The ID of the invoice.
- invoice_token (Optional[str]): The token of the invoice.
"""
error: int
error_message: str
invoice_id: Optional[int] = None
invoice_token: Optional[str] = None
@dataclass
class InvoiceSettings:
"""
This dataclass represents the settings for an invoice in the SuperFaktura API.
"""
language: Optional[str] = None
bysquare: Optional[bool] = None
callback_payment: Optional[str] = None
online_payment: Optional[bool] = None
payment_info: Optional[bool] = None
paypal: Optional[bool] = None
show_prices: Optional[bool] = None
signature: Optional[bool] = None
summary_bg_color: Optional[str] = None
def as_dict(self) -> dict:
"""Returns a dictionary representation of the InvoiceSettings."""
data = asdict(self)
for key in list(data.keys()):
if data[key] is None:
del data[key]
return data
class InvoiceType:
""" "
"""
Invoice Type Enumeration.
This enumeration represents the different types of invoices that can be created.
@@ -175,31 +214,32 @@ class Invoice(SuperFakturaAPI):
- update: Updates an existing invoice.
Usage:
invoice = Invoice()
invoice.add(
invoice=InvoiceModel(
type=InvoiceType.PROFORMA,
name="Invoice 3",
due=Date("2025-02-01"),
invoice_currency=Currencies.CZK,
>>> invoice = Invoice()
>>> invoice.add(
invoice_model=InvoiceModel(
type=InvoiceType.INVOICE,
name="My First Invoice",
due=Date("2025-04-01"),
invoice_currency=Currencies.EUR,
header_comment="We invoice you for services",
bank_accounts=[bank.default().as_dict()],
),
items=[
InvoiceItem(name="Services", unit_price=100, quantity=1, unit="ks", tax=21),
InvoiceItem(name="SIM card", unit_price=50, quantity=1, tax=21, unit="ks"),
InvoiceItem(
name="SIM card 2", unit_price=75, quantity=1, tax=21, unit="ks"
name="Website Development", unit_price=1000.0, quantity=1, tax=20
),
InvoiceItem(
name="Hosting Service (1 year)", unit_price=500.0, quantity=1, tax=20
),
],
contact=ClientContactModel(
name="Richard Kubíček",
email="kubicekr@eledio.com",
phone="+420 123 456 789",
address="Jaroslava Foglara 861/1",
ico="123",
name="John Doe",
email="john.doe@example.com",
phone="+1 555-1234",
address="123 Main Street, New York",
ico="987654321",
update=True,
country_id=57,
country_id=225,
),
)
"""
@@ -212,44 +252,53 @@ class Invoice(SuperFakturaAPI):
invoice_model: InvoiceModel,
items: List[InvoiceItem],
contact: ClientContactModel,
):
"""Creates a new invoice."""
invoice_settings: Optional[InvoiceSettings] = None,
) -> InvoiceRespModel:
"""
Adds a new invoice.
Args:
invoice_model (InvoiceModel): The invoice model.
items (List[InvoiceItem]): List of invoice items.
contact (ClientContactModel): The client contact model.
invoice_settings (Optional[InvoiceSettings]): The invoice settings.
Returns:
InvoiceRespModel: The response model for the invoice.
"""
data = {
"Invoice": invoice_model.as_dict(),
"InvoiceItem": [item.as_dict() for item in items],
"Client": contact.as_dict(),
"InvoiceSetting": invoice_settings.as_dict() if invoice_settings else {},
}
url = "invoices/create"
resp = self.post(endpoint=url, data=json.dumps(data, cls=DateEncoder))
return resp
if __name__ == "__main__":
invoice = Invoice()
bank = BankAccount()
invoice.add(
invoice_model=InvoiceModel(
type=InvoiceType.PROFORMA,
name="Invoice 3",
due=Date("2025-02-01"),
invoice_currency=Currencies.CZK,
header_comment="We invoice you for services",
bank_accounts=[bank.default().as_dict()],
),
items=[
InvoiceItem(name="Services", unit_price=100, quantity=1, unit="ks", tax=21),
InvoiceItem(name="SIM card", unit_price=50, quantity=1, tax=21, unit="ks"),
InvoiceItem(
name="SIM card 2", unit_price=75, quantity=1, tax=21, unit="ks"
),
],
contact=ClientContactModel(
name="Richard Kubíček",
email="kubicekr@eledio.com",
phone="+420 123 456 789",
address="Jaroslava Foglara 861/1",
ico="123",
update=True,
country_id=57,
),
invoice_resp = InvoiceRespModel(
error=resp["error"], error_message=resp["error_message"]
)
if "data" in resp:
if "Invoice" in resp["data"]:
invoice_resp.invoice_id = int(resp["data"]["Invoice"]["id"])
invoice_resp.invoice_token = resp["data"]["Invoice"]["token"]
return invoice_resp
def get_pdf(
self,
invoice: InvoiceRespModel,
descriptor: IO[bytes],
language: str = Language.Czech,
) -> None:
"""
Retrieves the PDF of the invoice.
Args:
invoice (InvoiceRespModel): The response model for the invoice.
descriptor (IO[bytes]): The descriptor to write the PDF data to.
language (str): The language for the PDF.
Returns:
None
"""
url = f"{language}/invoices/pdf/{invoice.invoice_id}/token:{invoice.invoice_token}"
self.download(url, descriptor)

View File

@@ -14,20 +14,17 @@ Functions:
- post: Creates or updates data in the SuperFaktura API.
Usage:
import superfaktura.superfaktura_api
# Create an instance of SuperFakturaAPI
api = superfaktura.superfaktura_api.SuperFakturaAPI()
# Retrieve data from the SuperFaktura API
data = api.get('endpoint')
# Create or update data in the SuperFaktura API
api.post('endpoint', data)
>>> import superfaktura.superfaktura_api
>>> # Create an instance of SuperFakturaAPI
>>> api = superfaktura.superfaktura_api.SuperFakturaAPI()
>>> # Retrieve data from the SuperFaktura API
>>> incoming_data = api.get('endpoint')
>>> # Create or update data in the SuperFaktura API
>>> api.post('endpoint', outgoing_data)
"""
import os
from typing import Dict
from typing import Dict, IO
import requests
from dotenv import load_dotenv # type: ignore
@@ -90,9 +87,54 @@ class SuperFakturaAPI:
url = f"{self._api_url}/{endpoint}"
req = requests.get(url=url, headers=self._auth_header, timeout=timeout)
if req.status_code == 200:
try:
return req.json()
except requests.exceptions.JSONDecodeError as e:
raise SuperFakturaAPIException(
f"Get status code: {req.status_code}; {req.json()}"
f"Unable to decode response as JSON; {req.content!r}; {e}"
) from e
raise SuperFakturaAPIException(
f"Get status code: {req.status_code}; {req.content!r}"
)
def download(self, endpoint: str, descriptor: IO[bytes], timeout: int = 5) -> None:
"""
Download data stream from the SuperFaktura API.
Download data stream from the specified endpoint in the SuperFaktura API.
Args:
endpoint (str): The API endpoint to retrieve data from (e.g. 'invoices', 'clients',
etc.).
descriptor (IO[bytes]): The descriptor to write the data stream to.
timeout (int, optional): The timeout for the API request in seconds. Defaults to 5.
Returns:
None
Raises:
SuperFakturaAPIException: If the API request fails or returns an error.
Examples:
>>> api = SuperFakturaAPI()
>>> with open("data.pdf", "wb") as f:
>>> api.download("some/endpoint", descriptor=f)
Notes:
The available endpoints can be found in the SuperFaktura API documentation.
"""
url = f"{self._api_url}/{endpoint}"
req = requests.get(url=url, headers=self._auth_header, timeout=timeout)
if req.status_code == 200:
if descriptor.writable():
descriptor.write(req.content)
else:
raise SuperFakturaAPIException(
f"Descriptor {descriptor} is not writable"
)
else:
raise SuperFakturaAPIException(
f"Download status code: {req.status_code}; {req.content!r}"
)
def post(self, endpoint: str, data: str, timeout: int = 5) -> Dict: