Remove internal filament management, integrate FilamentDB, add job detail page

Filament removal:
- Deleted app/models/filament.py, routers/filaments.py, templates/filaments.html
- Migration 0009: drops filaments table and filament_id FK from print_jobs
- Removed Filaments from sidebar and all internal references

FilamentDB integration (read-only):
- app/filamentdb.py: GET /reports/stock with 5-min in-memory cache
- available_materials() / available_colors() populate job form dropdowns
- Falls back to static constants if FilamentDB unreachable
- Dashboard filament count now from FilamentDB rolls_in_stock
- FILAMENTDB_URL added to .env.install; httpx added to requirements

Job detail page (/jobs/{id}):
- Two cards: Job info (customer, material, printer, bracket, timing, cost)
  and File & Geometry (bounding box + volume from stl_cache)
- Log timeline with Add Entry modal
- Filename in jobs list links to detail page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Hohenberg
2026-06-18 22:12:47 +02:00
parent 6868862d77
commit 1259a7294a
14 changed files with 294 additions and 138 deletions

View File

@@ -1,5 +1,4 @@
from app.models.base import Base
from app.models.filament import Filament
from app.models.job_bracket import JobBracket
from app.models.job_log import JobLog
from app.models.pricing import PricingConfig
@@ -10,4 +9,4 @@ from app.models.printer_type import PrinterType
from app.models.stl_cache import StlCache
from app.models.todo import Todo
__all__ = ["Base", "Printer", "PrinterLog", "PrinterType", "Filament", "JobBracket", "JobLog", "PrintJob", "StlCache", "Todo", "PricingConfig"]
__all__ = ["Base", "Printer", "PrinterLog", "PrinterType", "JobBracket", "JobLog", "PrintJob", "StlCache", "Todo", "PricingConfig"]

View File

@@ -1,38 +0,0 @@
import enum
from datetime import datetime
from decimal import Decimal
from sqlalchemy import DateTime, Enum, Numeric, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import Base
class Material(enum.Enum):
PLA = "PLA"
PETG = "PETG"
ABS = "ABS"
ASA = "ASA"
TPU = "TPU"
Nylon = "Nylon"
PC = "PC"
Other = "Other"
class Filament(Base):
__tablename__ = "filaments"
id: Mapped[int] = mapped_column(primary_key=True)
brand: Mapped[str | None] = mapped_column(String(100))
material: Mapped[Material] = mapped_column(Enum(Material), default=Material.PLA)
color: Mapped[str | None] = mapped_column(String(100))
color_hex: Mapped[str | None] = mapped_column(String(7))
weight_total_g: Mapped[Decimal] = mapped_column(Numeric(8, 2))
weight_remaining_g: Mapped[Decimal] = mapped_column(Numeric(8, 2))
price_per_kg_eur: Mapped[Decimal | None] = mapped_column(Numeric(8, 2))
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), onupdate=func.now()
)
jobs: Mapped[list["PrintJob"]] = relationship(back_populates="filament")

View File

@@ -3,7 +3,7 @@ import uuid as _uuid
from datetime import datetime
from decimal import Decimal
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, Numeric, String, Text, func
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import Base
@@ -23,7 +23,6 @@ class PrintJob(Base):
id: Mapped[int] = mapped_column(primary_key=True)
job_uuid: Mapped[str | None] = mapped_column(String(36), unique=True, default=lambda: str(_uuid.uuid4()))
printer_id: Mapped[int | None] = mapped_column(ForeignKey("printers.id"))
filament_id: Mapped[int | None] = mapped_column(ForeignKey("filaments.id"))
bracket_id: Mapped[int | None] = mapped_column(ForeignKey("job_brackets.id"))
name: Mapped[str] = mapped_column(String(200))
customer: Mapped[str | None] = mapped_column(String(200))
@@ -44,7 +43,6 @@ class PrintJob(Base):
)
printer: Mapped["Printer"] = relationship(back_populates="jobs")
filament: Mapped["Filament | None"] = relationship(back_populates="jobs")
bracket: Mapped["JobBracket | None"] = relationship(back_populates="jobs")
logs: Mapped[list["JobLog"]] = relationship(
back_populates="job", order_by="JobLog.created_at.desc()"