| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- """Shared upsert for the slot_preset_mappings row that drives the AMS slot
- card's displayed preset name.
- Three call sites must keep this row in sync with the currently-assigned spool:
- - ``api.routes.inventory.apply_spool_to_slot_via_mqtt`` (internal manual assign)
- - ``services.spool_tag_matcher.auto_assign_spool`` (internal RFID auto-assign)
- - ``main.auto_sync_spoolman_ams_trays`` (Spoolman RFID-driven sync)
- If any of them skips this row, the slot card surfaces the previous spool's
- preset name because the PrintersPage display chain consults
- slot_preset_mappings.preset_name first — it overrides cloudInfo.name and the
- spool's own slicer_filament_name.
- """
- from __future__ import annotations
- import logging
- from sqlalchemy import select
- from sqlalchemy.ext.asyncio import AsyncSession
- from backend.app.models.slot_preset import SlotPresetMapping
- from backend.app.models.spool import Spool
- from backend.app.utils.filament_ids import filament_id_to_setting_id
- logger = logging.getLogger(__name__)
- async def upsert_slot_preset(
- *,
- db: AsyncSession,
- printer_id: int,
- ams_id: int,
- tray_id: int,
- preset_id: str,
- preset_name: str,
- preset_source: str = "cloud",
- ) -> None:
- """Primitive upsert. No-op when ``preset_id`` is empty (the column is
- NOT NULL on the model, and an empty string isn't a useful key to
- overwrite by). Soft-fails on DB errors so a broken upsert never
- cascades into the surrounding spool-assign flow.
- """
- if not preset_id:
- return
- try:
- existing = await db.execute(
- select(SlotPresetMapping).where(
- SlotPresetMapping.printer_id == printer_id,
- SlotPresetMapping.ams_id == ams_id,
- SlotPresetMapping.tray_id == tray_id,
- )
- )
- mapping = existing.scalar_one_or_none()
- if mapping:
- mapping.preset_id = preset_id
- mapping.preset_name = preset_name
- mapping.preset_source = preset_source
- else:
- mapping = SlotPresetMapping(
- printer_id=printer_id,
- ams_id=ams_id,
- tray_id=tray_id,
- preset_id=preset_id,
- preset_name=preset_name,
- preset_source=preset_source,
- )
- db.add(mapping)
- await db.commit()
- except Exception as e:
- logger.warning(
- "Failed to save slot preset mapping for printer=%d ams=%d tray=%d: %s",
- printer_id,
- ams_id,
- tray_id,
- e,
- )
- async def upsert_slot_preset_for_spool(
- *,
- db: AsyncSession,
- spool: Spool,
- printer_id: int,
- ams_id: int,
- tray_id: int,
- tray_info_idx: str = "",
- tray_sub_brands: str = "",
- tray_type: str = "",
- setting_id: str = "",
- ) -> None:
- """Convenience wrapper for internal-mode call sites — derives the
- (preset_id, preset_name, preset_source) triple from a ``Spool`` ORM object,
- then defers to ``upsert_slot_preset``.
- Local numeric ``spool.slicer_filament`` (e.g. ``"50"``) → ``local_50``;
- cloud-form ids (GFS… / GFA… via ``filament_id_to_setting_id`` on the
- tray's ``tray_info_idx``) → standard setting_id form.
- """
- preset_name = spool.slicer_filament_name or tray_sub_brands or tray_type
- preset_source = "cloud"
- sf = spool.slicer_filament or ""
- if sf:
- base_sf_mapping = sf.split("_")[0] if "_" in sf else sf
- try:
- int(base_sf_mapping)
- preset_id = f"local_{base_sf_mapping}"
- preset_source = "local"
- except (ValueError, TypeError):
- preset_id = filament_id_to_setting_id(tray_info_idx) if tray_info_idx else setting_id
- else:
- preset_id = filament_id_to_setting_id(tray_info_idx) if tray_info_idx else ""
- await upsert_slot_preset(
- db=db,
- printer_id=printer_id,
- ams_id=ams_id,
- tray_id=tray_id,
- preset_id=preset_id,
- preset_name=preset_name,
- preset_source=preset_source,
- )
- async def upsert_slot_preset_for_spoolman_spool(
- *,
- db: AsyncSession,
- spoolman_spool: dict,
- tray_info_idx: str,
- tray_sub_brands: str,
- tray_type: str,
- printer_id: int,
- ams_id: int,
- tray_id: int,
- ) -> None:
- """Spoolman-mode counterpart. Derives ``preset_name`` from the Spoolman
- filament's display name (falling back to material if absent) and
- ``preset_id`` from the AMS-reported tray_info_idx (the cloud filament
- id the printer is currently using). ``preset_source`` is always
- ``"cloud"`` since Spoolman doesn't carry a local-preset concept.
- The ``spoolman_spool`` dict is the shape returned by
- ``SpoolmanClient.sync_ams_tray`` — ``spool["filament"]["name"]`` etc.
- """
- filament = spoolman_spool.get("filament") or {}
- preset_name = filament.get("name") or filament.get("material") or tray_sub_brands or tray_type
- preset_id = filament_id_to_setting_id(tray_info_idx) if tray_info_idx else ""
- await upsert_slot_preset(
- db=db,
- printer_id=printer_id,
- ams_id=ams_id,
- tray_id=tray_id,
- preset_id=preset_id,
- preset_name=preset_name or "",
- preset_source="cloud",
- )
|