feat(F-G-H-I): STL cache, invoices, import validation, notification settings
Phase F — STL Hash Cache:
- Migration 041: step_file_hash column on cad_files
- cache_service.py: SHA256 hash + MinIO-backed STL cache (check/store)
- render_step_thumbnail: compute+persist hash before render
- generate_stl_cache: check MinIO cache before cadquery conversion, store after
Phase G — Invoices:
- Migration 042: invoices + invoice_lines tables with RLS
- Invoice/InvoiceLine models + schemas
- billing service: generate_invoice_number (INV-YYYY-NNNN), create/list/get/delete/PDF
- WeasyPrint PDF generation; backend Dockerfile + pyproject.toml deps
- invoice_router with 6 endpoints; registered in main.py
- frontend: Billing.tsx page + api/billing.ts; route + nav link
Phase H — Import Sanity Check:
- Migration 043: import_validations table
- ImportValidation model + schemas
- run_sanity_check: material fuzzy-match (cutoff=0.8), STEP availability, duplicate detection
- validate_excel_import Celery task (queue: step_processing)
- uploads.py: create ImportValidation on /excel, fire task, expose GET /validations/{id}
- frontend: Upload.tsx polling ValidationDialog with Ampel status indicators
Phase I — Notification Settings:
- Migration 044: notification_configs table (user×event×channel toggles)
- NotificationConfig model + seeds (in_app=true, email=false)
- get/upsert/reset config endpoints on /notifications/config
- frontend: NotificationSettings.tsx page + api/notifications.ts extensions
Infrastructure:
- docker-compose.yml: add worker-thumbnail service (concurrency=1, Q=thumbnail_rendering)
- Fix Dockerfile: libgdk-pixbuf-2.0-0 (correct Debian bookworm package name)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,28 @@ def render_step_thumbnail(self, cad_file_id: str):
|
||||
On success, also auto-populates materials and marks the CadFile as completed.
|
||||
"""
|
||||
logger.info(f"Rendering thumbnail for CAD file: {cad_file_id}")
|
||||
|
||||
# Compute and persist STEP file hash for STL cache lookups
|
||||
try:
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
from app.config import settings as app_settings
|
||||
from app.models.cad_file import CadFile
|
||||
from app.domains.products.cache_service import compute_step_hash
|
||||
|
||||
sync_url = app_settings.database_url.replace("+asyncpg", "")
|
||||
_eng = create_engine(sync_url)
|
||||
with Session(_eng) as _sess:
|
||||
_cad = _sess.get(CadFile, cad_file_id)
|
||||
if _cad and _cad.stored_path and not _cad.step_file_hash:
|
||||
_hash = compute_step_hash(_cad.stored_path)
|
||||
_cad.step_file_hash = _hash
|
||||
_sess.commit()
|
||||
logger.info(f"Saved step_file_hash for {cad_file_id}: {_hash[:12]}…")
|
||||
_eng.dispose()
|
||||
except Exception:
|
||||
logger.warning(f"step_file_hash computation failed for {cad_file_id} (non-fatal)")
|
||||
|
||||
try:
|
||||
from app.services.step_processor import regenerate_cad_thumbnail
|
||||
success = regenerate_cad_thumbnail(cad_file_id, part_colors={})
|
||||
@@ -172,13 +194,24 @@ def generate_stl_cache(self, cad_file_id: str, quality: str):
|
||||
|
||||
try:
|
||||
from app.services.render_blender import convert_step_to_stl, export_per_part_stls
|
||||
from app.domains.products.cache_service import compute_step_hash, check_stl_cache, store_stl_cache
|
||||
from pathlib import Path as _Path
|
||||
step = _Path(step_path)
|
||||
stl_out = step.parent / f"{step.stem}_{quality}.stl"
|
||||
parts_dir = step.parent / f"{step.stem}_{quality}_parts"
|
||||
|
||||
if not stl_out.exists() or stl_out.stat().st_size == 0:
|
||||
convert_step_to_stl(step, stl_out, quality)
|
||||
# Check MinIO cache before running cadquery conversion
|
||||
step_hash = compute_step_hash(step_path)
|
||||
cached_bytes = check_stl_cache(step_hash, quality)
|
||||
if cached_bytes:
|
||||
stl_out.write_bytes(cached_bytes)
|
||||
logger.info(f"STL cache hit for {cad_file_id} ({quality}), skipped conversion")
|
||||
else:
|
||||
convert_step_to_stl(step, stl_out, quality)
|
||||
# Store result in MinIO for future workers
|
||||
if stl_out.exists() and stl_out.stat().st_size > 0:
|
||||
store_stl_cache(step_hash, quality, str(stl_out))
|
||||
if not (parts_dir / "manifest.json").exists():
|
||||
try:
|
||||
export_per_part_stls(step, parts_dir, quality)
|
||||
|
||||
Reference in New Issue
Block a user