Pārlūkot izejas kodu

fix(queue): a job moved to another printer drops the old printer's settings

The AMS mapping, Print Anyway and nozzle choices are worked out for one
printer. Bulk edit, single edit and order "dispatch remaining" carried
them to another printer, where they mapped the wrong colour, skipped the
new printer's filament checks or pinned every copy to one printer. They
now stay with the old printer and are worked out again for the new one.
maziggy 6 dienas atpakaļ
vecāks
revīzija
2699bebc37
60 mainītis faili ar 268 papildinājumiem un 7 dzēšanām
  1. 1 0
      CHANGELOG.md
  2. 22 1
      backend/app/api/routes/print_queue.py
  3. 7 0
      backend/app/services/print_batch.py
  4. 29 0
      backend/app/services/queue_item_printer.py
  5. 154 0
      backend/tests/integration/test_queue_item_printer_change.py
  6. 45 0
      frontend/src/__tests__/components/PrintModal.test.tsx
  7. 7 1
      frontend/src/components/PrintModal/index.tsx
  8. 0 0
      static/assets/AdditionalSection-DIccX06l.js
  9. 0 0
      static/assets/AmsUnitCard-DenssBrU.js
  10. 0 0
      static/assets/ArchivesPage-BBTsH0Ft.js
  11. 0 0
      static/assets/AssignToAmsModal-Dw34JqhL.js
  12. 0 0
      static/assets/CamWallPage-MumNsp-M.js
  13. 0 0
      static/assets/CameraDiagnoseModal-CjLEIPaz.js
  14. 0 0
      static/assets/CameraPage-C14CA7p1.js
  15. 0 0
      static/assets/CameraWall-yQLEmA3y.js
  16. 0 0
      static/assets/ColumnConfigModal-ZFUWgTiP.js
  17. 0 0
      static/assets/ConfigureAmsSlotModal-DNxMn7mZ.js
  18. 0 0
      static/assets/ConnectAuthorizePage-CiCaCX1M.js
  19. 0 0
      static/assets/ContextMenu-Bz6RiYw8.js
  20. 0 0
      static/assets/ExternalLinkPage-Cqpo7iG8.js
  21. 0 1
      static/assets/FileManagerPage-D2WRvz5W.js
  22. 0 0
      static/assets/FileUploadModal-CvwqbDck.js
  23. 0 0
      static/assets/FinancePage-BvjRxOSU.js
  24. 0 0
      static/assets/GCodeViewerPage-tRpIPQ_i.js
  25. 0 0
      static/assets/GroupEditPage-wG4Pyfg5.js
  26. 0 0
      static/assets/ImagePreviewModal-ClcA1pFC.js
  27. 0 0
      static/assets/InventoryPage-BprLX4qL.js
  28. 0 0
      static/assets/LibraryTrashPage-BuLdIPKl.js
  29. 0 0
      static/assets/LoginPage-BVVNzpkM.js
  30. 0 0
      static/assets/MaintenancePage-BaftlxtZ.js
  31. 0 0
      static/assets/ModelSourcesPage-DaGVpTVz.js
  32. 0 0
      static/assets/NotificationsPage-CRn5ePpt.js
  33. 0 1
      static/assets/PdfPreviewModal-C0SCv0OT.js
  34. 0 0
      static/assets/PhotoGalleryModal-BjY_Fe_1.js
  35. 0 0
      static/assets/PrinterLocationsPage-D7puTFwU.js
  36. 0 0
      static/assets/PrintersPage-D0nE3v30.js
  37. 0 0
      static/assets/ProfilesPage-DAvF6r-s.js
  38. 0 0
      static/assets/ProjectDetailPage-BKhmQ0pJ.js
  39. 0 0
      static/assets/ProjectsPage-jfAI_bSa.js
  40. 0 0
      static/assets/QueuePage-CvI9-tuU.js
  41. 0 0
      static/assets/RichTextEditor-COCJm1b2.js
  42. 0 0
      static/assets/SettingsPage-BAqfNCFg.js
  43. 0 0
      static/assets/SetupPage-5mE_7RBo.js
  44. 0 0
      static/assets/SliceModal-CicEVohG.js
  45. 0 0
      static/assets/SpoolBuddyAmsPage-BxPX_SOi.js
  46. 0 0
      static/assets/SpoolBuddyCalibrationPage-DxNC-jLk.js
  47. 0 0
      static/assets/SpoolBuddyDashboard-CxD2cOMT.js
  48. 0 0
      static/assets/SpoolBuddyInventoryPage-f0F2MgZL.js
  49. 1 1
      static/assets/SpoolBuddySettingsPage-Vdg3cH2q.js
  50. 0 0
      static/assets/SpoolBuddyWriteTagPage-D7NiHtRE.js
  51. 0 0
      static/assets/SpreadsheetPreviewModal-C7GhluZi.js
  52. 0 0
      static/assets/StatsPage-7Up6sJgh.js
  53. 0 0
      static/assets/StreamOverlayPage-CulfpJ8j.js
  54. 0 0
      static/assets/SystemInfoPage-ClowstMj.js
  55. 0 0
      static/assets/index-DtAYxO26.js
  56. 1 1
      static/assets/inventorySearch-CjzGy6J-.js
  57. 0 0
      static/assets/locationSensorDefaults-4TZCwGuA.js
  58. 0 0
      static/assets/pdf-awRPq96G.js
  59. 0 0
      static/assets/utils-D_Hqn6az.js
  60. 1 1
      static/index.html

+ 1 - 0
CHANGELOG.md

@@ -163,6 +163,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **The frontend build no longer warns about `path` and `crypto` being externalized for the STEP previewer (#2976)** — `occt-import-js`, the Emscripten build behind STEP previews, requires both modules, but only inside its `ENVIRONMENT_IS_NODE` branches; in the browser it loads its `.wasm` from the URL the preview worker passes and draws randomness from `crypto.getRandomValues`. Vite still externalized both and printed two warnings on every build. `vite.config.ts` now drops exactly those two warnings for that one package through `build.rolldownOptions.onLog`, so an externalization anywhere else, or of any other module, still shows.
 
 ### Fixed
+- **A queued job moved to another printer no longer keeps the old printer's settings** — A job's AMS tray mapping, **Print Anyway** and nozzle choice are worked out for one printer. Moving jobs to another printer (bulk edit, or editing one job) kept them, so a job could print from the slot that held the right material on the old printer but another colour on the new one, and a **Print Anyway** given for the old printer skipped the new printer's filament checks. **Dispatch remaining** of an order also pinned every copy of a job queued for any printer of a model to the printer its latest run happened to land on, with that printer's settings, so the other printers of that model stayed idle. These settings now stay with the old printer and are worked out again for the new one; plate, AMS use, filament overrides and print options are kept. Bulk edit leaves jobs with printer alternatives alone, as editing one does, and a job it moves to a printer no longer also waits for a model.
 - **Pages work for users without the settings permission** — Users in a group without **settings:read** could not read the install settings, so pages that need a few of them fell back to defaults. With billing on, the print dialog hid the cost centre picker and the print was then refused for lacking one. The print dialog also lost its print option and stagger defaults, currency and filament preference. The queue showed the wrong order with shortest job first on, the File Manager lost **Slice**, filament warnings an administrator had turned off still appeared, adding an unknown spool from its tag put it in the built-in inventory even in Spoolman mode, and the theme, currency and forecast lead time fell back to defaults. These settings now reach every signed-in user, without the credentials **settings:read** also shows; G-code snippets are only reported as set or not.
 - **SpoolBuddy writes the Spoolman spool to the tag in Spoolman mode** — Built-in spools are kept after switching to Spoolman. When one of them had the same id as the Spoolman spool being written, SpoolBuddy wrote the built-in spool's material, colour and weight to the tag and linked the tag to that hidden spool, while reporting success. The Spoolman spool never got the tag, so scanning it later found nothing. Writing a tag now uses only the inventory in use.
 - **Budget held by prints that ended is released** — Before a print without a user (authentication off, or its user deleted while it printed) was charged to its cost centre, it kept its budget reservation for good, so the cost centre showed that much less budget left. At the next start, reservations of prints that have finished or were removed from the queue are released. Only the reserved amount is freed; charges already booked don't change.

+ 22 - 1
backend/app/api/routes/print_queue.py

@@ -64,6 +64,7 @@ from backend.app.services.print_batch import (
     refresh_batch_status_for_item,
 )
 from backend.app.services.print_cost_estimate import estimate_queue_source_cost
+from backend.app.services.queue_item_printer import PRINTER_RESOLVED_DEFAULTS, drop_printer_resolved
 from backend.app.services.queue_position import lock_queue_positions, max_queue_position
 from backend.app.utils.printer_models import (
     is_gcode_compatible,
@@ -1330,7 +1331,11 @@ async def bulk_update_queue_items(
             raise HTTPException(400, "Printer not found")
 
     # Fetch all items, minus any bound to a printer the caller can't see (#1727)
-    result = await db.execute(select(PrintQueueItem).where(PrintQueueItem.id.in_(data.item_ids)))
+    result = await db.execute(
+        select(PrintQueueItem)
+        .where(PrintQueueItem.id.in_(data.item_ids))
+        .options(selectinload(PrintQueueItem.variants))
+    )
     items = [item for item in result.scalars().all() if printer_scope.allows(item.printer_id)]
 
     updated_count = 0
@@ -1362,6 +1367,17 @@ async def bulk_update_queue_items(
             continue
 
         item_update_data = update_data.copy()
+        if "printer_id" in item_update_data and item_update_data["printer_id"] != item.printer_id:
+            # A job with printer alternatives picks its own printer (#671),
+            # as the single edit refuses to override.
+            if item.variants:
+                skipped_count += 1
+                continue
+            # Now bound to this printer: not to a model, and nothing worked
+            # out for the old printer carries over.
+            item.target_model = None
+            item.target_location = None
+            drop_printer_resolved(item)
         if validates_billing_fields:
             trusted_estimated_cost = await _trusted_item_estimated_cost(
                 db,
@@ -2153,6 +2169,11 @@ async def update_queue_item(
         update_data["ams_mapping"] = None
         if item.printer_id and "skip_filament_check" not in update_data:
             update_data["skip_filament_check"] = False
+    # Likewise a job moved off its printer to another one: what the dialog
+    # sends for the new printer is kept, the rest goes with the old one.
+    if item.printer_id is not None and new_printer_id != item.printer_id:
+        for field, default in PRINTER_RESOLVED_DEFAULTS.items():
+            update_data.setdefault(field, default)
 
     # Serialize ams_mapping to JSON for TEXT column storage
     if "ams_mapping" in update_data:

+ 7 - 0
backend/app/services/print_batch.py

@@ -28,6 +28,7 @@ from sqlalchemy.orm import selectinload
 from backend.app.models.print_batch import PrintBatch, PrintBatchPlate
 from backend.app.models.print_log import PrintLogEntry
 from backend.app.models.print_queue import PrintQueueItem, PrintQueueVariant
+from backend.app.services.queue_item_printer import drop_printer_resolved
 from backend.app.services.queue_position import next_queue_position
 
 logger = logging.getLogger(__name__)
@@ -461,6 +462,12 @@ def _clone_queue_item(source: PrintQueueItem, *, position: int, created_by_id: i
     )
     for column in CLONED_SETTING_COLUMNS:
         setattr(clone, column, getattr(source, column))
+    # A printer the scheduler picked for a model-based job (or one with
+    # printer alternatives) was that run's pick, not the user's: each copy
+    # waits for any printer of the model again.
+    if source.printer_id is not None and (source.target_model or source.variants):
+        clone.printer_id = None
+        drop_printer_resolved(clone)
     return clone
 
 

+ 29 - 0
backend/app/services/queue_item_printer.py

@@ -0,0 +1,29 @@
+"""What a queue item keeps when its printer changes.
+
+Some of an item's settings were worked out for one printer: its AMS tray
+mapping (that printer's trays), "Print Anyway" (an answer to that printer's
+filament check) and its nozzle choice (that printer's nozzle rack). On
+another printer they mean something else, so they go with the old printer;
+the next dispatch works them out again. What the user chose for the print
+itself (plate, AMS on or off, filament overrides, print options) stays.
+"""
+
+from __future__ import annotations
+
+from collections.abc import Iterable
+from typing import Any
+
+PRINTER_RESOLVED_DEFAULTS: dict[str, Any] = {
+    "ams_mapping": None,
+    "skip_filament_check": False,
+    "nozzle_mapping": None,
+    "nozzle_rack_choice": None,
+}
+
+
+def drop_printer_resolved(item: Any, keep: Iterable[str] = ()) -> None:
+    """Reset *item*'s printer-specific settings, except those named in *keep*."""
+    kept = set(keep)
+    for field, default in PRINTER_RESOLVED_DEFAULTS.items():
+        if field not in kept:
+            setattr(item, field, default)

+ 154 - 0
backend/tests/integration/test_queue_item_printer_change.py

@@ -0,0 +1,154 @@
+"""A queued job moved to another printer leaves the old printer's settings behind."""
+
+from __future__ import annotations
+
+import json
+
+import pytest
+from httpx import AsyncClient
+from sqlalchemy import select
+
+from backend.app.models.print_queue import PrintQueueItem, PrintQueueVariant
+
+pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
+
+RESOLVED_FOR_A = {
+    "ams_mapping": "[2]",
+    "skip_filament_check": True,
+    "nozzle_mapping": json.dumps([1]),
+    "nozzle_rack_choice": json.dumps({"0": 3}),
+}
+
+
+async def _item(db_session, printer_id: int, archive_id: int, **extra) -> int:
+    item = PrintQueueItem(
+        printer_id=printer_id,
+        archive_id=archive_id,
+        status="pending",
+        position=1,
+        plate_id=2,
+        use_ams=False,
+        **{**RESOLVED_FOR_A, **extra},
+    )
+    db_session.add(item)
+    await db_session.commit()
+    return item.id
+
+
+async def _row(db_session, item_id: int) -> PrintQueueItem:
+    db_session.expire_all()
+    return await db_session.get(PrintQueueItem, item_id)
+
+
+def _resolved(item: PrintQueueItem) -> tuple:
+    return item.ams_mapping, item.skip_filament_check, item.nozzle_mapping, item.nozzle_rack_choice
+
+
+async def test_a_bulk_move_drops_what_was_worked_out_for_the_old_printer(
+    async_client: AsyncClient, db_session, printer_factory, archive_factory
+):
+    a, b = await printer_factory(name="A"), await printer_factory(name="B")
+    a_id, b_id = a.id, b.id
+    archive_id = (await archive_factory(a_id)).id
+    moved = await _item(db_session, a_id, archive_id)
+    stays = await _item(db_session, b_id, archive_id)
+    model_job = await _item(db_session, a_id, archive_id, target_model="X1C", target_location="Lab")
+
+    response = await async_client.patch(
+        "/api/v1/queue/bulk", json={"item_ids": [moved, stays, model_job], "printer_id": b_id}
+    )
+
+    assert response.status_code == 200, response.text
+    item = await _row(db_session, moved)
+    assert item.printer_id == b_id
+    assert _resolved(item) == (None, False, None, None)
+    assert (item.plate_id, item.use_ams) == (2, False)
+    assert _resolved(await _row(db_session, stays)) == tuple(RESOLVED_FOR_A.values())
+    pinned = await _row(db_session, model_job)
+    assert (pinned.printer_id, pinned.target_model, pinned.target_location) == (b_id, None, None)
+
+
+async def test_a_bulk_move_leaves_jobs_with_printer_alternatives_alone(
+    async_client: AsyncClient, db_session, printer_factory, archive_factory
+):
+    a, b = await printer_factory(name="A"), await printer_factory(name="B")
+    a_id, b_id = a.id, b.id
+    archive_id = (await archive_factory(a_id)).id
+    item_id = await _item(db_session, a_id, archive_id, target_model="X1C")
+    from backend.app.models.library import LibraryFile
+
+    file = LibraryFile(filename="part.3mf", file_path="part.3mf", file_type="3mf", file_size=1)
+    db_session.add(file)
+    await db_session.flush()
+    db_session.add(PrintQueueVariant(queue_item_id=item_id, position=0, target_model="X1C", library_file_id=file.id))
+    await db_session.commit()
+
+    response = await async_client.patch("/api/v1/queue/bulk", json={"item_ids": [item_id], "printer_id": b_id})
+
+    assert response.json()["skipped_count"] == 1
+    assert (await _row(db_session, item_id)).printer_id == a_id
+
+
+async def test_editing_a_job_onto_another_printer_keeps_only_what_was_sent_for_it(
+    async_client: AsyncClient, db_session, printer_factory, archive_factory
+):
+    a, b = await printer_factory(name="A"), await printer_factory(name="B")
+    a_id, b_id = a.id, b.id
+    archive_id = (await archive_factory(a_id)).id
+    item_id = await _item(db_session, a_id, archive_id)
+
+    # What the edit dialog sends: the new printer's mapping, no Print Anyway.
+    response = await async_client.patch(f"/api/v1/queue/{item_id}", json={"printer_id": b_id, "ams_mapping": [5]})
+
+    assert response.status_code == 200, response.text
+    item = await _row(db_session, item_id)
+    assert _resolved(item) == ("[5]", False, None, None)
+
+
+async def test_editing_a_job_on_the_same_printer_keeps_its_settings(
+    async_client: AsyncClient, db_session, printer_factory, archive_factory
+):
+    a = await printer_factory(name="A")
+    a_id = a.id
+    archive_id = (await archive_factory(a_id)).id
+    item_id = await _item(db_session, a_id, archive_id)
+
+    response = await async_client.patch(f"/api/v1/queue/{item_id}", json={"printer_id": a_id})
+
+    assert response.status_code == 200, response.text
+    assert _resolved(await _row(db_session, item_id)) == tuple(RESOLVED_FOR_A.values())
+
+
+async def test_copies_of_a_model_job_wait_for_any_printer_of_the_model(
+    async_client: AsyncClient, db_session, printer_factory, archive_factory
+):
+    from backend.tests.integration.test_print_batch_orders import _create_order
+
+    a = await printer_factory(name="A", model="X1C")
+    a_id = a.id
+    archive_id = (await archive_factory(a_id)).id
+    order = await _create_order(async_client, archive_id, [{"plate_id": 2, "quantity_target": 3}])
+    # Run 1 as the scheduler leaves it: its model, the printer it picked, and
+    # what it worked out for that printer.
+    source_id = await _item(db_session, a_id, archive_id, target_model="X1C", batch_id=order["id"])
+
+    response = await async_client.post(f"/api/v1/queue/batches/{order['id']}/dispatch", json={})
+
+    assert response.status_code == 200, response.text
+    db_session.expire_all()
+    copies = (
+        (
+            await db_session.execute(
+                select(PrintQueueItem)
+                .where(PrintQueueItem.batch_id == order["id"])
+                .where(PrintQueueItem.id != source_id)
+            )
+        )
+        .scalars()
+        .all()
+    )
+    assert copies
+    for copy in copies:
+        assert (copy.printer_id, copy.target_model) == (None, "X1C")
+        assert _resolved(copy) == (None, False, None, None)
+        assert (copy.plate_id, copy.use_ams) == (2, False)

+ 45 - 0
frontend/src/__tests__/components/PrintModal.test.tsx

@@ -2878,3 +2878,48 @@ describe('PrintModal — a deliberate material substitution is acknowledged (#27
     expect(saved.skip_filament_check).toBeUndefined();
   });
 });
+
+describe('PrintModal edit: moving a job to another printer', () => {
+  const mockOnClose = vi.fn();
+  let patched: Record<string, unknown>[];
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+    patched = [];
+    server.use(
+      http.get('/api/v1/printers/', () =>
+        HttpResponse.json([
+          { id: 1, name: 'Printer 01', model: 'H2C', ip_address: '192.168.1.101', enabled: true, is_active: true },
+          { id: 2, name: 'Printer 02', model: 'H2C', ip_address: '192.168.1.102', enabled: true, is_active: true },
+        ]),
+      ),
+      http.get('/api/v1/archives/:id/plates', () => HttpResponse.json({ is_multi_plate: false, plates: [] })),
+      http.get('/api/v1/printers/:id/status', () =>
+        HttpResponse.json({ connected: true, state: 'IDLE', ams: [], vt_tray: [] }),
+      ),
+      http.patch('/api/v1/queue/:id', async ({ request }) => {
+        patched.push((await request.json()) as Record<string, unknown>);
+        return HttpResponse.json({ id: 1, status: 'pending' });
+      }),
+    );
+  });
+
+  it("does not carry the old printer's nozzle rack picks over", async () => {
+    const user = userEvent.setup();
+    const item = createMockQueueItem({
+      printer_id: 1,
+      target_model: null,
+      nozzle_rack_choice: { 0: 3 },
+    } as Partial<PrintQueueItem>);
+    render(<PrintModal mode="edit-queue-item" archiveId={1} archiveName="Job" queueItem={item} onClose={mockOnClose} />);
+
+    // The dialog adds printers; moving the job is deselecting one, selecting the other.
+    await user.click(await screen.findByText('Printer 01'));
+    await user.click(screen.getByText('Printer 02'));
+    await user.click(screen.getByRole('button', { name: /save/i }));
+
+    await waitFor(() => expect(patched).toHaveLength(1));
+    expect(patched[0].printer_id).toBe(2);
+    expect(patched[0].nozzle_rack_choice).toBeNull();
+  });
+});

+ 7 - 1
frontend/src/components/PrintModal/index.tsx

@@ -774,6 +774,11 @@ export function PrintModal({
         setPerPrinterConfigs({});
         setInitialExpandApplied(new Set());
       }
+      // Rack positions name slots of the queued printer's nozzle rack.
+      if (printersChanged) {
+        setNozzleRackChoice({});
+        setNozzleRackChoiceByPlate({});
+      }
     } else {
       setManualMappings({});
       setManualMappingsByPlate({});
@@ -1461,7 +1466,8 @@ export function PrintModal({
                 manual_start: editManualStart,
                 ams_mapping: printerMapping,
                 // Only ever set here, never cleared: an earlier "Print Anyway"
-                // stays acknowledged across an edit, as it does today.
+                // stays acknowledged across an edit on the same printer. On
+                // another printer the backend drops it unless sent here.
                 skip_filament_check: mappingSubstitutesMaterial(printerId, plateId, printerMapping) ? true : undefined,
                 // null, not undefined: an operator who cleared their picks
                 // means "assign these again", and undefined would leave the

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/AdditionalSection-DIccX06l.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/AmsUnitCard-DenssBrU.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ArchivesPage-BBTsH0Ft.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/AssignToAmsModal-Dw34JqhL.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/CamWallPage-MumNsp-M.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/CameraDiagnoseModal-CjLEIPaz.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/CameraPage-C14CA7p1.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/CameraWall-yQLEmA3y.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ColumnConfigModal-ZFUWgTiP.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ConfigureAmsSlotModal-DNxMn7mZ.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ConnectAuthorizePage-CiCaCX1M.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ContextMenu-Bz6RiYw8.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ExternalLinkPage-Cqpo7iG8.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 1
static/assets/FileManagerPage-D2WRvz5W.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/FileUploadModal-CvwqbDck.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/FinancePage-BvjRxOSU.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/GCodeViewerPage-tRpIPQ_i.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/GroupEditPage-wG4Pyfg5.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ImagePreviewModal-ClcA1pFC.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/InventoryPage-BprLX4qL.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/LibraryTrashPage-BuLdIPKl.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/LoginPage-BVVNzpkM.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/MaintenancePage-BaftlxtZ.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ModelSourcesPage-DaGVpTVz.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/NotificationsPage-CRn5ePpt.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 1
static/assets/PdfPreviewModal-C0SCv0OT.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/PhotoGalleryModal-BjY_Fe_1.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/PrinterLocationsPage-D7puTFwU.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/PrintersPage-D0nE3v30.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ProfilesPage-DAvF6r-s.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ProjectDetailPage-BKhmQ0pJ.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/ProjectsPage-jfAI_bSa.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/QueuePage-CvI9-tuU.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/RichTextEditor-COCJm1b2.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SettingsPage-BAqfNCFg.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SetupPage-5mE_7RBo.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SliceModal-CicEVohG.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SpoolBuddyAmsPage-BxPX_SOi.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SpoolBuddyCalibrationPage-DxNC-jLk.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SpoolBuddyDashboard-CxD2cOMT.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SpoolBuddyInventoryPage-f0F2MgZL.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
static/assets/SpoolBuddySettingsPage-Vdg3cH2q.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SpoolBuddyWriteTagPage-D7NiHtRE.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SpreadsheetPreviewModal-C7GhluZi.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/StatsPage-7Up6sJgh.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/StreamOverlayPage-CulfpJ8j.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/SystemInfoPage-ClowstMj.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/index-DtAYxO26.js


+ 1 - 1
static/assets/inventorySearch-BwT_OPao.js → static/assets/inventorySearch-CjzGy6J-.js

@@ -1 +1 @@
-import{mt as e}from"./index-CGY3_LDo.js";function t(t,n){let r=n.trim().toLowerCase();if(!r)return!0;let i=r.match(/^#(\d+)$/);return i?t.id===Number(i[1]):String(t.id).includes(r)||t.material.toLowerCase().includes(r)||(t.brand?.toLowerCase().includes(r)??!1)||(t.color_name?.toLowerCase().includes(r)??!1)||(e(t.color_name,t.rgba,t.color_name_is_synthesized)?.toLowerCase().includes(r)??!1)||(t.subtype?.toLowerCase().includes(r)??!1)||(t.note?.toLowerCase().includes(r)??!1)||(t.slicer_filament_name?.toLowerCase().includes(r)??!1)||(t.storage_location?.toLowerCase().includes(r)??!1)||(t.material_number?.toLowerCase().includes(r)??!1)||(t.suppliers?.some(e=>e.supplier_name.toLowerCase().includes(r)||(e.supplier_article_number?.toLowerCase().includes(r)??!1))??!1)}function n(e,n){return n?e.filter(e=>t(e,n)):e}export{n as t};
+import{mt as e}from"./index-DtAYxO26.js";function t(t,n){let r=n.trim().toLowerCase();if(!r)return!0;let i=r.match(/^#(\d+)$/);return i?t.id===Number(i[1]):String(t.id).includes(r)||t.material.toLowerCase().includes(r)||(t.brand?.toLowerCase().includes(r)??!1)||(t.color_name?.toLowerCase().includes(r)??!1)||(e(t.color_name,t.rgba,t.color_name_is_synthesized)?.toLowerCase().includes(r)??!1)||(t.subtype?.toLowerCase().includes(r)??!1)||(t.note?.toLowerCase().includes(r)??!1)||(t.slicer_filament_name?.toLowerCase().includes(r)??!1)||(t.storage_location?.toLowerCase().includes(r)??!1)||(t.material_number?.toLowerCase().includes(r)??!1)||(t.suppliers?.some(e=>e.supplier_name.toLowerCase().includes(r)||(e.supplier_article_number?.toLowerCase().includes(r)??!1))??!1)}function n(e,n){return n?e.filter(e=>t(e,n)):e}export{n as t};

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/locationSensorDefaults-4TZCwGuA.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/pdf-awRPq96G.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
static/assets/utils-D_Hqn6az.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-CGY3_LDo.js"></script>
+    <script type="module" crossorigin src="/assets/index-DtAYxO26.js"></script>
     <link rel="modulepreload" crossorigin href="/assets/chunk-aKtaBQYM.js">
     <link rel="modulepreload" crossorigin href="/assets/client-BBL32ZR9.js">
     <link rel="modulepreload" crossorigin href="/assets/QueryClientProvider-rKpZZh6D.js">

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels