Add start, stop, and finish job actions
- POST /jobs/{id}/start: modal picks from idle/offline printers,
sets job → printing, printer → printing, logs "Job started on {name}"
- POST /jobs/{id}/stop: job → queued, clears printer_id + started_at,
printer → idle, logs "Job stopped (was on {name})"
- POST /jobs/{id}/finish: job → done, sets finished_at + duration_minutes,
printer → idle, logs "Job finished on {name}"
- Job detail shows Start / Stop+Finish buttons based on current status
- All printers currently printing are excluded from the Start printer picker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -260,6 +260,9 @@ def job_detail(job_id: int, request: Request, db: Session = Depends(get_db)):
|
|||||||
"materials": available_materials() or MATERIALS,
|
"materials": available_materials() or MATERIALS,
|
||||||
"colors": available_colors() or COLORS,
|
"colors": available_colors() or COLORS,
|
||||||
"all_statuses": [s.value for s in JobStatus],
|
"all_statuses": [s.value for s in JobStatus],
|
||||||
|
"available_printers": db.query(Printer).filter(
|
||||||
|
Printer.status != PrinterStatus.printing
|
||||||
|
).order_by(Printer.name).all(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -294,6 +297,68 @@ def edit_job(
|
|||||||
return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)
|
return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/jobs/{job_id}/start")
|
||||||
|
def start_job(job_id: int, printer_id: int = Form(...), db: Session = Depends(get_db)):
|
||||||
|
from fastapi import HTTPException
|
||||||
|
job = db.query(PrintJob).filter(PrintJob.id == job_id).first()
|
||||||
|
if not job:
|
||||||
|
raise HTTPException(404, "Job not found")
|
||||||
|
printer = db.query(Printer).filter(Printer.id == printer_id).first()
|
||||||
|
if not printer:
|
||||||
|
raise HTTPException(404, "Printer not found")
|
||||||
|
|
||||||
|
job.status = JobStatus.printing
|
||||||
|
job.printer_id = printer_id
|
||||||
|
job.started_at = datetime.now()
|
||||||
|
printer.status = PrinterStatus.printing
|
||||||
|
|
||||||
|
db.add(JobLog(job_id=job_id, message=f"Job started on {printer.name}"))
|
||||||
|
db.commit()
|
||||||
|
return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/jobs/{job_id}/stop")
|
||||||
|
def stop_job(job_id: int, db: Session = Depends(get_db)):
|
||||||
|
from fastapi import HTTPException
|
||||||
|
job = db.query(PrintJob).filter(PrintJob.id == job_id).first()
|
||||||
|
if not job:
|
||||||
|
raise HTTPException(404, "Job not found")
|
||||||
|
|
||||||
|
printer_name = job.printer.name if job.printer else "unknown printer"
|
||||||
|
if job.printer:
|
||||||
|
job.printer.status = PrinterStatus.idle
|
||||||
|
|
||||||
|
job.status = JobStatus.queued
|
||||||
|
job.printer_id = None
|
||||||
|
job.started_at = None
|
||||||
|
|
||||||
|
db.add(JobLog(job_id=job_id, message=f"Job stopped (was on {printer_name})"))
|
||||||
|
db.commit()
|
||||||
|
return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/jobs/{job_id}/finish")
|
||||||
|
def finish_job(job_id: int, db: Session = Depends(get_db)):
|
||||||
|
from fastapi import HTTPException
|
||||||
|
job = db.query(PrintJob).filter(PrintJob.id == job_id).first()
|
||||||
|
if not job:
|
||||||
|
raise HTTPException(404, "Job not found")
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
job.status = JobStatus.done
|
||||||
|
job.finished_at = now
|
||||||
|
if job.started_at:
|
||||||
|
job.duration_minutes = max(1, int((now - job.started_at).total_seconds() / 60))
|
||||||
|
|
||||||
|
printer_name = job.printer.name if job.printer else "unknown printer"
|
||||||
|
if job.printer:
|
||||||
|
job.printer.status = PrinterStatus.idle
|
||||||
|
|
||||||
|
db.add(JobLog(job_id=job_id, message=f"Job finished on {printer_name}"))
|
||||||
|
db.commit()
|
||||||
|
return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/jobs/{job_id}/analyse")
|
@router.post("/jobs/{job_id}/analyse")
|
||||||
def analyse_job_file(job_id: int, db: Session = Depends(get_db)):
|
def analyse_job_file(job_id: int, db: Session = Depends(get_db)):
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|||||||
@@ -25,6 +25,29 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# ── Job actions ── #}
|
||||||
|
{% set s = job.status.value %}
|
||||||
|
{% if s == 'queued' %}
|
||||||
|
<div class="mb-4">
|
||||||
|
<button class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#startModal">
|
||||||
|
<i class="bi bi-play-fill me-1"></i>Start Job
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% elif s == 'printing' %}
|
||||||
|
<div class="d-flex gap-2 mb-4">
|
||||||
|
<form method="POST" action="/jobs/{{ job.id }}/finish">
|
||||||
|
<button type="submit" class="btn btn-success btn-sm">
|
||||||
|
<i class="bi bi-check-lg me-1"></i>Finish
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form method="POST" action="/jobs/{{ job.id }}/stop">
|
||||||
|
<button type="submit" class="btn btn-outline-warning btn-sm">
|
||||||
|
<i class="bi bi-stop-fill me-1"></i>Stop
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
|
|
||||||
{# ── Job info ── #}
|
{# ── Job info ── #}
|
||||||
@@ -312,4 +335,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# ── Start Job Modal ── #}
|
||||||
|
{% if job.status.value == 'queued' %}
|
||||||
|
<div class="modal fade" id="startModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content bg-dark border-secondary">
|
||||||
|
<form method="POST" action="/jobs/{{ job.id }}/start">
|
||||||
|
<div class="modal-header border-secondary">
|
||||||
|
<h6 class="modal-title text-white">Start Job on Printer</h6>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% if available_printers %}
|
||||||
|
<label class="form-label text-secondary small">Select printer <span class="text-danger">*</span></label>
|
||||||
|
<select name="printer_id" class="form-select bg-dark border-secondary text-white" required>
|
||||||
|
<option value="">— select —</option>
|
||||||
|
{% for p in available_printers %}
|
||||||
|
<option value="{{ p.id }}">{{ p.name }}{% if p.location %} · {{ p.location }}{% endif %} ({{ p.status.value }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-warning mb-0">
|
||||||
|
<i class="bi bi-exclamation-triangle me-1"></i>All printers are currently busy.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-secondary">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-success btn-sm" {% if not available_printers %}disabled{% endif %}>
|
||||||
|
<i class="bi bi-play-fill me-1"></i>Start
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user