feat: Add quick wins batch (#42, #43, #25, #35, #20, #36, #39)
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
- #42: Allow spools heavier than theoretical max (remove weight clamps) - #43: Show filament custom fields on spool detail page - #25: Sort spools by custom fields and remaining weight - #35: Global search box for spool list - #20: Gallery view for spools (color grid with progress bars) - #36: Spool-level color override with ColorPicker - #39: Cost analytics endpoint, total spent & avg cost/kg stats - Fix: Filament select refresh after inline creation - Fix: Parse temperatures from filament comments Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,11 @@ class Spool(Base):
|
||||
default=None,
|
||||
comment="Extra weight to account for (DryPods, custom holders, etc.).",
|
||||
)
|
||||
color_hex: Mapped[Optional[str]] = mapped_column(
|
||||
String(8),
|
||||
default=None,
|
||||
comment="Spool-level color override (hex without #).",
|
||||
)
|
||||
location: Mapped[Optional[str]] = mapped_column(String(64), comment="Legacy flat location string.")
|
||||
location_id: Mapped[Optional[int]] = mapped_column(
|
||||
ForeignKey("location.id", ondelete="SET NULL"),
|
||||
|
||||
@@ -42,6 +42,7 @@ async def create(
|
||||
initial_weight: Optional[float] = None,
|
||||
spool_weight: Optional[float] = None,
|
||||
extra_weight: Optional[float] = None,
|
||||
color_hex: Optional[str] = None,
|
||||
used_weight: Optional[float] = None,
|
||||
first_used: Optional[datetime] = None,
|
||||
last_used: Optional[datetime] = None,
|
||||
@@ -70,7 +71,10 @@ async def create(
|
||||
"remaining_weight can only be used if the initial_weight is "
|
||||
"defined or the filament has a weight set.",
|
||||
)
|
||||
used_weight = max(initial_weight - remaining_weight, 0)
|
||||
if remaining_weight > initial_weight:
|
||||
# Spool is heavier than expected (manufacturing tolerance), adjust initial_weight
|
||||
initial_weight = remaining_weight
|
||||
used_weight = initial_weight - remaining_weight
|
||||
else:
|
||||
used_weight = 0
|
||||
|
||||
@@ -86,6 +90,7 @@ async def create(
|
||||
initial_weight=initial_weight,
|
||||
spool_weight=spool_weight,
|
||||
extra_weight=extra_weight,
|
||||
color_hex=color_hex,
|
||||
used_weight=used_weight,
|
||||
price=price,
|
||||
first_used=first_used,
|
||||
@@ -117,6 +122,7 @@ async def get_by_id(db: AsyncSession, spool_id: int) -> models.Spool:
|
||||
async def find( # noqa: C901, PLR0912
|
||||
*,
|
||||
db: AsyncSession,
|
||||
q: Optional[str] = None,
|
||||
filament_name: Optional[str] = None,
|
||||
filament_id: Optional[Union[int, Sequence[int]]] = None,
|
||||
filament_material: Optional[str] = None,
|
||||
@@ -155,6 +161,20 @@ async def find( # noqa: C901, PLR0912
|
||||
stmt = add_where_clause_str_opt(stmt, models.Spool.location, location)
|
||||
stmt = add_where_clause_str_opt(stmt, models.Spool.lot_nr, lot_nr)
|
||||
|
||||
# Global search: OR across multiple text fields
|
||||
if q is not None and q.strip():
|
||||
search_term = f"%{q.strip()}%"
|
||||
stmt = stmt.where(
|
||||
sqlalchemy.or_(
|
||||
models.Filament.name.ilike(search_term),
|
||||
models.Filament.material.ilike(search_term),
|
||||
models.Vendor.name.ilike(search_term),
|
||||
models.Spool.location.ilike(search_term),
|
||||
models.Spool.lot_nr.ilike(search_term),
|
||||
models.Spool.comment.ilike(search_term),
|
||||
)
|
||||
)
|
||||
|
||||
# Filter by color_hex (exact match, supports multiple comma-separated values)
|
||||
if filament_color_hex is not None:
|
||||
if isinstance(filament_color_hex, str):
|
||||
@@ -223,6 +243,18 @@ async def find( # noqa: C901, PLR0912
|
||||
sorts.append(models.Filament.name)
|
||||
elif fieldstr == "price":
|
||||
sorts.append(coalesce(models.Spool.price, models.Filament.price))
|
||||
elif fieldstr.startswith("extra."):
|
||||
# Sort by custom field value
|
||||
extra_key = fieldstr[len("extra."):]
|
||||
extra_alias = sqlalchemy.orm.aliased(models.SpoolField)
|
||||
stmt = stmt.outerjoin(
|
||||
extra_alias,
|
||||
sqlalchemy.and_(
|
||||
extra_alias.spool_id == models.Spool.id,
|
||||
extra_alias.key == extra_key,
|
||||
),
|
||||
)
|
||||
sorts.append(extra_alias.value)
|
||||
else:
|
||||
sorts.append(parse_nested_field(models.Spool, fieldstr))
|
||||
|
||||
@@ -260,7 +292,10 @@ async def update(
|
||||
elif k == "remaining_weight":
|
||||
if spool.initial_weight is None:
|
||||
raise ItemCreateError("remaining_weight can only be used if initial_weight is set.")
|
||||
spool.used_weight = max(spool.initial_weight - v, 0)
|
||||
if v > spool.initial_weight:
|
||||
# Spool is heavier than expected (manufacturing tolerance), adjust initial_weight
|
||||
spool.initial_weight = v
|
||||
spool.used_weight = spool.initial_weight - v
|
||||
elif isinstance(v, datetime):
|
||||
setattr(spool, k, utc_timezone_naive(v))
|
||||
elif k == "extra":
|
||||
|
||||
Reference in New Issue
Block a user