Replace combined filament dropdown with separate material + color dropdowns
- app/constants.py: MATERIALS and COLORS standard value lists
- Migration 0008: job_material + job_color columns on print_jobs
- Job creation (both /jobs and /brackets/{id}/jobs) stores these independently
of the filament inventory
- All job tables updated to display job_material · job_color
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
26
alembic/versions/0008_job_material_color.py
Normal file
26
alembic/versions/0008_job_material_color.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""add job_material and job_color to print_jobs
|
||||||
|
|
||||||
|
Revision ID: 0008
|
||||||
|
Revises: 0007
|
||||||
|
Create Date: 2026-06-18
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "0008"
|
||||||
|
down_revision: Union[str, None] = "0007"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.add_column("print_jobs", sa.Column("job_material", sa.String(50), nullable=True))
|
||||||
|
op.add_column("print_jobs", sa.Column("job_color", sa.String(100), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_column("print_jobs", "job_color")
|
||||||
|
op.drop_column("print_jobs", "job_material")
|
||||||
9
app/constants.py
Normal file
9
app/constants.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
MATERIALS = ["PLA", "PETG", "ABS", "ASA", "TPU", "Nylon", "PC", "Other"]
|
||||||
|
|
||||||
|
COLORS = [
|
||||||
|
"Black", "White", "Grey", "Silver",
|
||||||
|
"Red", "Orange", "Yellow",
|
||||||
|
"Green", "Blue", "Purple", "Pink",
|
||||||
|
"Brown", "Beige", "Natural",
|
||||||
|
"Transparent", "Translucent",
|
||||||
|
]
|
||||||
@@ -28,6 +28,8 @@ class PrintJob(Base):
|
|||||||
name: Mapped[str] = mapped_column(String(200))
|
name: Mapped[str] = mapped_column(String(200))
|
||||||
customer: Mapped[str | None] = mapped_column(String(200))
|
customer: Mapped[str | None] = mapped_column(String(200))
|
||||||
file_name: Mapped[str | None] = mapped_column(String(255))
|
file_name: Mapped[str | None] = mapped_column(String(255))
|
||||||
|
job_material: Mapped[str | None] = mapped_column(String(50))
|
||||||
|
job_color: Mapped[str | None] = mapped_column(String(100))
|
||||||
status: Mapped[JobStatus] = mapped_column(Enum(JobStatus), default=JobStatus.queued)
|
status: Mapped[JobStatus] = mapped_column(Enum(JobStatus), default=JobStatus.queued)
|
||||||
queue_position: Mapped[int | None] = mapped_column(Integer)
|
queue_position: Mapped[int | None] = mapped_column(Integer)
|
||||||
started_at: Mapped[datetime | None] = mapped_column(DateTime)
|
started_at: Mapped[datetime | None] = mapped_column(DateTime)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from app.models.printer_log import PrinterLog
|
|||||||
from app.models.printer_type import PrinterType
|
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.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
|
from app.stl_analysis import analyse, get_cache_map
|
||||||
|
|
||||||
@@ -140,6 +141,8 @@ def jobs_page(request: Request, q: str = "", db: Session = Depends(get_db)):
|
|||||||
"brackets": db.query(JobBracket).order_by(JobBracket.name).all(),
|
"brackets": db.query(JobBracket).order_by(JobBracket.name).all(),
|
||||||
"stl_files": (stl_files := list_stl_files()),
|
"stl_files": (stl_files := list_stl_files()),
|
||||||
"stl_cache": get_cache_map(stl_files, db),
|
"stl_cache": get_cache_map(stl_files, db),
|
||||||
|
"materials": MATERIALS,
|
||||||
|
"colors": COLORS,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -147,7 +150,8 @@ def jobs_page(request: Request, q: str = "", db: Session = Depends(get_db)):
|
|||||||
async def create_job(
|
async def create_job(
|
||||||
printer_id: str = Form(""),
|
printer_id: str = Form(""),
|
||||||
customer: str = Form(""),
|
customer: str = Form(""),
|
||||||
filament_id: str = Form(""),
|
job_material: str = Form(""),
|
||||||
|
job_color: str = Form(""),
|
||||||
bracket_id: str = Form(""),
|
bracket_id: str = Form(""),
|
||||||
amount: int = Form(1),
|
amount: int = Form(1),
|
||||||
notes: str = Form(""),
|
notes: str = Form(""),
|
||||||
@@ -184,9 +188,10 @@ async def create_job(
|
|||||||
name=job_name,
|
name=job_name,
|
||||||
file_name=filename,
|
file_name=filename,
|
||||||
printer_id=int(printer_id) if printer_id else None,
|
printer_id=int(printer_id) if printer_id else None,
|
||||||
filament_id=int(filament_id) if filament_id else None,
|
|
||||||
bracket_id=effective_bracket_id,
|
bracket_id=effective_bracket_id,
|
||||||
customer=customer_str,
|
customer=customer_str,
|
||||||
|
job_material=job_material or None,
|
||||||
|
job_color=job_color or None,
|
||||||
notes=notes.strip() or None,
|
notes=notes.strip() or None,
|
||||||
)
|
)
|
||||||
db.add(job)
|
db.add(job)
|
||||||
@@ -236,6 +241,8 @@ def bracket_detail(bracket_id: int, request: Request, db: Session = Depends(get_
|
|||||||
"filaments": db.query(Filament).order_by(Filament.material, Filament.color).all(),
|
"filaments": db.query(Filament).order_by(Filament.material, Filament.color).all(),
|
||||||
"stl_files": (stl_files := list_stl_files()),
|
"stl_files": (stl_files := list_stl_files()),
|
||||||
"stl_cache": get_cache_map(stl_files, db),
|
"stl_cache": get_cache_map(stl_files, db),
|
||||||
|
"materials": MATERIALS,
|
||||||
|
"colors": COLORS,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -243,7 +250,8 @@ def bracket_detail(bracket_id: int, request: Request, db: Session = Depends(get_
|
|||||||
async def add_jobs_to_bracket(
|
async def add_jobs_to_bracket(
|
||||||
bracket_id: int,
|
bracket_id: int,
|
||||||
quantity: int = Form(1),
|
quantity: int = Form(1),
|
||||||
filament_id: str = Form(""),
|
job_material: str = Form(""),
|
||||||
|
job_color: str = Form(""),
|
||||||
notes: str = Form(""),
|
notes: str = Form(""),
|
||||||
stl_file: UploadFile = File(None),
|
stl_file: UploadFile = File(None),
|
||||||
stl_select: str = Form(""),
|
stl_select: str = Form(""),
|
||||||
@@ -263,7 +271,8 @@ async def add_jobs_to_bracket(
|
|||||||
name=Path(filename).stem if filename else "Unnamed",
|
name=Path(filename).stem if filename else "Unnamed",
|
||||||
file_name=filename,
|
file_name=filename,
|
||||||
bracket_id=bracket_id,
|
bracket_id=bracket_id,
|
||||||
filament_id=int(filament_id) if filament_id else None,
|
job_material=job_material or None,
|
||||||
|
job_color=job_color or None,
|
||||||
notes=notes.strip() or None,
|
notes=notes.strip() or None,
|
||||||
)
|
)
|
||||||
db.add(job)
|
db.add(job)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-white">{{ j.file_name or j.name }}</td>
|
<td class="text-white">{{ j.file_name or j.name }}</td>
|
||||||
<td class="text-secondary">
|
<td class="text-secondary">
|
||||||
{% if j.filament %}{{ j.filament.material.value }}{% if j.filament.color %} · {{ j.filament.color }}{% endif %}{% else %}—{% endif %}
|
{% if j.job_material %}{{ j.job_material }}{% if j.job_color %} · {{ j.job_color }}{% endif %}{% else %}—{% endif %}
|
||||||
</td>
|
</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>
|
<td>
|
||||||
@@ -127,16 +127,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-4">
|
||||||
<label class="form-label text-secondary small">Material & Color</label>
|
<label class="form-label text-secondary small">Material</label>
|
||||||
<select name="filament_id" class="form-select bg-dark border-secondary text-white">
|
<select name="job_material" class="form-select bg-dark border-secondary text-white">
|
||||||
<option value="">— none —</option>
|
<option value="">— none —</option>
|
||||||
{% for f in filaments %}
|
{% for m in materials %}<option>{{ m }}</option>{% endfor %}
|
||||||
<option value="{{ f.id }}">{{ f.material.value }}{% if f.color %} · {{ f.color }}{% endif %}{% if f.brand %} ({{ f.brand }}){% endif %}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-4">
|
||||||
|
<label class="form-label text-secondary small">Color</label>
|
||||||
|
<select name="job_color" class="form-select bg-dark border-secondary text-white">
|
||||||
|
<option value="">— none —</option>
|
||||||
|
{% for c in colors %}<option>{{ c }}</option>{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
<label class="form-label text-secondary small">
|
<label class="form-label text-secondary small">
|
||||||
Quantity
|
Quantity
|
||||||
<span class="text-secondary fw-normal">(copies to create)</span>
|
<span class="text-secondary fw-normal">(copies to create)</span>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
<td class="text-secondary">{{ j.file_name or '—' }}</td>
|
<td class="text-secondary">{{ j.file_name or '—' }}</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.filament %}{{ j.filament.material.value }}{% if j.filament.color %} · {{ j.filament.color }}{% endif %}{% else %}—{% endif %}
|
{% if j.job_material %}{{ 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 %}
|
||||||
@@ -140,18 +140,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="row g-3">
|
||||||
<label class="form-label text-secondary small">Material & Color</label>
|
<div class="col-md-6">
|
||||||
<select name="filament_id" class="form-select bg-dark border-secondary text-white">
|
<label class="form-label text-secondary small">Material</label>
|
||||||
|
<select name="job_material" class="form-select bg-dark border-secondary text-white">
|
||||||
<option value="">— none —</option>
|
<option value="">— none —</option>
|
||||||
{% for f in filaments %}
|
{% for m in materials %}<option>{{ m }}</option>{% endfor %}
|
||||||
<option value="{{ f.id }}">
|
|
||||||
{{ f.material.value }}{% if f.color %} · {{ f.color }}{% endif %}
|
|
||||||
{% if f.brand %} ({{ f.brand }}){% endif %}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label text-secondary small">Color</label>
|
||||||
|
<select name="job_color" class="form-select bg-dark border-secondary text-white">
|
||||||
|
<option value="">— none —</option>
|
||||||
|
{% for c in colors %}<option>{{ c }}</option>{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user