Show spool availability indicator per job on jobs list

- filamentdb.spool_counts(): builds (material, color_name) → roll count
  from cached stock report; counts rolls_in_stock + rolls_in_use (non-graveyard)
- Jobs list: coloured dot before material — green (2+), yellow (1), red (0)
- FILAMENTDB_URL added to .env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Hohenberg
2026-06-18 22:22:32 +02:00
parent bc1769625e
commit 392acef28e
3 changed files with 27 additions and 2 deletions

View File

@@ -50,6 +50,20 @@ def available_materials() -> list[str]:
return sorted(out) return sorted(out)
def spool_counts() -> dict[tuple[str, str], int]:
"""Returns {(material, color_name): available_rolls} from cached stock.
'Available' = rolls_in_stock + rolls_in_use (anything not in the graveyard).
Multiple filament types with the same material+color are summed.
"""
result: dict[tuple[str, str], int] = {}
for r in get_stock():
key = (r.get("material") or "", r.get("color_name") or "")
count = (r.get("rolls_in_stock") or 0) + (r.get("rolls_in_use") or 0)
result[key] = result.get(key, 0) + count
return result
def available_colors(material: str | None = None) -> list[str]: def available_colors(material: str | None = None) -> list[str]:
"""Unique color names in stock, optionally filtered by material.""" """Unique color names in stock, optionally filtered by material."""
rows = get_stock() rows = get_stock()

View File

@@ -20,7 +20,7 @@ from app.models.printer_type import PrinterType
from app.models.pricing import PricingConfig from app.models.pricing import PricingConfig
from app.models.todo import Todo from app.models.todo import Todo
from app.constants import COLORS, MATERIALS from app.constants import COLORS, MATERIALS
from app.filamentdb import available_colors, available_materials, get_stock from app.filamentdb import available_colors, available_materials, get_stock, spool_counts
from app.stl import get_stl_dir, list_stl_files from app.stl import get_stl_dir, list_stl_files
from app.stl_analysis import analyse, get_cache_map # noqa: F401 used in job_detail route from app.stl_analysis import analyse, get_cache_map # noqa: F401 used in job_detail route
@@ -145,6 +145,7 @@ def jobs_page(request: Request, q: str = "", db: Session = Depends(get_db)):
"stl_cache": get_cache_map(stl_files, db), "stl_cache": get_cache_map(stl_files, db),
"materials": available_materials() or MATERIALS, "materials": available_materials() or MATERIALS,
"colors": available_colors() or COLORS, "colors": available_colors() or COLORS,
"spool_map": spool_counts(),
}) })

View File

@@ -53,7 +53,17 @@
<td><a href="/jobs/{{ j.id }}" class="text-secondary text-decoration-none">{{ j.file_name or j.name or '—' }}</a></td> <td><a href="/jobs/{{ j.id }}" class="text-secondary text-decoration-none">{{ j.file_name or j.name or '—' }}</a></td>
<td class="text-secondary">{{ j.printer.name if j.printer else '—' }}</td> <td class="text-secondary">{{ j.printer.name if j.printer else '—' }}</td>
<td class="text-secondary"> <td class="text-secondary">
{% if j.job_material %}{{ j.job_material }}{% if j.job_color %} · {{ j.job_color }}{% endif %}{% else %}—{% endif %} {% if j.job_material %}
{% set count = spool_map.get((j.job_material, j.job_color or ''), 0) %}
{% if count >= 2 %}
<span class="text-success me-1" title="{{ count }} rolls available"></span>
{% elif count == 1 %}
<span class="text-warning me-1" title="1 roll available"></span>
{% else %}
<span class="text-danger me-1" title="No rolls in stock"></span>
{% endif %}
{{ j.job_material }}{% if j.job_color %} · {{ j.job_color }}{% endif %}
{% else %}—{% endif %}
</td> </td>
<td> <td>
{% set s = j.status.value %} {% set s = j.status.value %}