Test_DB / db.py
Antoni09's picture
Upload 10 files
9c4e7c0 verified
import os
from contextlib import contextmanager
from typing import Any, Dict, List, Optional, Sequence
import psycopg2
from psycopg2.extras import RealDictCursor
DATABASE_URL = os.environ.get("NEON_DATABASE_URL")
if not DATABASE_URL:
raise RuntimeError(
"Brak zmiennej NEON_DATABASE_URL. Ustaw sekret w Hugging Face lub "
"ustaw zmienną środowiskową lokalnie."
)
@contextmanager
def db_conn():
conn = psycopg2.connect(DATABASE_URL, cursor_factory=RealDictCursor)
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def fetch_one(query: str, params: Sequence[Any]) -> Optional[Dict[str, Any]]:
with db_conn() as conn, conn.cursor() as cur:
cur.execute(query, params)
return cur.fetchone()
def fetch_all(query: str, params: Sequence[Any] = ()) -> List[Dict[str, Any]]:
with db_conn() as conn, conn.cursor() as cur:
cur.execute(query, params)
return cur.fetchall()
def execute(query: str, params: Sequence[Any]) -> None:
with db_conn() as conn, conn.cursor() as cur:
cur.execute(query, params)
def create_account(login: str, email: str, password_hash: str) -> int:
with db_conn() as conn, conn.cursor() as cur:
cur.execute(
"""
INSERT INTO accounts (login, password_hash)
VALUES (%s, %s)
RETURNING id
""",
(login, password_hash),
)
account_id = cur.fetchone()["id"]
cur.execute(
"""
INSERT INTO business_profiles (account_id, company_name, owner_name,
address_line, postal_code, city, tax_id, bank_account)
VALUES (%s, '', '', '', '', '', '', '')
""",
(account_id,),
)
return account_id
def update_business(account_id: int, data: Dict[str, str]) -> None:
execute(
"""
UPDATE business_profiles
SET company_name = %s,
owner_name = %s,
address_line = %s,
postal_code = %s,
city = %s,
tax_id = %s,
bank_account = %s
WHERE account_id = %s
""",
(
data["company_name"],
data["owner_name"],
data["address_line"],
data["postal_code"],
data["city"],
data["tax_id"],
data["bank_account"],
account_id,
),
)
def fetch_business_logo(account_id: int) -> Optional[Dict[str, Optional[str]]]:
row = fetch_one(
"""
SELECT logo_mime_type, logo_data_base64
FROM business_profiles
WHERE account_id = %s
""",
(account_id,),
)
if not row:
return None
mime_type = row.get("logo_mime_type")
data_base64 = row.get("logo_data_base64")
if not mime_type or not data_base64:
return None
return {"mime_type": mime_type, "data": data_base64}
def update_business_logo(account_id: int, mime: Optional[str], data_base64: Optional[str]) -> None:
execute(
"""
UPDATE business_profiles
SET logo_mime_type = %s,
logo_data_base64 = %s
WHERE account_id = %s
""",
(mime, data_base64, account_id),
)
def upsert_client(account_id: int, payload: Dict[str, str]) -> int:
row = fetch_one(
"""
SELECT id FROM clients
WHERE account_id = %s AND tax_id = %s
""",
(account_id, payload["tax_id"]),
)
if row:
client_id = row["id"]
execute(
"""
UPDATE clients
SET name = %s,
address_line = %s,
postal_code = %s,
city = %s,
phone = %s
WHERE id = %s
""",
(
payload["name"],
payload["address_line"],
payload["postal_code"],
payload["city"],
payload.get("phone"),
client_id,
),
)
return client_id
with db_conn() as conn, conn.cursor() as cur:
cur.execute(
"""
INSERT INTO clients (account_id, name, address_line, postal_code, city, tax_id, phone)
VALUES (%s, %s, %s, %s, %s, %s, %s)
RETURNING id
""",
(
account_id,
payload["name"],
payload["address_line"],
payload["postal_code"],
payload["city"],
payload["tax_id"],
payload.get("phone"),
),
)
return cur.fetchone()["id"]
def search_clients(account_id: int, term: str, limit: int = 10) -> List[Dict[str, Any]]:
query = (term or "").strip().lower()
like = f"%{query}%"
return fetch_all(
"""
SELECT name, tax_id, address_line, postal_code, city, phone
FROM clients
WHERE account_id = %s
AND (
%s = '' OR
LOWER(COALESCE(name, '')) LIKE %s OR
LOWER(COALESCE(tax_id, '')) LIKE %s
)
ORDER BY LOWER(COALESCE(name, tax_id, '')) ASC
LIMIT %s
""",
(account_id, query, like, like, limit),
)
def insert_invoice(account_id: int, client_id: int, invoice: Dict[str, Any]) -> int:
with db_conn() as conn, conn.cursor() as cur:
cur.execute(
"""
INSERT INTO invoices (account_id, client_id, invoice_number, issued_at,
sale_date, payment_term_days, exemption_note,
total_net, total_vat, total_gross)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING id
""",
(
account_id,
client_id,
invoice["invoice_id"],
invoice["issued_at"],
invoice["sale_date"],
invoice.get("payment_term", 14),
invoice.get("exemption_note"),
invoice["totals"]["net"],
invoice["totals"]["vat"],
invoice["totals"]["gross"],
),
)
invoice_id = cur.fetchone()["id"]
cur.executemany(
"""
INSERT INTO invoice_items (invoice_id, line_no, name, quantity, unit,
vat_code, vat_label, unit_price_net,
unit_price_gross, net_total, vat_amount, gross_total)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""",
[
(
invoice_id,
idx + 1,
item["name"],
item["quantity"],
item.get("unit"),
item.get("vat_code"),
item.get("vat_label"),
item["unit_price_net"],
item["unit_price_gross"],
item["net_total"],
item["vat_amount"],
item["gross_total"],
)
for idx, item in enumerate(invoice["items"])
],
)
cur.executemany(
"""
INSERT INTO invoice_vat_summary (invoice_id, vat_label, net_total, vat_total, gross_total)
VALUES (%s, %s, %s, %s, %s)
""",
[
(
invoice_id,
row["vat_label"],
row["net_total"],
row["vat_total"],
row["gross_total"],
)
for row in invoice["summary"]
],
)
return invoice_id