[Fix]: Update location when linking/unlinking to/from Spoolman (#669)

* Enhance link spool functionality with additional printer and AMS details

* Refactor linkSpool function to accept detailed context object for improved spooling integration

* Enhance LinkSpoolModal to include amsName in props and linkSpool mutation for improved functionality

* Add amsName prop to PrinterCard for enhanced spool linking functionality

* Updates LinkSpoolModal test to work with recent spool location update changes

* Adds clear_location to unlink testing

* Refactor LinkSpoolRequest to remove ams_name and update location generation logic for improved clarity

* Remove amsName from LinkSpoolModal and PrinterCard for cleaner API integration

* Remove amsName from LinkSpoolModal test props and update linkSpool mock response for improved clarity
This commit is contained in:
Dakota G
2026-03-11 09:38:14 +01:00
committed by GitHub
parent 253828c2b5
commit be2ddbee5d
5 changed files with 49 additions and 8 deletions
+18 -3
View File
@@ -1,7 +1,7 @@
"""Spoolman integration API routes."""
import logging
import json
import logging
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
@@ -669,6 +669,9 @@ class LinkSpoolRequest(BaseModel):
spool_tag: str | None = None
tray_uuid: str | None = None
tag_uid: str | None = None
printer_id: int | None = None
ams_id: int | None = None
tray_id: int | None = None
@router.post("/spools/{spool_id}/link")
@@ -708,12 +711,23 @@ async def link_spool(
if set(spool_tag) == {"0"}:
raise HTTPException(status_code=400, detail="Invalid spool tag format (all-zero tag is not linkable)")
spool_tag = spool_tag.upper()
# Build location like: "{Printer Name} - {AMS Name} {Slot Number}"
location: str | None = None
if request.printer_id is not None and request.ams_id is not None and request.tray_id is not None:
printer_result = await db.execute(select(Printer).where(Printer.id == request.printer_id))
printer = printer_result.scalar_one_or_none()
if not printer:
raise HTTPException(status_code=404, detail="Printer not found")
location = f"{printer.name} - {client.convert_ams_slot_to_location(request.ams_id, request.tray_id)}"
# Update spool with tag
# Note: Spoolman extra field values must be valid JSON, so we encode the string
import json
result = await client.update_spool(
spool_id=spool_id,
location=location,
extra={"tag": json.dumps(spool_tag)},
)
@@ -748,6 +762,7 @@ async def unlink_spool(
result = await client.update_spool(
spool_id=spool_id,
clear_location=True,
extra={"tag": json.dumps("")},
)
@@ -479,6 +479,7 @@ class TestSpoolmanAPI:
mock_spoolman_client.update_spool.assert_called_once_with(
spool_id=1,
clear_location=True,
extra={"tag": '""'},
)
@@ -54,6 +54,7 @@ describe('LinkSpoolModal', () => {
filament_material: 'PLA',
filament_color_hex: 'FF0000',
remaining_weight: 800,
location: null,
},
{
id: 2,
@@ -61,13 +62,14 @@ describe('LinkSpoolModal', () => {
filament_material: 'PETG',
filament_color_hex: '0000FF',
remaining_weight: 500,
location: null,
},
];
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(api.getUnlinkedSpools).mockResolvedValue(mockSpools);
vi.mocked(api.linkSpool).mockResolvedValue({});
vi.mocked(api.linkSpool).mockResolvedValue({ success: true, message: 'ok' });
});
describe('rendering', () => {
@@ -125,7 +127,12 @@ describe('LinkSpoolModal', () => {
fireEvent.click(screen.getByText(/Generic PLA Red/).closest('button')!);
await waitFor(() => {
expect(api.linkSpool).toHaveBeenCalledWith(1, 'A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4');
expect(api.linkSpool).toHaveBeenCalledWith(1, {
spoolTag: 'ABCD1234',
printerId: 1,
amsId: 0,
trayId: 0,
});
});
});
+15 -2
View File
@@ -3527,10 +3527,23 @@ export const api = {
request<UnlinkedSpool[]>('/spoolman/spools/unlinked'),
getLinkedSpools: () =>
request<LinkedSpoolsMap>('/spoolman/spools/linked'),
linkSpool: (spoolId: number, spoolTag: string) =>
linkSpool: (
spoolId: number,
context: {
spoolTag: string;
printerId: number;
amsId: number;
trayId: number;
}
) =>
request<{ success: boolean; message: string }>(`/spoolman/spools/${spoolId}/link`, {
method: 'POST',
body: JSON.stringify({ spool_tag: spoolTag }),
body: JSON.stringify({
spool_tag: context.spoolTag,
printer_id: context.printerId,
ams_id: context.amsId,
tray_id: context.trayId,
}),
}),
unlinkSpool: (spoolId: number) =>
request<{ success: boolean; message: string }>(`/spoolman/spools/${spoolId}/unlink`, {
+6 -1
View File
@@ -46,7 +46,12 @@ export function LinkSpoolModal({ isOpen, onClose, tagUid, trayUuid, printerId, a
const linkMutation = useMutation({
mutationFn: (spoolId: number) =>
api.linkSpool(spoolId, spoolTag!),
api.linkSpool(spoolId, {
spoolTag: spoolTag!,
printerId,
amsId,
trayId,
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['unlinked-spools'] });
queryClient.invalidateQueries({ queryKey: ['linked-spools'] });