mirror of
https://github.com/TronoSfera/Law.git
synced 2026-05-19 02:23:45 +03:00
84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
import unicodedata
|
|
|
|
|
|
def _ascii_text(value: Any) -> str:
|
|
text = str(value or "")
|
|
normalized = unicodedata.normalize("NFKD", text)
|
|
return normalized.encode("ascii", "ignore").decode("ascii")
|
|
|
|
|
|
def _escape_pdf_text(value: str) -> str:
|
|
return value.replace("\\", "\\\\").replace("(", "\\(").replace(")", "\\)")
|
|
|
|
|
|
def _build_content_stream(lines: list[str]) -> bytes:
|
|
safe_lines = [_escape_pdf_text(_ascii_text(line)) for line in lines]
|
|
if not safe_lines:
|
|
safe_lines = ["Invoice"]
|
|
parts = ["BT", "/F1 11 Tf", "50 800 Td"]
|
|
for index, line in enumerate(safe_lines):
|
|
if index == 0:
|
|
parts.append(f"({line}) Tj")
|
|
else:
|
|
parts.append("T*")
|
|
parts.append(f"({line}) Tj")
|
|
parts.append("ET")
|
|
return "\n".join(parts).encode("latin-1", errors="ignore")
|
|
|
|
|
|
def build_invoice_pdf_bytes(
|
|
*,
|
|
invoice_number: str,
|
|
amount: float,
|
|
currency: str,
|
|
status: str,
|
|
issued_at: datetime | None,
|
|
paid_at: datetime | None,
|
|
payer_display_name: str,
|
|
request_track_number: str,
|
|
issued_by_name: str | None,
|
|
requisites: dict[str, Any] | None,
|
|
) -> bytes:
|
|
lines = [
|
|
f"Invoice: {invoice_number}",
|
|
f"Request: {request_track_number}",
|
|
f"Payer: {payer_display_name}",
|
|
f"Amount: {amount:.2f} {currency}",
|
|
f"Status: {status}",
|
|
f"Issued at: {issued_at.isoformat() if issued_at else '-'}",
|
|
f"Paid at: {paid_at.isoformat() if paid_at else '-'}",
|
|
f"Issued by: {issued_by_name or '-'}",
|
|
"Requisites:",
|
|
]
|
|
req = dict(requisites or {})
|
|
if req:
|
|
for key in sorted(req.keys()):
|
|
lines.append(f"{key}: {req.get(key)}")
|
|
else:
|
|
lines.append("-")
|
|
|
|
stream = _build_content_stream(lines)
|
|
objects = [
|
|
b"1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n",
|
|
b"2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n",
|
|
b"3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >> endobj\n",
|
|
b"4 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj\n",
|
|
f"5 0 obj << /Length {len(stream)} >> stream\n".encode("latin-1") + stream + b"\nendstream endobj\n",
|
|
]
|
|
|
|
body = b"%PDF-1.4\n"
|
|
offsets = [0]
|
|
for obj in objects:
|
|
offsets.append(len(body))
|
|
body += obj
|
|
xref_offset = len(body)
|
|
body += f"xref\n0 {len(objects)+1}\n".encode("latin-1")
|
|
body += b"0000000000 65535 f \n"
|
|
for offset in offsets[1:]:
|
|
body += f"{offset:010d} 00000 n \n".encode("latin-1")
|
|
body += f"trailer << /Size {len(objects)+1} /Root 1 0 R >>\nstartxref\n{xref_offset}\n%%EOF\n".encode("latin-1")
|
|
return body
|