Sfoglia il codice sorgente

Forderung-Abfragen halb-dynamisch eingebunden

gc-server3 1 mese fa
parent
commit
26f21aae4d

+ 2 - 1
app/main.py

@@ -12,7 +12,8 @@ app.include_router(router)
 @app.on_event("startup")
 def on_startup():
     # Create tables for demo if they do not exist
-    Base.metadata.create_all(bind=engine)
+    # Base.metadata.create_all(bind=engine)
+    pass
 
 
 app.mount("/static", StaticFiles(directory="static"), name="static")

+ 80 - 3
app/routes.py

@@ -1,4 +1,5 @@
 import io
+from datetime import datetime
 
 import pandas as pd
 from docx import Document
@@ -6,6 +7,7 @@ from docxtpl import DocxTemplate
 from fastapi import APIRouter, Depends, Form, HTTPException, Request, Response
 from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
 from fastapi.templating import Jinja2Templates
+from sqlalchemy import text
 from sqlalchemy.orm import Session
 
 from .auth import ldap_authenticate
@@ -41,7 +43,82 @@ def login_post(username: str = Form(...), password: str = Form(...)):
 @router.get("/forderungen", response_class=HTMLResponse)
 def forderungsliste(request: Request, db: Session = Depends(get_session), limit: int = 100):
     q = db.query(Forderung).order_by(Forderung.faelligkeit.asc()).limit(limit).all()
-    return templates.TemplateResponse("base/list.html", {"request": request, "forderungen": q})
+    return templates.TemplateResponse(request, "base/list.html", {"request": request, "forderungen": q})
+
+
+def number_format(input: float) -> str:
+    return format(input, "0,.2f").replace(".", ":").replace(",", ".").replace(":", ",")
+
+
+def date_format(input: datetime) -> str:
+    return input.strftime("%d.%m.%Y")
+
+
+templates.env.filters["number_format"] = number_format
+templates.env.filters["date_format"] = date_format
+
+
+@router.get("/forderungen/liste", response_class=HTMLResponse)
+def forderungen_liste(request: Request, db: Session = Depends(get_session), limit: int = 100):
+    context = {
+        "RechnungsdatumVon": {"selectedDate": "'2000-01-01T00:00:00'"},
+        "RechnungsdatumBis": {"selectedDate": "'2027-01-01T00:00:00'"},
+        "Hauptbetrieb": {"selectedOptionValue": "''"},
+        "Standort": {"selectedOptionValue": "''"},
+        "Rechnungsnummer": {"text": "''"},
+        "Kunde": {"selectedOptionValue": "''"},
+        "Verursacher": {"selectedOptionValue": "''"},
+        "Fahrzeug": {"selectedOptionValue": "''"},
+        "Staffel": {"selectedOptionValue": "''"},
+        "Mahnstufe": {"selectedOptionValue": "''"},
+        "WiedervorlageVon": {"selectedDate": "'2000-01-01T00:00:00'"},
+        "WiedervorlageBis": {"selectedDate": "'2027-01-01T00:00:00'"},
+        "BenutzerSelect": {"selectedOptionValue": "winter"},
+    }
+    query = templates.TemplateResponse(request, "forderungen/queries/forderungen_liste.sql", context).body.decode(
+        "utf-8"
+    )
+    # print(query)
+    # q = db.execute(text("SELECT * FROM [dbo].[Forderungen]"))
+    q = db.execute(text(query))
+    return templates.TemplateResponse(request, "forderungen/liste.html", {"request": request, "forderungen_liste": q})
+
+
+@router.get("/forderungen/details/{client_db}_{document_no}", response_class=HTMLResponse)
+def forderungen_details(
+    request: Request, client_db: str, document_no: str, db: Session = Depends(get_session), limit: int = 100
+):
+    context = {
+        "appsmith": {
+            "URL": {
+                "queryParams": {
+                    "Client_DB": "'" + client_db + "'",
+                    "Document_No": "'" + document_no + "'",
+                }
+            }
+        },
+        "BelegeFilter": {"selectedOptionValue": "D"},
+        "BenutzerSelect": {"selectedOptionValue": "winter"},
+    }
+
+    template_context = {}
+    for filename in [
+        "auftrag_positionen",
+        "forderung_belege",
+        "forderung_kommentar",
+        "forderung_kopf",
+        "forderung_mahnung",
+    ]:
+        query = templates.TemplateResponse(request, f"forderungen/queries/{filename}.sql", context).body.decode("utf-8")
+        template_context[filename] = db.execute(text(query)).fetchall()
+    template_context["forderung"] = {
+        "id": "12345",
+        "rechnungsnummer": "WERE123445",
+        "kunde": "Burghard",
+        "betrag": "120,00",
+        "faelligkeit": "23.12.1983",
+    }
+    return templates.TemplateResponse(request, "forderungen/details.html", template_context)
 
 
 @router.get("/chat", response_class=HTMLResponse)
@@ -106,7 +183,7 @@ def detail(request: Request, id: int, db: Session = Depends(get_session)):
     f = db.query(Forderung).filter(Forderung.id == id).first()
     if not f:
         raise HTTPException(status_code=404)
-    return templates.TemplateResponse("base/detail.html", {"request": request, "forderung": f})
+    return templates.TemplateResponse(request, "base/detail.html", {"request": request, "forderung": f})
 
 
 @router.post("/detail/{id}/bemerkung")
@@ -142,7 +219,7 @@ def export_docx(id: int, db: Session = Depends(get_session)):
 
 @router.get("/export/docx")
 def export_docx2(id: int, db: Session = Depends(get_session)):
-    doc = DocxTemplate("templates\\docx\\Mahnung_AHR.docx")
+    doc = DocxTemplate("templates\\forderungen\\docs\\Mahnung_AHR.docx")
     context = {
         "Kunde_Name": "Robert Burghard",
         "Kunde_Adresse": "Im Waldhof 14a",

+ 143 - 0
templates/forderungen/details.html

@@ -0,0 +1,143 @@
+{% extends "base/base.html" %}
+{% block content %}
+<div class="card mb-3">
+  <div class="card-body">
+    <h5 class="card-title">Rechnung {{ forderung.rechnungsnummer }}</h5>
+    <p>Kunde: {{ forderung.kunde.name if forderung.kunde else '' }}</p>
+    <p>Betrag: {{ forderung.betrag }}</p>
+    <p>Fälligkeit: {{ forderung.faelligkeit }}</p>
+    <div class="mt-3">
+      <a class="btn btn-outline-secondary me-2" href="/detail/{{ forderung.id }}/export/docx">Export DOCX</a>
+      <a class="btn btn-outline-primary" href="mailto:?subject=Rechnung%20{{ forderung.rechnungsnummer }}&body=Sie%20finden%20Details%20unter%20{{ request.url }}">E-Mail (mailto)</a>
+    </div>
+  </div>
+</div>
+
+<div class="card">
+  <div class="card-body">
+    <h6>Neue Bemerkung</h6>
+    <form method="post" action="/detail/{{ forderung.id }}/bemerkung">
+      <div class="mb-2">
+        <textarea name="bemerkung" class="form-control" rows="3"></textarea>
+      </div>
+      <div class="mb-2">
+        <label>Wiedervorlage</label>
+        <input type="date" name="wiedervorlage_datum" class="form-control" />
+      </div>
+      <button class="btn btn-primary" type="submit">Speichern</button>
+      <a class="btn btn-secondary ms-2" href="/forderungen">Zurück</a>
+    </form>
+  </div>
+</div>
+
+{% if forderung.bemerkungen %}
+<div class="mt-3">
+  <h5>Bemerkungen</h5>
+  <ul class="list-group">
+    {% for b in forderung.bemerkungen %}
+    <li class="list-group-item">{{ b.zeitstempel }} - {{ b.benutzer }}: {{ b.bemerkung }} {% if b.wiedervorlage_datum %} (WV: {{ b.wiedervorlage_datum }}){% endif %}</li>
+    {% endfor %}
+  </ul>
+</div>
+{% endif %}
+
+<h2>Forderung Details</h2>
+
+<table class="table table-striped">
+  <tbody>
+    {% for row in forderung_kopf %}
+    
+    {% for value in row %}
+    <tr>
+      <th>{{ loop.index }}</th>
+      <td>{{ value }}</td>
+    </tr>
+      {% endfor %}
+    {% endfor %}
+  </tbody>
+</table>
+
+<h2>Kommentare</h2>
+
+<table class="table table-striped">
+  <thead>
+    <tr>
+        <th>Timestamp</th>
+        <th>Name</th>
+        <th>Rolle</th>
+        <th>Begründung</th>
+        <th>Wiedervorlage</th>
+        <th>Kommentar</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for row in forderung_kommentar %}
+    <tr>
+      <td>{{ row.Timestamp|date_format }}</td>
+      <td>{{ row.Name }}</td>
+      <td>{{ row.Rolle }}</td>
+      <td>{{ row.Begründung }}</td>
+      <td>{{ row.Wiedervorlage }}</td>
+      <td>{{ row.Kommentar }}</td>
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+
+
+<h2>Mahnungen</h2>
+
+<table class="table table-striped">
+  <thead>
+    <tr>
+        <th>Kunde_Nr</th>
+        <th>Mahnung_Nr</th>
+        <th>Mahndatum</th>
+        <th>Mahnstufe</th>
+        <th>Rechnung_Nr</th>
+        <th>Rechnung_Datum</th>
+        <th>Betrag</th>
+        <th>Offen</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for row in forderung_mahnung %}
+    <tr>
+      <td>{{ row.Kunde_Nr }}</td>
+      <td>{{ row.Mahnung_Nr }}</td>
+      <td>{{ row.Mahndatum|date_format }}</td>
+      <td>{{ row.Rechnung_Nr }}</td>
+      <td>{{ row.Rechnung_Datum|date_format }}</td>
+      <td>{{ row.Betrag|number_format }}</td>
+      <td>{{ row.Offen|number_format }}</td>
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+
+
+
+<h2>Auftragspositionen</h2>
+
+<table class="table table-striped">
+  <thead>
+    <tr>
+        <th>Auftrag_Nr</th>
+        <th>Auftrag_Position</th>
+        <th>Betrag</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for row in auftrag_positionen %}
+    <tr>
+      <td>{{ row.Auftrag_Nr }}</td>
+      <td>{{ row.Auftrag_Position }}</td>
+      <td>{{ row.Betrag|number_format }}</td>
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+
+
+
+{% endblock %}

+ 0 - 0
templates/docx/Mahnung_AHR.docx → templates/forderungen/docs/Mahnung_AHR.docx


+ 43 - 0
templates/forderungen/liste.html

@@ -0,0 +1,43 @@
+{% extends "base/base.html" %}
+{% block content %}
+<div class="d-flex mb-3">
+  <a class="btn btn-secondary me-2" href="/export/csv">Export CSV</a>
+  <a class="btn btn-secondary" href="/export/xlsx">Export XLSX</a>
+</div>
+<table class="table table-striped">
+  <thead>
+    <tr>
+        <th>.</th>
+        <th>Filiale</th>
+        <th>Kunde</th>
+        <th>Bereich</th>
+        <th>Verursacher</th>
+        <th>RG-Nr.</th>
+        <th>RG-Datum</th>
+        <th>Tage</th>
+        <th>Mahnstufe</th>
+        <th>offen</th>
+        <th>Kunde<br>offen ges.</th>
+        <th>Kommentar</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for row in forderungen_liste %}
+    <tr>
+      <td><a href="/forderungen/details/{{ row.Client_DB }}_{{ row.Document_No }}" class="btn btn-sm btn-primary">X</a></td>
+      <td>{{ row.Standort_Name }}</td>
+      <td>{{ row.Kunde }}</td>
+      <td>{{ row.Bereich }}</td>
+      <td>{{ row.Verursacher }}</td>
+      <td>{{ row.Document_No }}</td>
+      <td>{{ row.Invoice_Date|date_format }}</td>
+      <td>{{ row.Tage }}</td>
+      <td>{{ row.Mahnstufe }}</td>
+      <td>{{ row.offen|number_format }}</td>
+      <td>{{ row.offen_Kunde_gesamt|number_format }}</td>
+      <td>{{ row.Kommentar_Fibu or '' }}</td>
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+{% endblock %}

+ 8 - 0
templates/forderungen/queries/auftrag_positionen.sql

@@ -0,0 +1,8 @@
+SELECT TOP 100 [Auftrag_Nr]
+     , [Auftrag_Position]
+     , [Betrag]
+FROM [dbo].[Auftrag_Positionen]
+WHERE (
+	Client_DB = {{ appsmith.URL.queryParams.Client_DB }}
+AND [Rechnung_Nr] = {{ appsmith.URL.queryParams.Document_No }}
+) OR 1 != 1

+ 30 - 0
templates/forderungen/queries/forderung_belege.sql

@@ -0,0 +1,30 @@
+SELECT TOP 1000 [Client_DB]
+     , [Kunde_Nr]
+     , [Fahrzeug_Nr]
+     , [Beleg_Nr]
+     , [Bezug_Beleg_Nr]
+     , [Beleg_Datum]
+     , [Beleg_Beschreibung]
+     , [Betrag]
+     , [Benutzer]
+     , iif([Open] = '0', 'N', 'J') as [Status]
+     , CASE
+           WHEN [Document Type] = '2' THEN 'R'
+           WHEN [Document Type] = '3' THEN 'R'
+           WHEN [Document Type] = '1' THEN 'Z'
+           WHEN [Document Type] = '0' THEN 'K'
+           ELSE 'X' END AS [Beleg_Art]
+FROM [dbo].[Forderung_Belege]
+WHERE (
+	[Client_DB] = {{ appsmith.URL.queryParams.Client_DB }}
+AND [Kunde_Nr] IN (SELECT [Kunde_Nr] FROM [dbo].[Forderung_Mahnung] WHERE [Rechnung_Nr] = {{ appsmith.URL.queryParams.Document_No }} )
+AND (
+	('D' = '{{ BelegeFilter.selectedOptionValue }}')
+	OR
+	('F' = '{{ BelegeFilter.selectedOptionValue }}' AND [Fahrzeug_Nr] IN (SELECT [Fahrzeug_Nr] FROM [dbo].[Forderung_Mahnung] WHERE [Rechnung_Nr] = {{ appsmith.URL.queryParams.Document_No }} )) 
+
+)
+	
+) OR 1 != 1
+
+ORDER BY Beleg_Datum

+ 10 - 0
templates/forderungen/queries/forderung_kommentar.sql

@@ -0,0 +1,10 @@
+SELECT Timestamp,
+ Name,
+ Rolle,
+ Begründung,
+ Wiedervorlage,
+ Kommentar
+ 
+FROM dbo.Forderungen_Kommentar_Benutzer
+WHERE Client_DB = {{ appsmith.URL.queryParams.Client_DB }}
+AND Document_No = {{ appsmith.URL.queryParams.Document_No }}

+ 4 - 0
templates/forderungen/queries/forderung_kopf.sql

@@ -0,0 +1,4 @@
+SELECT * 
+FROM dbo.Forderungen
+WHERE Client_DB = {{ appsmith.URL.queryParams.Client_DB }}
+AND Document_No = {{ appsmith.URL.queryParams.Document_No }}

+ 15 - 0
templates/forderungen/queries/forderung_mahnung.sql

@@ -0,0 +1,15 @@
+SELECT TOP 100 [Client_DB]
+     , [Kunde_Nr]
+     , [Mahnung_Nr]
+     , [Mahndatum]
+     , [Mahnstufe]
+     , [Rechnung_Nr]
+     , [Rechnung_Datum]
+     , [Betrag]
+     , [Offen]
+FROM [dbo].[Forderung_Mahnung]
+WHERE (
+	Client_DB = {{ appsmith.URL.queryParams.Client_DB }}
+AND [Kunde_Nr] IN (SELECT [Kunde_Nr] FROM [dbo].[Forderung_Mahnung] WHERE [Rechnung_Nr] = {{ appsmith.URL.queryParams.Document_No }} )
+) OR 1 != 1
+ORDER BY Mahndatum

+ 52 - 0
templates/forderungen/queries/forderungen_liste.sql

@@ -0,0 +1,52 @@
+SELECT [T1].[Client_DB]
+     , [T1].[Hauptbetrieb_ID]
+     , [T1].[Hauptbetrieb_Name]
+     , [T1].[Standort_ID]
+     , [T1].[Standort_Name]     
+     , [T1].[Document_No]
+     , [T1].[Rechnung_Gutschrift]     
+     , [T1].[User_ID]
+     , [T1].[VIN]
+     , [T1].[Comment]
+     , [T1].[offen]
+     , [T1].[offen_Kunde_gesamt]
+     , [T1].[Invoice_Date]
+     , [T1].[Verursacher]
+     , [T1].[Kunde]
+     , [T1].[Beleg]
+     , [T1].[Bereich]
+     , [T1].[Tage]
+		 , case when [T1].[Tage] > 90 then 3
+		 when [T1].[Tage] > 60 then 2
+		   when [T1].[Tage] > 30 then 1
+			 else 0 end as [Stufe]
+     , [T1].[Staffel]
+     , [T1].[Mahnstufe]
+     , [T1].[Forderungsart]
+     , [T1].[Abwarten]
+     , [T1].[Verursacher_Benutzer_ID]
+	   , T3.[Kommentar_Fibu]
+     , T3.[Kommentar_Abteilung] 
+		 , T3.[Wiedervorlage]
+		 , T3.[Begründung]
+
+FROM [dbo].[Forderungen] T1
+INNER JOIN [dbo].[Benutzer_Rechte] T2 ON (T2.Benutzer_ID = '{{ BenutzerSelect.selectedOptionValue }}' OR T2.Benutzer_ID = '')
+AND T1.Hauptbetrieb_ID = T2.Hauptbetrieb_ID AND T1.Standort_ID = T2.Standort_ID AND (T2.Rolle = 'Buchhaltung' OR T2.Benutzer_ID = T1.Verursacher_Benutzer_ID)
+LEFT JOIN [dbo].[Forderungen_Kommentar_letzte] T3 ON T1.Client_DB = T3.Client_DB AND T1.Document_No = T3.Document_No
+WHERE 1 = 1
+AND [Invoice_Date] >= {{ RechnungsdatumVon.selectedDate }}
+AND [Invoice_Date] <= {{ RechnungsdatumBis.selectedDate }}
+AND T1.[Client_DB] LIKE '%' + {{ Hauptbetrieb.selectedOptionValue }}
+AND T1.[Standort_ID] LIKE '%' + {{ Standort.selectedOptionValue }}
+AND T1.[Document_No] LIKE '%' + {{ Rechnungsnummer.text }} + '%'
+AND [Kunde] LIKE '%' + {{ Kunde.selectedOptionValue }}
+AND [Verursacher] LIKE '%' + {{ Verursacher.selectedOptionValue }}
+AND [VIN] LIKE '%' + {{ Fahrzeug.selectedOptionValue }}
+AND [Staffel] LIKE '%' + {{ Staffel.selectedOptionValue }}
+AND [Mahnstufe] LIKE '%' + {{ Mahnstufe.selectedOptionValue }}
+AND (T3.Wiedervorlage IS NULL OR (
+	T3.Wiedervorlage >= {{ WiedervorlageVon.selectedDate }}
+	AND T3.Wiedervorlage <= {{ WiedervorlageBis.selectedDate }}
+))
+ORDER BY [Tage] DESC, T3.[Wiedervorlage] ASC

+ 9 - 0
templates/forderungen/queries/insert_kommentar.sql

@@ -0,0 +1,9 @@
+INSERT INTO dbo.Forderungen_Kommentar (Client_DB, Document_No, Timestamp, Benutzer_ID, Begründung, Wiedervorlage, Kommentar) VALUES (
+	'{{frm_Kommentar.data.txt_Client_DB}}',
+	'{{frm_Kommentar.data.txt_Document_No}}',
+  getdate(),
+	'{{appsmith.URL.queryParams.Benutzer_ID}}',
+	'{{frm_Kommentar.data.Select1.selectedOptionValue}}',
+	'{{frm_Kommentar.data.DatePicker1}}',
+  '{{frm_Kommentar.data.txt_Kommentar}}'
+	)