SLICE-5: products import spine — canonical CSV → preview → confirm (SD-0002 §7.2) #26
@@ -9,7 +9,8 @@ Deliberately DB-free: the catalog snapshot dataclasses are defined here and the
|
|||||||
repo layer builds them. Only fields present in the file's canonical fields{}
|
repo layer builds them. Only fields present in the file's canonical fields{}
|
||||||
participate in a comparison — an absent column is untouched, never a change
|
participate in a comparison — an absent column is untouched, never a change
|
||||||
(§6.5.1); a present-but-empty cell resolves to the field's CLEAR_DEFAULTS entry.
|
(§6.5.1); a present-but-empty cell resolves to the field's CLEAR_DEFAULTS entry.
|
||||||
Catalog variants/images absent from the file are likewise untouched (INV-10).
|
Catalog variants/images absent from the file are likewise untouched (INV-10);
|
||||||
|
file variants match catalog variants by their option-value combination (INV-13).
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ def compute_diff(catalog: dict[str, CatalogProduct], products: list[CanonicalPro
|
|||||||
)
|
)
|
||||||
summary[_SUMMARY_KEY[kind]] += 1
|
summary[_SUMMARY_KEY[kind]] += 1
|
||||||
fingerprint = hashlib.sha256(
|
fingerprint = hashlib.sha256(
|
||||||
json.dumps(records, sort_keys=True, separators=(",", ":"), default=str).encode()
|
json.dumps(records, sort_keys=True, separators=(",", ":")).encode()
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
return DiffResult(records=records, summary=summary, fingerprint=fingerprint)
|
return DiffResult(records=records, summary=summary, fingerprint=fingerprint)
|
||||||
|
|
||||||
@@ -140,11 +141,13 @@ def _update_detail(product: CanonicalProduct, current: CatalogProduct) -> dict:
|
|||||||
{"options": list(variant.options), "kind": "add", "set": _resolved_variant_fields(variant)}
|
{"options": list(variant.options), "kind": "add", "set": _resolved_variant_fields(variant)}
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
variant_changes = [
|
variant_changes = []
|
||||||
_change(f, match.fields.get(f), resolved)
|
for f, resolved in _resolved_fields(variant.fields).items():
|
||||||
for f, resolved in _resolved_fields(variant.fields).items()
|
# position lives on the catalog variant as an attribute, not in
|
||||||
if resolved != match.fields.get(f)
|
# fields{} — compare it explicitly (as images do for theirs).
|
||||||
]
|
before = match.position if f == "position" else match.fields.get(f)
|
||||||
|
if resolved != before:
|
||||||
|
variant_changes.append(_change(f, before, resolved))
|
||||||
if variant_changes:
|
if variant_changes:
|
||||||
variant_entries.append(
|
variant_entries.append(
|
||||||
{"options": list(variant.options), "kind": "update", "changes": variant_changes}
|
{"options": list(variant.options), "kind": "update", "changes": variant_changes}
|
||||||
|
|||||||
@@ -95,6 +95,24 @@ def test_new_option_combo_is_variant_add_existing_untouched():
|
|||||||
assert "add" in kinds
|
assert "add" in kinds
|
||||||
|
|
||||||
|
|
||||||
|
POSITION_HEADER = HEADER + ",Variant Position"
|
||||||
|
|
||||||
|
|
||||||
|
def test_matching_variant_position_column_classifies_unchanged():
|
||||||
|
# position is an attribute on CatalogVariant (not in fields{}); the compare
|
||||||
|
# must read it from there, not invent a before:None.
|
||||||
|
diff = compute_diff(_catalog_mug(), _canon(POSITION_HEADER, MUG_ROW + ",1"))
|
||||||
|
assert diff.records[0]["kind"] == "unchanged"
|
||||||
|
|
||||||
|
|
||||||
|
def test_changed_variant_position_reports_honest_before():
|
||||||
|
diff = compute_diff(_catalog_mug(), _canon(POSITION_HEADER, MUG_ROW + ",2"))
|
||||||
|
[rec] = diff.records
|
||||||
|
assert rec["kind"] == "update"
|
||||||
|
[ventry] = rec["detail"]["variants"]
|
||||||
|
assert {"field": "position", "before": 1, "after": 2} in ventry["changes"]
|
||||||
|
|
||||||
|
|
||||||
def test_error_product_classifies_error():
|
def test_error_product_classifies_error():
|
||||||
diff = compute_diff({}, _canon("Handle,Title,Variant Price", "mug,Mug,nope"))
|
diff = compute_diff({}, _canon("Handle,Title,Variant Price", "mug,Mug,nope"))
|
||||||
[rec] = diff.records
|
[rec] = diff.records
|
||||||
|
|||||||
Reference in New Issue
Block a user