"""Phase 5: event-sourced inventory ledger (no GUI).""" from woodshop.cutplan import build_cut_plan from woodshop.inventory import Ledger, plan_consumption from woodshop.scene import Scene def test_purchase_then_consume_on_hand(): led = Ledger() led.purchase("2x4", 5) led.purchase("ply-3/4", 2) assert led.on_hand() == {"2x4": 5, "ply-3/4": 2} led.record_build("table", 1, consumed={"2x4": 2}, offcuts=[]) assert led.on_hand()["2x4"] == 3 def test_adjustment_corrects_count(): led = Ledger() led.purchase("2x4", 5) led.adjust("2x4", -1, reason="broke one") assert led.on_hand()["2x4"] == 4 def test_record_build_keeps_and_discards_offcuts(): led = Ledger() offcuts = [{"stock": "2x4", "length_in": 20, "width_in": 3.5, "is_sheet": False}, {"stock": "2x4", "length_in": 8, "width_in": 3.5, "is_sheet": False}] bid = led.record_build("shelf", 1, consumed={"2x4": 1}, offcuts=offcuts, keep_ids={f"{ 'b1'}-o1"}, fates={"b1-o2": "burned"}) bin_ = led.offcut_bin() assert len(bin_) == 1 and bin_[0].id == "b1-o1" # kept the 20" stats = led.stats() assert stats["offcuts_kept"] == 1 and stats["offcuts_burned"] == 1 def test_consume_offcut_removes_it_from_bin(): led = Ledger() led.record_build("a", 1, consumed={}, offcuts=[ {"stock": "2x4", "length_in": 20, "width_in": 3.5, "is_sheet": False}]) oid = led.offcut_bin()[0].id led.consume_offcut(oid) assert led.offcut_bin() == [] def test_plan_consumption_from_cutplan(): s = Scene() s.place("2x4", 60) # leaves a reusable ~35" offcut on a 96" stick plan = build_cut_plan(s) consumed, offcuts = plan_consumption(plan) assert consumed.get("2x4") == 1 assert offcuts and offcuts[0]["stock"] == "2x4" and offcuts[0]["length_in"] > 12 def test_available_stock_combines_full_and_offcuts(): led = Ledger() led.purchase("2x4", 2) led.record_build("a", 1, consumed={}, offcuts=[ {"stock": "2x4", "length_in": 20, "width_in": 3.5, "is_sheet": False}]) avail = led.available_stock() assert sum(1 for p in avail if p.is_offcut) == 1 assert sum(1 for p in avail if not p.is_offcut and p.stock == "2x4") == 2 def test_ledger_save_load_roundtrip(tmp_path, monkeypatch): monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path)) led = Ledger() led.purchase("2x4", 3, price=3.98) led.save() again = Ledger.load() assert again.on_hand()["2x4"] == 3 assert again.stats()["spent"] == round(3 * 3.98, 2) def test_stats_aggregate_across_projects(): led = Ledger() led.record_build("table", 2, consumed={}, offcuts=[]) led.record_build("shelf", 1, consumed={}, offcuts=[]) st = led.stats() assert st["units_built"] == 3 and st["builds"] == 2 assert st["by_project"] == {"table": 2, "shelf": 1}