Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import re, os, datetime
- from fastapi import APIRouter, Request, Form, BackgroundTasks
- from fastapi.responses import HTMLResponse, RedirectResponse, FileResponse
- from backend.routes.pdf_templates import create_documents_pdf
- from backend.config import templates, db
- from datetime import datetime, timedelta
- from bson import ObjectId
- from backend.data.currencies import CURRENCY_SYMBOLS
- from backend.routes.settings_preferences import settings_col
- from typing import List, Optional, Union
- from jinja2 import Undefined
- router = APIRouter()
- invoices_col = db["invoices"]
- customers_col = db["customers"]
- artikelen_col = db["artikelen"]
- DATE_FORMAT_MAP = {
- "DD-MM-YYYY": "%d-%m-%Y",
- "YYYY-MM-DD": "%Y-%m-%d",
- "MM/DD/YYYY": "%m/%d/%Y",
- "DD/MM/YYYY": "%d/%m/%Y",
- "YYYY/MM/DD": "%Y/%m/%d",
- "MMM DD, YYYY": "%b %d, %Y",
- "DD MMM YYYY": "%d %b %Y",
- "YYYY.MM.DD": "%Y.%m.%d",
- "DD.MM.YYYY": "%d.%m.%Y",
- }
- def clean_item(item):
- item = dict(item)
- for k, v in item.items():
- if isinstance(v, datetime):
- item[k] = v.isoformat()
- elif isinstance(v, ObjectId):
- item[k] = str(v)
- if "prijs" in item:
- try:
- item["prijs"] = "{:.2f}".format(float(item["prijs"])).replace(".", ",")
- except Exception:
- item["prijs"] = str(item["prijs"])
- return {k: v for k, v in item.items() if v is not None and v != "Undefined"}
- async def get_invoices_for_user(user_id):
- invoices = await invoices_col.find({"user_id": user_id}).to_list(length=100)
- today = datetime.today().date()
- for invoice in invoices:
- vervaldatum = invoice.get("vervaldatum")
- if vervaldatum:
- try:
- verval_date = datetime.strptime(vervaldatum, "%Y-%m-%d").date()
- if invoice.get("status") not in ["betaald", "geannuleerd"] and verval_date < today:
- invoice["status"] = "verlopen"
- except Exception:
- pass
- return invoices
- @router.get("/admin/invoices", response_class=HTMLResponse)
- async def invoices_overview(request: Request):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- status = request.query_params.get("status", "all")
- query = {"user_id": session_user["id"]}
- if status == "concept":
- query["status"] = "concept"
- elif status == "verzonden":
- query["status"] = "verzonden"
- invoices_cursor = invoices_col.find(query).sort("toegevoegd", -1)
- invoices = []
- async for invoice in invoices_cursor:
- invoice["id"] = str(invoice["_id"])
- klant = await customers_col.find_one({"_id": ObjectId(invoice["klant"])})
- invoice["klant_naam"] = klant["naam"] if klant else ""
- invoice["totaal"] = sum(item["aantal"] * item["prijs"] for item in invoice.get("items", []))
- invoice["status"] = invoice.get("status", "concept")
- invoices.append(invoice)
- prefs = await settings_col.find_one({"user_id": session_user["id"]}) or {}
- date_format_python = DATE_FORMAT_MAP.get(prefs.get("datumnotatie", "DD-MM-YYYY"), "%d-%m-%Y")
- valuta_symbol = CURRENCY_SYMBOLS.get(prefs.get("valuta", "EUR"), "€")
- return templates.TemplateResponse(
- "invoices/invoices.html",
- {
- "request": request,
- "invoices": invoices,
- "active_tab": status,
- "date_format_python": date_format_python,
- "valuta_symbol": valuta_symbol,
- }
- )
- @router.get("/admin/invoices/create", response_class=HTMLResponse)
- async def create_invoice_page(request: Request):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- prefs = await settings_col.find_one({"user_id": session_user["id"]}) or {}
- date_format = prefs.get("datumnotatie", "YYYY-MM-DD")
- today_dt = datetime.utcnow()
- today = today_dt.strftime("%Y-%m-%d")
- vervaldatum = (today_dt + timedelta(days=7)).strftime("%Y-%m-%d")
- today_display = today_dt.strftime("%d-%m-%Y")
- vervaldatum_display = (today_dt + timedelta(days=7)).strftime("%d-%m-%Y")
- # Fetch all items system-wide for dropdowns
- items_raw = await db["items"].find({}).to_list(None)
- artikelList = [clean_item(item) for item in items_raw]
- # Fetch all customers for selector
- customers_raw = await customers_col.find({"user_id": session_user["id"]}).to_list(None)
- customers = [clean_item(c) for c in customers_raw]
- return templates.TemplateResponse(
- "invoices/create_edit_invoice.html",
- {
- "request": request,
- "invoice": None,
- "selected_customer": None,
- "selected_customer_name": "",
- "customers": customers,
- "today": today,
- "nummer": await get_next_invoice_number(session_user["id"]),
- "vervaldatum": vervaldatum,
- "today_display": today_display,
- "vervaldatum_display": vervaldatum_display,
- "date_format": date_format,
- "items": [],
- "section_titles": ['Sectie 1'],
- "artikelList": artikelList,
- "valuta_symbol": CURRENCY_SYMBOLS.get(prefs.get("valuta", "EUR"), "€"),
- }
- )
- @router.post("/admin/invoices/create")
- async def create_invoice(
- request: Request,
- klant: str = Form(...),
- datum: str = Form(""),
- vervaldatum: str = Form(""),
- nummer: str = Form(...),
- opmerkingen: str = Form(""),
- korting: str = Form("0"),
- korting_type: str = Form("€"),
- section_titles: Optional[List[str]] = Form(None),
- item_naam: List[str] = Form(...),
- item_beschrijving: List[str] = Form(...),
- item_aantal: List[str] = Form(...),
- item_prijs: List[str] = Form(...),
- item_section: List[int] = Form(...),
- ):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- # Normalize section_titles
- if section_titles and isinstance(section_titles, str):
- section_titles = [section_titles]
- # Set default dates if not provided
- today = datetime.today()
- if not datum:
- datum = today.strftime("%Y-%m-%d")
- if not vervaldatum:
- vervaldatum = (today + timedelta(days=14)).strftime("%Y-%m-%d")
- items = []
- for naam, beschrijving, aantal, prijs, section in zip(item_naam, item_beschrijving, item_aantal, item_prijs, item_section):
- items.append({
- "naam": naam,
- "beschrijving": beschrijving,
- "aantal": int(aantal),
- "prijs": float(prijs.replace(",", ".")),
- "section": int(section)
- })
- invoice = {
- "klant": klant,
- "datum": datum,
- "vervaldatum": vervaldatum,
- "nummer": nummer,
- "opmerkingen": opmerkingen,
- "korting": korting,
- "korting_type": korting_type,
- "section_titles": section_titles or [],
- "items": items,
- "user_id": session_user["id"],
- "toegevoegd": datetime.utcnow(),
- "status": "concept",
- }
- await invoices_col.insert_one(invoice)
- return RedirectResponse(f"/admin/invoices/{nummer}/view", status_code=303)
- @router.get("/admin/invoices/{nummer}/edit", response_class=HTMLResponse)
- async def edit_invoice_page(request: Request, nummer: str):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- invoice = await invoices_col.find_one({"nummer": nummer, "user_id": session_user["id"]})
- if not invoice:
- return HTMLResponse(status_code=404, content="Factuur niet gevonden")
- items = [clean_item(item) for item in invoice.get("items", [])]
- items_raw = await db["items"].find({}).to_list(None)
- artikelList = [clean_item(item) for item in items_raw if item is not None]
- artikelList = [item for item in artikelList if item is not None and item != "Undefined" and is_serializable(item)]
- prefs = await settings_col.find_one({"user_id": session_user["id"]}) or {}
- valuta_symbol = CURRENCY_SYMBOLS.get(prefs.get("valuta", "EUR"), "€")
- klant_obj = None
- if invoice.get("klant"):
- try:
- klant_obj = await customers_col.find_one({"_id": ObjectId(invoice["klant"])})
- except Exception:
- klant_obj = None
- customers_raw = await customers_col.find({"user_id": session_user["id"]}).to_list(None)
- customers = [clean_item(c) for c in customers_raw if c is not None]
- customers = [c for c in customers if c is not None and c != "Undefined" and is_serializable(c)]
- return templates.TemplateResponse(
- "invoices/create_edit_invoice.html",
- {
- "request": request,
- "invoice": invoice,
- "selected_customer": invoice.get("klant"),
- "selected_customer_name": klant_obj["naam"] if klant_obj else "",
- "customers": customers,
- "today": invoice.get("datum", datetime.utcnow().strftime("%Y-%m-%d")),
- "nummer": invoice.get("nummer", ""),
- "vervaldatum": invoice.get("vervaldatum", ""),
- "items": items,
- "section_titles": invoice.get("section_titles", ['Sectie 1']),
- "artikelList": artikelList,
- "valuta_symbol": valuta_symbol,
- }
- )
- @router.post("/admin/invoices/{nummer}/edit")
- async def update_invoice(
- request: Request,
- nummer: str,
- klant: str = Form(...),
- datum: str = Form(...),
- vervaldatum: str = Form(""),
- opmerkingen: str = Form(""),
- korting: str = Form("0"),
- korting_type: str = Form("€"),
- section_titles: Optional[List[str]] = Form(None),
- item_naam: list[str] = Form(...),
- item_beschrijving: list[str] = Form(...),
- item_aantal: list[str] = Form(...),
- item_prijs: list[str] = Form(...),
- item_section: list[str] = Form(...),
- ):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- items = []
- for naam, beschrijving, aantal, prijs, section in zip(item_naam, item_beschrijving, item_aantal, item_prijs, item_section):
- items.append({
- "naam": naam,
- "beschrijving": beschrijving,
- "aantal": int(aantal),
- "prijs": float(prijs.replace(",", ".")),
- "section": int(section)
- })
- if section_titles and isinstance(section_titles, str):
- section_titles = [section_titles]
- await invoices_col.update_one(
- {"nummer": nummer, "user_id": session_user["id"]},
- {"$set": {
- "klant": klant,
- "datum": datum,
- "vervaldatum": vervaldatum,
- "nummer": nummer,
- "opmerkingen": opmerkingen,
- "korting": korting,
- "korting_type": korting_type,
- "items": items,
- "section_titles": section_titles or [],
- "status": "concept"
- }}
- )
- return RedirectResponse(f"/admin/invoices/{nummer}/view", status_code=303)
- async def get_next_invoice_number(user_id):
- customization_col = db["customization"]
- customization = await customization_col.find_one({"user_id": user_id}) or {}
- components = customization.get("facturen_components", [])
- prefix = ""
- seq_length = 4
- for comp in components:
- if comp.get("type") == "volgnummer":
- seq_length = int(comp.get("length", 4))
- break
- v = comp.get("value", "")
- if comp.get("type") == "scheidingsteken":
- prefix += "-"
- elif comp.get("type") in ("voorvoegsel", "reeksen"):
- prefix += v
- prefix_regex = re.escape(prefix) + r"\d{" + str(seq_length) + r"}$"
- last_doc = await invoices_col.find_one(
- {"nummer": {"$regex": prefix_regex}, "user_id": user_id},
- sort=[("nummer", -1)]
- )
- last_number = last_doc["nummer"] if last_doc else None
- def generate_invoice_number(components, last_number=None):
- number = ""
- seq_length = 4
- for comp in components:
- t = comp.get("type")
- v = comp.get("value", "")
- if t == "scheidingsteken":
- number += "-"
- elif t == "voorvoegsel":
- number += v
- elif t == "reeksen":
- number += v
- elif t == "volgnummer":
- seq_length = int(comp.get("length", 4))
- number += "{SEQ}"
- seq = 1
- if last_number:
- regex = re.escape(number).replace("\\{SEQ\\}", f"(\\d{{{seq_length}}})")
- m = re.match(regex, last_number)
- if m:
- seq = int(m.group(1)) + 1
- return number.replace("{SEQ}", str(seq).zfill(seq_length))
- if components:
- return generate_invoice_number(components, last_number)
- else:
- last = await invoices_col.find({"user_id": user_id}).sort("toegevoegd", -1).to_list(1)
- if last and "nummer" in last[0]:
- num = last[0]["nummer"]
- try:
- prefix, number = num.split("-")
- return f"{prefix}-{int(number)+1:04d}"
- except Exception:
- return "Fac-0001"
- return "Fac-0001"
- @router.post("/admin/invoices/{nummer}/delete")
- async def delete_invoice(request: Request, nummer: str):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- await invoices_col.delete_one({"nummer": nummer, "user_id": session_user["id"]})
- return RedirectResponse("/admin/invoices", status_code=303)
- @router.get("/admin/invoices/{nummer}/view", response_class=HTMLResponse)
- async def view_invoice(request: Request, nummer: str):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- invoice = await invoices_col.find_one({"nummer": nummer, "user_id": session_user["id"]})
- if not invoice:
- return HTMLResponse(status_code=404, content="Factuur niet gevonden")
- vervaldatum = invoice.get("vervaldatum")
- if vervaldatum:
- try:
- verval_date = datetime.strptime(vervaldatum, "%Y-%m-%d").date()
- if invoice.get("status") not in ["betaald", "geannuleerd"] and verval_date < datetime.today().date():
- invoice["status"] = "verlopen"
- except Exception:
- pass
- invoice["totaal"] = sum(item["aantal"] * item["prijs"] for item in invoice.get("items", []))
- prefs = await settings_col.find_one({"user_id": session_user["id"]}) or {}
- valuta_symbol = CURRENCY_SYMBOLS.get(prefs.get("valuta", "EUR"), "€")
- company = await db["company"].find_one({"user_id": session_user["id"]}) or {}
- facturen = await invoices_col.find({"user_id": session_user["id"]}).to_list(length=100)
- for factuur in facturen:
- factuur["totaal"] = sum(item["aantal"] * item["prijs"] for item in factuur.get("items", []))
- klant_id = factuur.get("klant")
- try:
- klant_id = ObjectId(klant_id)
- except Exception:
- pass
- klant_obj = await db["customers"].find_one({"_id": klant_id})
- factuur["klant_naam"] = klant_obj["naam"] if klant_obj and "naam" in klant_obj else str(factuur["klant"])
- return templates.TemplateResponse(
- "invoices/view_invoice.html",
- {
- "request": request,
- "company_name": company.get("bedrijfsnaam", ""),
- "facturen": facturen,
- "invoice": invoice,
- "valuta_symbol": valuta_symbol,
- }
- )
- @router.get("/admin/invoices/{nummer}/mark-sent")
- async def mark_sent_get(request: Request, nummer: str):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- await invoices_col.update_one({"nummer": nummer, "user_id": session_user["id"]}, {"$set": {"status": "verzonden"}})
- return RedirectResponse(f"/admin/invoices", status_code=303)
- @router.get("/admin/invoices/{nummer}/mark-paid")
- async def mark_paid_get(request: Request, nummer: str):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- await invoices_col.update_one({"nummer": nummer, "user_id": session_user["id"]}, {"$set": {"status": "betaald"}})
- return RedirectResponse(f"/admin/invoices", status_code=303)
- @router.get("/admin/invoices/{nummer}/mark-cancelled")
- async def mark_cancelled_get(request: Request, nummer: str):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- await invoices_col.update_one({"nummer": nummer, "user_id": session_user["id"]}, {"$set": {"status": "geannuleerd"}})
- return RedirectResponse(f"/admin/invoices", status_code=303)
- @router.get("/admin/invoices/{nummer}/copy")
- async def copy_invoice(request: Request, nummer: str):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- orig = await invoices_col.find_one({"nummer": nummer, "user_id": session_user["id"]})
- if not orig:
- return RedirectResponse("/admin/invoices")
- new_nummer = await get_next_invoice_number(session_user["id"])
- new_invoice = orig.copy()
- new_invoice.pop("_id", None)
- new_invoice["nummer"] = new_nummer
- await invoices_col.insert_one(new_invoice)
- return RedirectResponse(f"/admin/invoices/{new_nummer}/edit", status_code=303)
- @router.get("/admin/invoices/{nummer}/pdf")
- async def download_invoice_pdf(request: Request, nummer: str, background_tasks: BackgroundTasks):
- session_user = request.session.get("user")
- if not session_user:
- return RedirectResponse("/login")
- invoice = await invoices_col.find_one({"nummer": nummer, "user_id": session_user["id"]})
- if not invoice:
- return HTMLResponse(status_code=404, content="Factuur niet gevonden")
- company = await db["company"].find_one({"user_id": session_user["id"]}) or {}
- company_info = [
- company.get("bedrijfsnaam", ""),
- company.get("adres", ""),
- f"{company.get('postcode', '')} {company.get('stad', '')}",
- f"{company.get('provincie', '')}, {company.get('land', '')}",
- company.get("telefoon", ""),
- ]
- logo_path = company.get("logo", "/static/uploads/logos/company_logo.png")
- if logo_path.startswith("/static/"):
- logo_path = os.path.join("/opt/invoiceatlas/frontend", logo_path.lstrip("/"))
- customer = await customers_col.find_one({"_id": ObjectId(invoice["klant"])}) if invoice.get("klant") else {}
- customer_info = [
- customer.get("naam", ""),
- customer.get("contactpersoon", ""),
- customer.get("email", ""),
- customer.get("telefoon", ""),
- customer.get("adres", ""),
- f"{customer.get('postcode', '')} {customer.get('stad', '')}",
- customer.get("land", ""),
- ]
- items = []
- for item in invoice.get("items", []):
- items.append({
- "title": item.get("naam", ""),
- "description": item.get("beschrijving", ""),
- "quantity": item.get("aantal", 1),
- "price": item.get("prijs", 0.0),
- "total": item.get("aantal", 1) * item.get("prijs", 0.0),
- "section": item.get("section", None)
- })
- section_titles = invoice.get("section_titles", [])
- subtotaal = sum(i["total"] for i in items)
- korting = float(invoice.get("korting", 0))
- btw = 0.0
- totaal = subtotaal - korting + btw
- pdf_data = {
- "logo_path": logo_path,
- "company_name": company.get("bedrijfsnaam", "Bedrijfsnaam"),
- "company_info": company_info,
- "customer_info": customer_info,
- "kvk_nummer": company.get("kvk_nummer", ""),
- "btw_nummer": company.get("btw_nummer", ""),
- "iban": company.get("iban", ""),
- "section_titles": section_titles,
- "factuur_nummer": invoice["nummer"],
- "factuur_datum": invoice["datum"],
- "vervaldatum": invoice["vervaldatum"],
- "items": items,
- "opmerkingen": invoice.get("opmerkingen", ""),
- "subtotaal": subtotaal,
- "kortingen": korting,
- "btw": btw,
- "totaal": totaal
- }
- pdf_filename = create_documents_pdf(pdf_data)
- background_tasks.add_task(os.remove, pdf_filename)
- return FileResponse(
- pdf_filename,
- media_type="application/pdf",
- headers={"Content-Disposition": "inline; filename=factuur.pdf"}
- )
Advertisement
Add Comment
Please, Sign In to add comment