woodshop/tests/test_inventory.py

80 lines
2.8 KiB
Python

"""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}