Ver Fonte

fix(assign-spool): refresh printer status after assignment so card updates without manual Force-refresh (#1414 follow-up)

  Reporter (@snozzlebert on A1 mini external slot) saw the
  Filament page Location column update correctly after Assign
  Spool, but the Printer card kept showing "Empty slot" until
  they manually pressed Force-refresh. MQTT command was going
  through fine; gap was client-side.

  AssignSpoolModal's two onSuccess callbacks invalidated the
  inventory / slot-assignment queries but never invalidated
  ['printerStatus', printerId] and never issued a pushall. For
  Bambu RFID-tagged spools the printer echoes the new tray_type
  on its own; for non-RFID spools and A1 mini external slots
  the firmware doesn't volunteer that state change.

  Added nudgePrinterRepublish() helper called from both onSuccess
  paths: api.refreshPrinterStatus(printerId) to issue the pushall
  (same call the Force-refresh button uses) plus invalidate
  printerStatus so the refetch lands. Refresh failures are
  swallowed — the assignment itself succeeded; a stale-cache
  nudge that didn't go through shouldn't surface as "assign
  failed". Same pattern as ConfigureAmsSlotModal since #1235,
  with the extra pushall because assign-spool affects firmware-
  side state.
maziggy há 1 semana atrás
pai
commit
9bcaafbc1a

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
CHANGELOG.md


+ 38 - 0
frontend/src/__tests__/components/AssignSpoolModal.test.tsx

@@ -9,9 +9,12 @@ vi.mock('../../api/client', () => ({
     getSpools: vi.fn(),
     getAssignments: vi.fn(),
     assignSpool: vi.fn(),
+    assignSpoolmanSlot: vi.fn(),
     getSpoolmanInventorySpools: vi.fn(),
+    getSpoolmanSlotAssignments: vi.fn().mockResolvedValue([]),
     getSettings: vi.fn().mockResolvedValue({}),
     getAuthStatus: vi.fn().mockResolvedValue({ auth_enabled: false }),
+    refreshPrinterStatus: vi.fn().mockResolvedValue({ status: 'ok' }),
   },
 }));
 
@@ -285,6 +288,41 @@ describe('AssignSpoolModal', () => {
       expect(screen.getByText(/Devil Design/)).toBeInTheDocument();
     });
   });
+
+  it('nudges the printer to republish after successful assignment (#1414)', async () => {
+    // The backend's assign-spool path issues an MQTT command, but firmware
+    // (esp. A1 mini external slots and any non-RFID assignment) doesn't
+    // always echo the new tray state back on its own — the printer card
+    // then sits on stale data until the user hits Force-refresh. Modal
+    // calls refreshPrinterStatus to issue a pushall so the printer
+    // republishes state, mirroring the Force-refresh button.
+    const { default: userEvent } = await import('@testing-library/user-event');
+    const user = userEvent.setup();
+
+    // Tray material matches the spool to skip the mismatch confirm dialog.
+    (api.getSpools as ReturnType<typeof vi.fn>).mockResolvedValue([manualSpool]);
+    (api.assignSpool as ReturnType<typeof vi.fn>).mockResolvedValue({
+      id: 1, spool_id: 1, printer_id: 7, ams_id: 0, tray_id: 0,
+    });
+
+    render(
+      <AssignSpoolModal
+        {...defaultProps}
+        printerId={7}
+        trayInfo={{ type: 'PLA', material: 'PLA', profile: 'PLA', color: 'FF0000', location: 'AMS 1 - Slot 1' }}
+      />
+    );
+
+    await waitFor(() => {
+      expect(screen.getByText(/Polymaker/)).toBeInTheDocument();
+    });
+    await user.click(screen.getByText(/Polymaker/));
+    await user.click(screen.getByRole('button', { name: /assign spool/i }));
+
+    await waitFor(() => {
+      expect(api.refreshPrinterStatus).toHaveBeenCalledWith(7);
+    });
+  });
 });
 
 describe('AssignSpoolModal — Spoolman enabled (T-Gap 7)', () => {

+ 16 - 0
frontend/src/components/AssignSpoolModal.tsx

@@ -113,6 +113,20 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr
     );
   }, [allSpoolmanAssignments, printerId, amsId, trayId]);
 
+  // #1414: nudge the printer to republish its state after we assign a
+  // spool. The backend assign-spool path already issues an MQTT command,
+  // but firmware (especially A1 mini external slots and any non-RFID
+  // assignment) doesn't always echo the new tray state back on its own,
+  // so the printer card sits on stale data and the user has to press
+  // Force-refresh to see the assignment. Calling /refresh-status forces
+  // a pushall the way the Force-refresh button does. Failures are
+  // intentionally swallowed — the assignment itself succeeded; if the
+  // refresh is offline the next poll / websocket update will catch up.
+  const nudgePrinterRepublish = () => {
+    api.refreshPrinterStatus(printerId).catch(() => {});
+    queryClient.invalidateQueries({ queryKey: ['printerStatus', printerId] });
+  };
+
   const assignMutation = useMutation({
     mutationFn: (spoolId: number) =>
       api.assignSpool({ spool_id: spoolId, printer_id: printerId, ams_id: amsId, tray_id: trayId }),
@@ -126,6 +140,7 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr
         return filtered;
       });
       queryClient.invalidateQueries({ queryKey: ['spool-assignments'] });
+      nudgePrinterRepublish();
       showToast(t('inventory.assignSuccess'), 'success');
       setShowMismatchConfirm(false);
       setPendingAssignId(null);
@@ -148,6 +163,7 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr
     onSuccess: () => {
       queryClient.invalidateQueries({ queryKey: ['spoolman-inventory-spools'] });
       queryClient.invalidateQueries({ queryKey: ['spoolman-slot-assignments'] });
+      nudgePrinterRepublish();
       showToast(t('inventory.assignSuccess'), 'success');
       onClose();
     },

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
static/assets/index-D9JqDSB2.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-CcGEQ2Qx.js"></script>
+    <script type="module" crossorigin src="/assets/index-D9JqDSB2.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-KYwGxnG9.css">
   </head>
   <body>

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff