routes.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import io
  2. from datetime import datetime
  3. import pandas as pd
  4. from docx import Document
  5. from docxtpl import DocxTemplate
  6. from fastapi import APIRouter, Depends, Form, HTTPException, Request, Response
  7. from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
  8. from fastapi.templating import Jinja2Templates
  9. from sqlalchemy import text
  10. from sqlalchemy.orm import Session
  11. from .auth import ldap_authenticate
  12. from .db import get_session
  13. from .models import Bemerkung, Forderung
  14. from .schemas import BemerkungIn
  15. router = APIRouter()
  16. templates = Jinja2Templates(directory="templates")
  17. @router.get("/", response_class=HTMLResponse)
  18. def index(request: Request):
  19. return RedirectResponse(url="/forderungen")
  20. @router.get("/login", response_class=HTMLResponse)
  21. def login_get(request: Request):
  22. return templates.TemplateResponse("base/login.html", {"request": request})
  23. @router.post("/login")
  24. def login_post(username: str = Form(...), password: str = Form(...)):
  25. user = ldap_authenticate(username, password)
  26. if not user:
  27. raise HTTPException(status_code=401, detail="Invalid credentials")
  28. # For a simple demo we set a cookie (not secure) -- replace with real session in production
  29. response = RedirectResponse(url="/forderungen", status_code=302)
  30. response.set_cookie("user", user["username"])
  31. return response
  32. @router.get("/forderungen", response_class=HTMLResponse)
  33. def forderungsliste(request: Request, db: Session = Depends(get_session), limit: int = 100):
  34. q = db.query(Forderung).order_by(Forderung.faelligkeit.asc()).limit(limit).all()
  35. return templates.TemplateResponse(request, "base/list.html", {"request": request, "forderungen": q})
  36. def number_format(input: float) -> str:
  37. return format(input, "0,.2f").replace(".", ":").replace(",", ".").replace(":", ",")
  38. def date_format(input: datetime) -> str:
  39. return input.strftime("%d.%m.%Y")
  40. templates.env.filters["number_format"] = number_format
  41. templates.env.filters["date_format"] = date_format
  42. @router.get("/forderungen/liste", response_class=HTMLResponse)
  43. def forderungen_liste(request: Request, db: Session = Depends(get_session), limit: int = 100):
  44. context = {
  45. "RechnungsdatumVon": {"selectedDate": "'2000-01-01T00:00:00'"},
  46. "RechnungsdatumBis": {"selectedDate": "'2027-01-01T00:00:00'"},
  47. "Hauptbetrieb": {"selectedOptionValue": "''"},
  48. "Standort": {"selectedOptionValue": "''"},
  49. "Rechnungsnummer": {"text": "''"},
  50. "Kunde": {"selectedOptionValue": "''"},
  51. "Verursacher": {"selectedOptionValue": "''"},
  52. "Fahrzeug": {"selectedOptionValue": "''"},
  53. "Staffel": {"selectedOptionValue": "''"},
  54. "Mahnstufe": {"selectedOptionValue": "''"},
  55. "WiedervorlageVon": {"selectedDate": "'2000-01-01T00:00:00'"},
  56. "WiedervorlageBis": {"selectedDate": "'2027-01-01T00:00:00'"},
  57. "BenutzerSelect": {"selectedOptionValue": "winter"},
  58. }
  59. query = templates.TemplateResponse(request, "forderungen/queries/forderungen_liste.sql", context).body.decode(
  60. "utf-8"
  61. )
  62. # print(query)
  63. # q = db.execute(text("SELECT * FROM [dbo].[Forderungen]"))
  64. q = db.execute(text(query)).fetchall()
  65. col_names = list(q[0]._asdict().keys())
  66. config = {
  67. # "RechnungsdatumVon": (),
  68. # "RechnungsdatumBis": {"selectedDate": "'2027-01-01T00:00:00'"},
  69. "Hauptbetrieb": ("Client_DB", "Hauptbetrieb_Name"),
  70. "Standort": ("Standort_ID", "Standort_Name"),
  71. # "Rechnungsnummer": {"text": "''"},
  72. "Kunde": ("Kunde", "Kunde"),
  73. "Verursacher": ("Verursacher", "Verursacher"),
  74. "Fahrzeug": ("VIN", "VIN"),
  75. "Staffel": ("Staffel", "Staffel"),
  76. "Mahnstufe": ("Mahnstufe", "Mahnstufe"),
  77. # "WiedervorlageVon": {"selectedDate": "'2000-01-01T00:00:00'"},
  78. # "WiedervorlageBis": {"selectedDate": "'2027-01-01T00:00:00'"},
  79. # "BenutzerSelect": {"selectedOptionValue": "winter"},
  80. }
  81. filters = {}
  82. for filter_name, (key, value) in config.items():
  83. filters[filter_name] = {row[col_names.index(key)]: row[col_names.index(value)] for row in q}
  84. # print(filters["Standort"])
  85. return templates.TemplateResponse(
  86. request, "forderungen/liste.html", {"request": request, "forderungen_liste": q, "filter": filters}
  87. )
  88. @router.get("/forderungen/details/{client_db}_{document_no}", response_class=HTMLResponse)
  89. def forderungen_details(
  90. request: Request, client_db: str, document_no: str, db: Session = Depends(get_session), limit: int = 100
  91. ):
  92. context = {
  93. "appsmith": {
  94. "URL": {
  95. "queryParams": {
  96. "Client_DB": "'" + client_db + "'",
  97. "Document_No": "'" + document_no + "'",
  98. }
  99. }
  100. },
  101. "BelegeFilter": {"selectedOptionValue": "D"},
  102. "BenutzerSelect": {"selectedOptionValue": "winter"},
  103. }
  104. template_context = {}
  105. for filename in [
  106. "auftrag_positionen",
  107. "forderung_belege",
  108. "forderung_kommentar",
  109. "forderung_kopf",
  110. "forderung_mahnung",
  111. ]:
  112. query = templates.TemplateResponse(request, f"forderungen/queries/{filename}.sql", context).body.decode("utf-8")
  113. template_context[filename] = db.execute(text(query)).fetchall()
  114. template_context["forderung"] = {
  115. "id": "12345",
  116. "rechnungsnummer": "WERE123445",
  117. "kunde": "Burghard",
  118. "betrag": "120,00",
  119. "faelligkeit": "23.12.1983",
  120. }
  121. return templates.TemplateResponse(request, "forderungen/details.html", template_context)
  122. @router.get("/chat", response_class=HTMLResponse)
  123. def chat(request: Request):
  124. # q = db.query(Forderung).order_by(Forderung.faelligkeit.asc()).limit(limit).all()
  125. return templates.TemplateResponse(request, "base/chat.html")
  126. @router.get("/export/csv")
  127. def export_csv(db: Session = Depends(get_session)):
  128. q = db.query(Forderung).all()
  129. rows = []
  130. for f in q:
  131. rows.append(
  132. {
  133. "id": f.id,
  134. "rechnungsnummer": f.rechnungsnummer,
  135. "kunde": f.kunde.name if f.kunde else "",
  136. "betrag": float(f.betrag),
  137. "faelligkeit": f.faelligkeit.isoformat() if f.faelligkeit else "",
  138. }
  139. )
  140. df = pd.DataFrame(rows)
  141. buf = io.StringIO()
  142. df.to_csv(buf, index=False)
  143. buf.seek(0)
  144. return Response(
  145. content=buf.getvalue(),
  146. media_type="text/csv",
  147. headers={"Content-Disposition": "attachment; filename=forderungen.csv"},
  148. )
  149. @router.get("/export/xlsx")
  150. def export_xlsx(db: Session = Depends(get_session)):
  151. q = db.query(Forderung).all()
  152. rows = []
  153. for f in q:
  154. rows.append(
  155. {
  156. "id": f.id,
  157. "rechnungsnummer": f.rechnungsnummer,
  158. "kunde": f.kunde.name if f.kunde else "",
  159. "betrag": float(f.betrag),
  160. "faelligkeit": f.faelligkeit.isoformat() if f.faelligkeit else "",
  161. }
  162. )
  163. df = pd.DataFrame(rows)
  164. buf = io.BytesIO()
  165. with pd.ExcelWriter(buf, engine="openpyxl") as writer:
  166. df.to_excel(writer, index=False, sheet_name="Forderungen")
  167. buf.seek(0)
  168. return StreamingResponse(
  169. buf,
  170. media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  171. headers={"Content-Disposition": "attachment; filename=forderungen.xlsx"},
  172. )
  173. @router.get("/detail/{id}", response_class=HTMLResponse)
  174. def detail(request: Request, id: int, db: Session = Depends(get_session)):
  175. f = db.query(Forderung).filter(Forderung.id == id).first()
  176. if not f:
  177. raise HTTPException(status_code=404)
  178. return templates.TemplateResponse(request, "base/detail.html", {"request": request, "forderung": f})
  179. @router.post("/detail/{id}/bemerkung")
  180. def save_bemerkung(id: int, data: BemerkungIn, request: Request, db: Session = Depends(get_session)):
  181. user = request.cookies.get("user") or "anonymous"
  182. b = Bemerkung(
  183. forderung_id=id, benutzer=user, bemerkung=data.bemerkung, wiedervorlage_datum=data.wiedervorlage_datum
  184. )
  185. db.add(b)
  186. db.commit()
  187. return RedirectResponse(url=f"/detail/{id}", status_code=302)
  188. @router.get("/detail/{id}/export/docx")
  189. def export_docx(id: int, db: Session = Depends(get_session)):
  190. f = db.query(Forderung).filter(Forderung.id == id).first()
  191. if not f:
  192. raise HTTPException(status_code=404)
  193. doc = Document()
  194. doc.add_heading(f"Rechnung {f.rechnungsnummer}", level=1)
  195. doc.add_paragraph(f'Kunde: {f.kunde.name if f.kunde else ""}')
  196. doc.add_paragraph(f"Betrag: {float(f.betrag)}")
  197. doc.add_paragraph(f"Fälligkeit: {f.faelligkeit}")
  198. buf = io.BytesIO()
  199. doc.save(buf)
  200. buf.seek(0)
  201. return StreamingResponse(
  202. buf,
  203. media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  204. headers={"Content-Disposition": f"attachment; filename=rechnung_{f.rechnungsnummer}.docx"},
  205. )
  206. @router.get("/export/docx")
  207. def export_docx2(id: int, db: Session = Depends(get_session)):
  208. doc = DocxTemplate("templates\\forderungen\\docs\\Mahnung_AHR.docx")
  209. context = {
  210. "Kunde_Name": "Robert Burghard",
  211. "Kunde_Adresse": "Im Waldhof 14a",
  212. "Kunde_PLZ": "61476",
  213. "Kunde_Ort": "Kronberg",
  214. "Datum_heute": "25.03.2026",
  215. "Kunde_Nr": "23121983",
  216. "Fahrzeug_Kennzeichen": "OF-RB 512",
  217. "Rechnung_Nr": "2600007",
  218. "Betrag_SB": "250,00",
  219. "Betrag_USt": "380,00",
  220. }
  221. doc.render(context)
  222. # doc.save("export\\Mahnung_AHR_Burghard.docx")
  223. buf = io.BytesIO()
  224. doc.save(buf)
  225. buf.seek(0)
  226. return StreamingResponse(
  227. buf,
  228. media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  229. headers={"Content-Disposition": "attachment; filename=Mahnung_Burghard.docx"},
  230. )