mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 11:56:01 +02:00
Print costs covered filament and energy but not the printer. Each printer can now carry an optional wear cost per printing hour. Every run's wear is its logged duration at that rate, stored on the print log entry; the archive keeps the first run's wear, like energy (#1378). Failed and cancelled runs count, reconciled runs (unknown duration) don't, and a rate applies to new prints only. Wear is shown on the archive card, as a hidden sortable Print Log column, as a Quick Stats tile, in the most expensive record, in project totals and in batch order cost, and is kept by GitHub backup and restore. Billing still charges filament only. New nullable columns: printers.wear_cost_per_hour, print_log_entries.wear_cost and print_archives.wear_cost.
58 lines
3.0 KiB
Python
58 lines
3.0 KiB
Python
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, Float, ForeignKey, Integer, String, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from backend.app.core.database import Base
|
|
|
|
|
|
class PrintLogEntry(Base):
|
|
"""Independent print log entry. Written when print events occur.
|
|
|
|
This is a separate table from archives/queue — clearing the log
|
|
never touches archives or queue items.
|
|
|
|
archive_id is a nullable FK so log entries survive archive deletion (ON
|
|
DELETE SET NULL). Aggregating runs per archive — for the per-archive
|
|
"Print Log" view and for statistics that should not double-count
|
|
overwritten archives (#1378) — is done via WHERE archive_id = X.
|
|
"""
|
|
|
|
__tablename__ = "print_log_entries"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
archive_id: Mapped[int | None] = mapped_column(
|
|
ForeignKey("print_archives.id", ondelete="SET NULL"), nullable=True, index=True
|
|
)
|
|
# Which queue item produced this run, when one did. Printer-initiated
|
|
# prints have none. Batch cost/energy roll-up joins on this (#342): the
|
|
# archive alone can't attribute a run to an order because several orders
|
|
# — and plain reprints — share one archive.
|
|
queue_item_id: Mapped[int | None] = mapped_column(
|
|
ForeignKey("print_queue.id", ondelete="SET NULL"), nullable=True, index=True
|
|
)
|
|
print_name: Mapped[str | None] = mapped_column(String(255))
|
|
printer_name: Mapped[str | None] = mapped_column(String(255))
|
|
printer_id: Mapped[int | None] = mapped_column(Integer)
|
|
status: Mapped[str] = mapped_column(String(20)) # completed, failed, stopped, cancelled, skipped
|
|
started_at: Mapped[datetime | None] = mapped_column(DateTime)
|
|
completed_at: Mapped[datetime | None] = mapped_column(DateTime)
|
|
duration_seconds: Mapped[int | None] = mapped_column(Integer)
|
|
filament_type: Mapped[str | None] = mapped_column(String(50))
|
|
filament_color: Mapped[str | None] = mapped_column(String(50))
|
|
filament_used_grams: Mapped[float | None] = mapped_column(Float)
|
|
cost: Mapped[float | None] = mapped_column(Float)
|
|
energy_kwh: Mapped[float | None] = mapped_column(Float)
|
|
energy_cost: Mapped[float | None] = mapped_column(Float)
|
|
# Printer wear for this run: duration at the printer's hourly wear cost (#694).
|
|
wear_cost: Mapped[float | None] = mapped_column(Float)
|
|
failure_reason: Mapped[str | None] = mapped_column(String(100))
|
|
# User quality verdict ('good' / 'reject'), mirrored from the archive on
|
|
# PATCH exactly like status/failure_reason (#1444 mirror) so verdict-aware
|
|
# statistics can stay on this table (#1898).
|
|
user_verdict: Mapped[str | None] = mapped_column(String(10), nullable=True)
|
|
thumbnail_path: Mapped[str | None] = mapped_column(String(500))
|
|
created_by_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
|
|
created_by_username: Mapped[str | None] = mapped_column(String(100))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|