feat: CSV-Import ueber bestehende Pipeline mit dreiwertiger Saldo-Pruefung
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ from app.auth import require_auth
|
||||
from app.config import get_settings
|
||||
from app.db import get_session
|
||||
from app.models.tables import Statement, Transaction
|
||||
from app.services.importer import process_pdf
|
||||
from app.services.importer import process_file
|
||||
|
||||
router = APIRouter(prefix="/api/imports", tags=["imports"],
|
||||
dependencies=[Depends(require_auth)])
|
||||
@@ -50,22 +50,22 @@ class TransactionOut(BaseModel):
|
||||
class PreviewOut(BaseModel):
|
||||
statement: StatementOut
|
||||
transactions: list[TransactionOut]
|
||||
balance_ok: bool
|
||||
balance_ok: bool | None
|
||||
duplicates: int
|
||||
|
||||
|
||||
@router.post("/upload", response_model=StatementOut, status_code=201)
|
||||
def upload(file: UploadFile, session: Session = Depends(get_session)):
|
||||
safe_name = Path(file.filename or "upload.pdf").name
|
||||
if not safe_name or not safe_name.lower().endswith(".pdf"):
|
||||
raise HTTPException(400, "Nur PDF-Dateien")
|
||||
if not safe_name or not safe_name.lower().endswith((".pdf", ".csv")):
|
||||
raise HTTPException(400, "Nur PDF- oder CSV-Dateien")
|
||||
settings = get_settings()
|
||||
inbox_dir = settings.inbox_dir
|
||||
inbox_dir.mkdir(parents=True, exist_ok=True)
|
||||
dest = inbox_dir / safe_name
|
||||
with dest.open("wb") as f:
|
||||
f.write(file.file.read())
|
||||
stmt = process_pdf(session, dest)
|
||||
stmt = process_file(session, dest)
|
||||
return StatementOut.model_validate(stmt)
|
||||
|
||||
|
||||
@@ -74,9 +74,10 @@ def scan_inbox(session: Session = Depends(get_session)):
|
||||
settings = get_settings()
|
||||
inbox_dir = settings.inbox_dir
|
||||
inbox_dir.mkdir(parents=True, exist_ok=True)
|
||||
paths = sorted(inbox_dir.glob("*.pdf")) + sorted(inbox_dir.glob("*.csv"))
|
||||
results = []
|
||||
for path in sorted(inbox_dir.glob("*.pdf")):
|
||||
stmt = process_pdf(session, path)
|
||||
for path in paths:
|
||||
stmt = process_file(session, path)
|
||||
results.append(StatementOut.model_validate(stmt))
|
||||
return results
|
||||
|
||||
@@ -96,11 +97,17 @@ def preview(statement_id: int, session: Session = Depends(get_session)):
|
||||
select(Transaction).where(Transaction.statement_id == statement_id)
|
||||
.order_by(Transaction.booking_date, Transaction.id)
|
||||
).scalars().all()
|
||||
balance_ok = (
|
||||
stmt.opening_balance is not None and stmt.closing_balance is not None
|
||||
and (stmt.opening_balance + sum((t.amount for t in txs), Decimal("0"))
|
||||
- stmt.closing_balance) == 0
|
||||
)
|
||||
# Dreiwertig (Ausbaustufe 3 Task 3): manche CSV-Formate (z.B. HVB, DKB)
|
||||
# liefern keine Saldodaten - None statt False, damit die Vorschau nicht
|
||||
# faelschlich einen Saldo-Fehler anzeigt, wo schlicht keine Pruefung
|
||||
# moeglich ist.
|
||||
if stmt.opening_balance is None or stmt.closing_balance is None:
|
||||
balance_ok = None
|
||||
else:
|
||||
balance_ok = (
|
||||
stmt.opening_balance + sum((t.amount for t in txs), Decimal("0"))
|
||||
- stmt.closing_balance
|
||||
) == 0
|
||||
duplicates = sum(1 for t in txs if t.is_duplicate)
|
||||
return PreviewOut(
|
||||
statement=StatementOut.model_validate(stmt),
|
||||
|
||||
Reference in New Issue
Block a user