Metadata-Version: 2.4
Name: pyfox-invoice
Version: 0.1.1
Summary: A type-safe invoice generation library using Pydantic and Typst
Keywords: invoice,pdf,typst,pydantic,sepa,qr-code,billing
Author: Daniil Sanzharov
Author-email: Daniil Sanzharov <daniil.sanzharov@proton.me>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial :: Accounting
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Dist: babel>=2.18.0
Requires-Dist: phonenumbers>=9.0.30
Requires-Dist: pillow>=12.2.0
Requires-Dist: pydantic>=2.13.3
Requires-Dist: segno>=1.6.6
Requires-Dist: typst>=0.14.8
Requires-Python: >=3.12
Project-URL: Homepage, https://github.com/daniilfurgen/pyfox-invoice
Project-URL: Repository, https://github.com/daniilfurgen/pyfox-invoice
Project-URL: Issues, https://github.com/daniilfurgen/pyfox-invoice/issues
Description-Content-Type: text/markdown

# pyfox-invoice

**pyfox-invoice** is a robust and flexible Python library designed to generate beautiful, high-quality invoices, pro-forma invoices, and payment requests (bills). It processes your financial data and produces stunning PDF outputs using the [Typst](https://typst.app/) rendering engine.

The library strictly separates data modeling, regional localization, and document rendering, making it easily extensible for various geographical regions and output styles.

## Features

- **Type-Safe Data Modeling**: Uses [Pydantic v2](https://docs.pydantic.dev/) to ensure your input data is robust, clean, and strictly validated.
- **High-Quality Rendering**: Leverages Typst to generate modern, clean, and perfectly aligned PDF documents faster than traditional HTML-to-PDF tools.
- **Regional Localization**: Built-in support for translations, address formatting, and local conventions for:
  - European Union (English, Germany, France)
  - Poland
  - Ukraine
- **Payment QR Codes**: Automatically generates compliant payment QR codes to facilitate quick and accurate payments:
  - **EPC (SEPA) QR**: For EU countries (Germany, France, etc.)
  - **ZBP QR**: For Poland
  - **NBU QR**: For Ukraine
- **Multiple Visual Styles**: Includes ready-to-use templates (`classic`, `modern`, and `compact-bill`) to match your branding.

## Installation

Assuming you are using `pip` or another package manager like `uv` or `poetry`:

```bash
pip install pyfox-invoice
```

## Quick Start

Here's a simple example of generating a localized German invoice with a SEPA QR code in the `classic` style.

```python
import datetime
from decimal import Decimal
from pyfox_invoice.models import (
    Invoice, InvoiceType, LineItem, Party, PaymentDetails, VATLine
)
from pyfox_invoice.regions import DERegion
from pyfox_invoice.renderers import PDFTypstRenderer

# 1. Define the Seller and Buyer
seller = Party(
    name="Tech Solutions GmbH",
    tax_id="DE123456789",
    address="Musterstraße 1, 12345 Berlin, Germany",
)

buyer = Party(
    name="Max Mustermann",
    address="Kaufstraße 2, 80331 München, Germany",
    email="max@mustermann.de",
)

# 2. Define Payment Details (Used for EPC / SEPA QR Code)
payment_details = PaymentDetails(
    iban="DE12345678901234567890",
    currency="EUR",
    extra_fields={
        "beneficiary": "Tech Solutions GmbH",
        "bic": "ABCDEFGH",
        "remittance_information": "Invoice INV-2024-001",
    },
)

# 3. Add Line Items
items = [
    LineItem(
        description="Software Consulting",
        quantity=Decimal("10"),
        unit="h",
        unit_price=Decimal("150.00"),
        total_amount=Decimal("1500.00"),
    )
]

# 4. Construct the Invoice Model
invoice = Invoice(
    number="INV-2024-001",
    issue_date=datetime.date.today(),
    due_date=datetime.date.today() + datetime.timedelta(days=14),
    seller=seller,
    buyer=buyer,
    items=items,
    subtotal=Decimal("1500.00"),
    vat_line=VATLine(tax_rate=Decimal("19"), tax_amount=Decimal("285.00")),
    total=Decimal("1785.00"),
    currency="EUR",
    payment_details=payment_details,
    type=InvoiceType.INVOICE,
)

# 5. Render the PDF (or PNG/SVG)
region = DERegion()
renderer = PDFTypstRenderer(template_name="classic")
pdf_bytes = renderer.render(invoice, region, output_format="pdf")

# You can also generate image formats:
# png_bytes = renderer.render(invoice, region, output_format="png")
# svg_bytes = renderer.render(invoice, region, output_format="svg")

# 6. Save the output
with open("invoice.pdf", "wb") as f:
    f.write(pdf_bytes)
```

<img height=980 alt="Output invoice demo" src ="https://github.com/daniilfurgen/pyfox-invoice/blob/main/docs/images/invoice.png?raw=true">

## Input Data Format

The library operates on a strict schema-driven approach. You must construct Pydantic models for your data, which guarantees the renderer always receives clean information.

Instead of programatic approach, `InvoiceGenerator` object can be used to generated desired document based on input data in JSON.

_Note: The library trusts your input. It does not calculate taxes or totals; it only formats and renders them._

### Core Models

- **`Invoice`**: The root model containing all document information (number, dates, amounts, type).
- **`Party`**: Represents either the buyer or the seller (name, address, tax IDs, contact info).
- **`LineItem`**: Represents a single row in the invoice (description, quantity, unit price, total).
- **`VATLine`**: Summarizes the tax rates and amounts.
- **`PaymentDetails`**: Contains bank information (IBAN, Currency) and `extra_fields` required for generating regional QR codes (like BIC, beneficiary, and remittance info).

### Example of JSON input

```json
{
  "number": "JSON-2024-005",
  "issue_date": "2024-09-01",
  "due_date": "2024-09-15",
  "type": "invoice",
  "currency": "EUR",
  "seller": {
    "name": "Data Systems SAS",
    "tax_id": "FR12345678901",
    "address": "10 Rue de la Paix, 75002 Paris, France",
    "email": "contact@datasystems.fr",
    "phone": "+33 1 23 45 67 89"
  },
  "buyer": {
    "name": "Acme Corp",
    "address": "15 Avenue des Champs-Élysées, 75008 Paris, France"
  },
  "items": [
    {
      "description": "Database Migration (Net Price)",
      "quantity": "1",
      "unit": "project",
      "unit_price": "2000.00",
      "total_amount": "2000.00"
    },
    {
      "description": "Server Maintenance (Net Price)",
      "quantity": "10",
      "unit": "h",
      "unit_price": "80.00",
      "total_amount": "800.00"
    }
  ],
  "subtotal": "2800.00",
  "vat_line": {
    "tax_rate": "20",
    "tax_amount": "560.00",
    "tax_label": "TVA (20%)"
  },
  "total": "3360.00",
  "payment_details": {
    "iban": "FR7612345678901234567890123",
    "currency": "EUR",
    "extra_fields": {
      "beneficiary": "Data Systems SAS",
      "bic": "ABCDFRPP",
      "remittance_information": "Facture JSON-2024-005"
    }
  },
  "note": "Thank you for your business!"
}
```

```py
from pathlib import Path

from pyfox_invoice import InvoiceGenerator
from pyfox_invoice.regions import FRRegion
from pyfox_invoice.renderers import PDFTypstRenderer
from pyfox_invoice.adapters import JsonAdapter


def main() -> None:
    # 1. Load JSON data from a file
    json_path = Path(__file__).parent / "sample_invoice.json"
    with json_path.open() as f:
        json_data = f.read()

    # 2. Create an InvoiceGenerator with the appropriate region, renderer, and adapter
    generator = InvoiceGenerator(
        region=FRRegion(),
        renderer=PDFTypstRenderer(),
        adapter=JsonAdapter(),
    )

    # 3. Render the invoice to PNG and PDF
    png_bytes = generator.generate(json_data, output_format="png")
    pdf_bytes = generator.generate(json_data, output_format="pdf")

    # 4. Save the output
    output_dir = Path(__file__).parent / "images"
    output_dir.mkdir(exist_ok=True)

    (output_dir / "json_invoice.png").write_bytes(png_bytes)
    (output_dir / "json_invoice.pdf").write_bytes(pdf_bytes)
    print("Generated json_invoice.png and json_invoice.pdf from JSON input")


if __name__ == "__main__":
    main()
```

![Example JSON input Invoice](https://github.com/daniilfurgen/pyfox-invoice/blob/main/docs/images/json_invoice.png)

## Templates & Styles

`pyfox-invoice` comes with three built-in Typst templates. When instantiating the `PDFTypstRenderer`, simply pass the `template_name`:

- **`classic`**: A timeless, traditional invoice layout. Best for conservative businesses and detailed line items.
- **`modern`**: A sleek, contemporary design with bolder typography and clean spacing. Great for digital agencies and modern startups.
- **`compact-bill`**: A condensed layout modeled after retail receipts and quick payment requests. Perfect for POS (Point of Sale) or simple one-item transactions.

```python
# Use modern template
renderer = PDFTypstRenderer(template_name="modern")

# Use classic template
renderer = PDFTypstRenderer(template_name="classic")

# Use compact bill template
renderer = PDFTypstRenderer(template_name="compact-bill")
```

|                                                Modern (Proforma)                                                |                                                                                              Classic (Invoice) |
| :-------------------------------------------------------------------------------------------------------------: | -------------------------------------------------------------------------------------------------------------: |
| ![Modern (Proforma)](https://github.com/daniilfurgen/pyfox-invoice/blob/main/docs/images/proforma.png?raw=true) | ![Classic (Invoice)](https://github.com/daniilfurgen/pyfox-invoice/blob/main/docs/images/invoice.png?raw=true) |

|                                           Compact Bill (ISO-A5 paper size)                                           |
| :------------------------------------------------------------------------------------------------------------------: |
| ![Compact Bill](https://github.com/daniilfurgen/pyfox-invoice/blob/main/docs/images/custom_fields_bill.svg?raw=true) |
