|
@@ -690,6 +690,174 @@ async def test_find_matching_untagged_spool_relationships_loaded(db_session):
|
|
|
assert _relationship_is_loaded(found, "assignments")
|
|
assert _relationship_is_loaded(found, "assignments")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# -- find_matching_untagged_spool: #918 regressions ------------------------
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_find_matching_untagged_spool_null_subtype_fallback(db_session):
|
|
|
|
|
+ """#918: Quick-Add spool (subtype=NULL) matches when AMS reports a subtype.
|
|
|
|
|
+
|
|
|
|
|
+ The form's Quick-Add mode only requires `material`, so bulk-logged spools
|
|
|
|
|
+ have subtype=NULL. Before the fix, the strict `subtype = 'Basic'` filter
|
|
|
|
|
+ excluded these rows and the system created duplicates on first AMS read.
|
|
|
|
|
+ """
|
|
|
|
|
+ spool = Spool(
|
|
|
|
|
+ material="PLA",
|
|
|
|
|
+ subtype=None, # Quick-Add bulk entry
|
|
|
|
|
+ rgba="FFFFFFFF",
|
|
|
|
|
+ brand="Bambu Lab",
|
|
|
|
|
+ label_weight=1000,
|
|
|
|
|
+ core_weight=250,
|
|
|
|
|
+ )
|
|
|
|
|
+ db_session.add(spool)
|
|
|
|
|
+ await db_session.commit()
|
|
|
|
|
+
|
|
|
|
|
+ # Tray reports "PLA Basic" → subtype parsed as "Basic"
|
|
|
|
|
+ found = await find_matching_untagged_spool(db_session, SAMPLE_TRAY)
|
|
|
|
|
+ assert found is not None
|
|
|
|
|
+ assert found.id == spool.id
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_find_matching_untagged_spool_prefers_exact_subtype_over_null(db_session):
|
|
|
|
|
+ """#918: When both an exact-subtype and a NULL-subtype row match, exact wins.
|
|
|
|
|
+
|
|
|
|
|
+ The NULL fallback exists only as a backstop for Quick-Add bulk-logged
|
|
|
|
|
+ spools — if the user did the work to record subtype="Basic", it must
|
|
|
|
|
+ take precedence over a vague "PLA" record, even if the latter is older.
|
|
|
|
|
+ """
|
|
|
|
|
+ import asyncio
|
|
|
|
|
+
|
|
|
|
|
+ null_spool = Spool(
|
|
|
|
|
+ material="PLA",
|
|
|
|
|
+ subtype=None, # Older but vague — should NOT win
|
|
|
|
|
+ rgba="FFFFFFFF",
|
|
|
|
|
+ brand="Bambu Lab",
|
|
|
|
|
+ label_weight=1000,
|
|
|
|
|
+ core_weight=250,
|
|
|
|
|
+ )
|
|
|
|
|
+ db_session.add(null_spool)
|
|
|
|
|
+ await db_session.flush()
|
|
|
|
|
+
|
|
|
|
|
+ await asyncio.sleep(0.05)
|
|
|
|
|
+
|
|
|
|
|
+ exact_spool = Spool(
|
|
|
|
|
+ material="PLA",
|
|
|
|
|
+ subtype="Basic", # Newer but specific — should win
|
|
|
|
|
+ rgba="FFFFFFFF",
|
|
|
|
|
+ brand="Bambu Lab",
|
|
|
|
|
+ label_weight=1000,
|
|
|
|
|
+ core_weight=250,
|
|
|
|
|
+ )
|
|
|
|
|
+ db_session.add(exact_spool)
|
|
|
|
|
+ await db_session.commit()
|
|
|
|
|
+
|
|
|
|
|
+ found = await find_matching_untagged_spool(db_session, SAMPLE_TRAY)
|
|
|
|
|
+ assert found is not None
|
|
|
|
|
+ assert found.id == exact_spool.id
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_find_matching_untagged_spool_rejects_non_bambu_brand(db_session):
|
|
|
|
|
+ """#918: A same-color non-Bambu spool must NOT attract a Bambu UUID.
|
|
|
|
|
+
|
|
|
|
|
+ Without the brand filter, a Polymaker untagged spool of matching
|
|
|
|
|
+ material/color would silently acquire a Bambu RFID UUID, leaving the
|
|
|
|
|
+ user with brand="Polymaker" but a Bambu Lab tray UUID — corrupt data.
|
|
|
|
|
+ """
|
|
|
|
|
+ spool = Spool(
|
|
|
|
|
+ material="PLA",
|
|
|
|
|
+ subtype="Basic",
|
|
|
|
|
+ rgba="FFFFFFFF",
|
|
|
|
|
+ brand="Polymaker", # NOT Bambu — must be rejected
|
|
|
|
|
+ label_weight=1000,
|
|
|
|
|
+ core_weight=250,
|
|
|
|
|
+ )
|
|
|
|
|
+ db_session.add(spool)
|
|
|
|
|
+ await db_session.commit()
|
|
|
|
|
+
|
|
|
|
|
+ found = await find_matching_untagged_spool(db_session, SAMPLE_TRAY)
|
|
|
|
|
+ assert found is None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_find_matching_untagged_spool_accepts_null_brand(db_session):
|
|
|
|
|
+ """#918: Quick-Add spools with brand=NULL still match a Bambu RFID read.
|
|
|
|
|
+
|
|
|
|
|
+ Quick-Add doesn't require brand, so a user bulk-logging Bambu spools may
|
|
|
|
|
+ leave it empty. The matcher allows NULL brand because the alternative
|
|
|
|
|
+ (forcing every Quick-Add spool to be tagged "Bambu") is the exact
|
|
|
|
|
+ friction the auto-matcher exists to remove.
|
|
|
|
|
+ """
|
|
|
|
|
+ spool = Spool(
|
|
|
|
|
+ material="PLA",
|
|
|
|
|
+ subtype="Basic",
|
|
|
|
|
+ rgba="FFFFFFFF",
|
|
|
|
|
+ brand=None, # Quick-Add left brand blank
|
|
|
|
|
+ label_weight=1000,
|
|
|
|
|
+ core_weight=250,
|
|
|
|
|
+ )
|
|
|
|
|
+ db_session.add(spool)
|
|
|
|
|
+ await db_session.commit()
|
|
|
|
|
+
|
|
|
|
|
+ found = await find_matching_untagged_spool(db_session, SAMPLE_TRAY)
|
|
|
|
|
+ assert found is not None
|
|
|
|
|
+ assert found.id == spool.id
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_find_matching_untagged_spool_accepts_bambu_brand_variants(db_session):
|
|
|
|
|
+ """#918: Both 'Bambu' (form dropdown) and 'Bambu Lab' (catalog) match.
|
|
|
|
|
+
|
|
|
|
|
+ DEFAULT_BRANDS in the form lists 'Bambu'; the catalog uses 'Bambu Lab'.
|
|
|
|
|
+ Users can pick either. The fuzzy %bambu% LIKE handles both, plus
|
|
|
|
|
+ 'BambuLab', 'bambu lab', etc.
|
|
|
|
|
+ """
|
|
|
|
|
+ for brand_value in ("Bambu", "Bambu Lab", "BambuLab", "bambu lab"):
|
|
|
|
|
+ spool = Spool(
|
|
|
|
|
+ material="PLA",
|
|
|
|
|
+ subtype="Basic",
|
|
|
|
|
+ rgba="FFFFFFFF",
|
|
|
|
|
+ brand=brand_value,
|
|
|
|
|
+ label_weight=1000,
|
|
|
|
|
+ core_weight=250,
|
|
|
|
|
+ )
|
|
|
|
|
+ db_session.add(spool)
|
|
|
|
|
+ await db_session.commit()
|
|
|
|
|
+
|
|
|
|
|
+ found = await find_matching_untagged_spool(db_session, SAMPLE_TRAY)
|
|
|
|
|
+ assert found is not None, f"brand={brand_value!r} should match"
|
|
|
|
|
+ assert found.id == spool.id
|
|
|
|
|
+
|
|
|
|
|
+ # Clean up so the next iteration starts fresh.
|
|
|
|
|
+ await db_session.delete(spool)
|
|
|
|
|
+ await db_session.commit()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_find_matching_untagged_spool_null_subtype_with_null_brand(db_session):
|
|
|
|
|
+ """#918: Pure Quick-Add row (brand=NULL, subtype=NULL) matches.
|
|
|
|
|
+
|
|
|
|
|
+ This is the exact scenario from Arn0uDz's report: 20 spools logged via
|
|
|
|
|
+ Quick Add, then placed in the AMS one at a time. Before the fix every
|
|
|
|
|
+ insertion duplicated; after the fix the first matching row is reused.
|
|
|
|
|
+ """
|
|
|
|
|
+ spool = Spool(
|
|
|
|
|
+ material="PLA",
|
|
|
|
|
+ subtype=None,
|
|
|
|
|
+ rgba="FFFFFFFF",
|
|
|
|
|
+ brand=None,
|
|
|
|
|
+ label_weight=1000,
|
|
|
|
|
+ core_weight=250,
|
|
|
|
|
+ )
|
|
|
|
|
+ db_session.add(spool)
|
|
|
|
|
+ await db_session.commit()
|
|
|
|
|
+
|
|
|
|
|
+ found = await find_matching_untagged_spool(db_session, SAMPLE_TRAY)
|
|
|
|
|
+ assert found is not None
|
|
|
|
|
+ assert found.id == spool.id
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# -- link_tag_to_inventory_spool -------------------------------------------
|
|
# -- link_tag_to_inventory_spool -------------------------------------------
|
|
|
|
|
|
|
|
|
|
|