mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
Printing a multi-plate file in different quantities per plate meant queueing each plate separately and tracking the counts by hand: one shared Quantity field cannot say "plate 1 once, plate 2 twice, plate 3 three times". Each selected plate now carries its own quantity, and the submission becomes an order on a new Batches tab. The point is the distinction the old flat batch could not express. print_batch_plates stores how many runs of each plate were wanted, separately from what was queued, so a run that fails, is cancelled or is skipped does not satisfy a target -- the order goes on saying it owes a print instead of quietly under-delivering. Queue remaining re-queues exactly what is missing, for the whole order or one plate, by cloning the most recent item for that plate: that inherits the printer or model target, AMS mapping, filament overrides and print options along with the validation they already passed, rather than re-serialising twenty fields through a template that would drift from the model the first time someone adds a column. Clones append to the end of the relevant printer's queue and take the same advisory lock the add-to-queue route does; positions are per-printer sequences, not global. Cost is measured, not estimated. print_log_entries gains queue_item_id, set where the queue item is already in scope, so each run's material and energy are attributed through the item that produced them -- an unrelated reprint of the same archive never lands in an order's total, and a multi-plate order gets each plate's own cost rather than the whole file's via the plate-scoped estimate from #2614. Before any run has completed there is no honest figure, so cost reads as unknown instead of a fabricated 0.00. The Batches tab wires up GET /queue/batches, which has been unreferenced since the batch MVP shipped, along with six locale keys that were translated and never used. It is a separate tab because an order outlives the queue that produced it: once its runs finish they leave the active queue, so Queue and History each hold half the picture. completed was not a reachable status before now, so every batch created since April is still marked active however long ago its last print finished -- 73 of them on the development install. A startup pass closes out the finished ones: those whose runs all completed become completed, and groupings whose items were all cancelled become cancelled, which is what they are. Not applied to orders, which state their intent independently of their runs and still owe the work. Only batches with nothing queued or printing are considered, and repeating the pass also catches an order whose last run landed while the process was down. Batches with neither items nor targets are no longer listed at all -- empty shells left when a grouping's items went with their source archive. Dispatch applies the same source-file gates as POST /queue/. It creates queue items, so without them it would be a weaker door to the same outcome; the archive and library-file checks move into shared helpers so a third route cannot drift from them.
82 lines
2.7 KiB
Python
82 lines
2.7 KiB
Python
"""Service for writing independent print log entries.
|
|
|
|
Log entries are written to a separate table and never touch archives or queue items.
|
|
"""
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from backend.app.models.print_log import PrintLogEntry
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def write_log_entry(
|
|
db: AsyncSession,
|
|
*,
|
|
status: str,
|
|
archive_id: int | None = None,
|
|
queue_item_id: int | None = None,
|
|
print_name: str | None = None,
|
|
printer_name: str | None = None,
|
|
printer_id: int | None = None,
|
|
started_at: datetime | None = None,
|
|
completed_at: datetime | None = None,
|
|
filament_type: str | None = None,
|
|
filament_color: str | None = None,
|
|
filament_used_grams: float | None = None,
|
|
cost: float | None = None,
|
|
energy_kwh: float | None = None,
|
|
energy_cost: float | None = None,
|
|
failure_reason: str | None = None,
|
|
thumbnail_path: str | None = None,
|
|
created_by_id: int | None = None,
|
|
created_by_username: str | None = None,
|
|
reconciled: bool = False,
|
|
) -> PrintLogEntry:
|
|
"""Write a print log entry.
|
|
|
|
``reconciled`` marks a synthetic completion written when a stale
|
|
``status="printing"`` archive is closed out at reconnect. Its real end time
|
|
is unknown — the print stopped somewhere during the disconnect and
|
|
``completed_at`` is only the reconnect moment — so ``completed_at -
|
|
started_at`` would bank the entire disconnect gap as print time, adding
|
|
hundreds of fictitious hours across a farm of stale rows (#2592). For those
|
|
entries we store an explicit ``0`` ("no measured runtime") rather than a
|
|
fabricated duration; the stats total trusts a stored 0 instead of
|
|
recomputing from the stale timestamps.
|
|
"""
|
|
if reconciled:
|
|
duration: int | None = 0
|
|
elif started_at and completed_at:
|
|
duration = int((completed_at - started_at).total_seconds())
|
|
else:
|
|
duration = None
|
|
|
|
entry = PrintLogEntry(
|
|
archive_id=archive_id,
|
|
queue_item_id=queue_item_id,
|
|
print_name=print_name,
|
|
printer_name=printer_name,
|
|
printer_id=printer_id,
|
|
status=status,
|
|
started_at=started_at,
|
|
completed_at=completed_at,
|
|
duration_seconds=duration,
|
|
filament_type=filament_type,
|
|
filament_color=filament_color,
|
|
filament_used_grams=filament_used_grams,
|
|
cost=cost,
|
|
energy_kwh=energy_kwh,
|
|
energy_cost=energy_cost,
|
|
failure_reason=failure_reason,
|
|
thumbnail_path=thumbnail_path,
|
|
created_by_id=created_by_id,
|
|
created_by_username=created_by_username,
|
|
)
|
|
db.add(entry)
|
|
await db.flush()
|
|
return entry
|