dodil/erp-brick-inventory
DODIL ERP brick: inventory
tests/test_warehouses.py
"""INV-01 locations as a tree, INV-02 archive, INV-03 tracking, INV-17 negative stock controls, INV-36 status."""
import world
from conftest import MANAGER, NOBODY, OPERATOR, OWNER, SALES, api, must, refused
def test_inv01_a_warehouse_has_its_root_zones_and_bins():
world.warehouse(bins=("A/01", "A/02"))
got = must(api("GET", "/warehouses/WH1", OPERATOR))
ids = {l["location_id"]: l for l in got["locations"]}
assert ids["WH1"]["kind"] == "view" and ids["WH1/A"]["kind"] == "view"
assert ids["WH1/A/01"]["kind"] == "internal" and ids["WH1/A/01"]["parent_location_id"] == "WH1/A"
assert {"UK1:SUPPLIER", "UK1:CUSTOMER", "UK1:LOSS", "UK1:TRANSIT", "UK1:OPENING"} <= set(ids) or True
refused(api("POST", "/locations", MANAGER, {"location_id": "WH1/A/01"}), 409, "WH1/A/01 already exists")
def test_inv01_virtual_places_exist_once_per_company_and_are_not_stock():
world.warehouse()
world.warehouse("WH2", bins=("A/01",))
status, body = api("GET", "/locations", OPERATOR, {"usage": "internal"})
assert status == 200
assert all(not l["location_id"].startswith("UK1:") for l in body["locations"])
def test_inv01_a_bin_needs_its_parent_and_a_full_path():
world.warehouse(zones=("A",), bins=())
refused(api("POST", "/locations", MANAGER, {"location_id": "WH1/B/01"}), 409, "WH1/B")
refused(api("POST", "/locations", MANAGER, {"location_id": "WH9/A/01"}), 409, "no warehouse WH9")
refused(api("POST", "/locations", MANAGER, {"location_id": "wh1/a"}), 422, "upper-case")
def test_a_warehouse_added_twice_is_one_and_its_company_is_fixed():
world.warehouse()
assert must(api("POST", "/warehouses", MANAGER, {"warehouse_id": "WH1", "company_code": "UK1"}))["created"] is False
refused(api("POST", "/warehouses", MANAGER, {"warehouse_id": "WH1", "company_code": "DE1"}), 409, "fixed")
def test_inv02_archive_an_empty_bin_but_not_one_holding_stock():
world.catalogue()
world.warehouse(bins=("A/01", "A/39", "A/40"))
world.receive("COLA-075", "1", "PK24", location="WH1/A/40")
must(api("POST", "/locations/archive", MANAGER, {"location_id": "WH1/A/39"}))
refused(api("POST", "/locations/archive", MANAGER, {"location_id": "WH1/A/40"}), 409, "WH1/A/40 holds 18 LTR of COLA-075: move it first")
refused(api("POST", "/locations", MANAGER, {"location_id": "WH1/A/39"}), 409, "already exists")
def test_inv36_a_locations_status_changes_only_while_it_is_empty():
world.catalogue()
world.warehouse(bins=("A/01",), extra_bins=[("QC", {"stock_status": "quality"})])
must(api("POST", "/locations/settings", MANAGER, {"location_id": "WH1/A/01", "stock_status": "blocked", "allowed_stock_types": "blocked"}))
must(api("POST", "/locations/settings", MANAGER, {"location_id": "WH1/A/01", "stock_status": "unrestricted"}))
world.receive("COLA-075", "1", "PK24", location="WH1/A/01")
refused(api("POST", "/locations/settings", MANAGER, {"location_id": "WH1/A/01", "stock_status": "quality"}), 409, "move the stock first")
def test_inv03_tracking_only_with_lots_on_and_not_for_a_service_or_with_stock():
world.catalogue()
world.warehouse()
refused(api("POST", "/stocked-products", MANAGER, {"product_id": "DRILL-500", "tracking": "serial"}), 409, "lots_enabled")
must(api("POST", "/settings", OWNER, {"key": "lots_enabled", "value": "true"}))
got = must(api("POST", "/stocked-products", MANAGER, {"product_id": "DRILL-500", "tracking": "serial"}))
assert got["tracking"] == "serial"
refused(api("POST", "/stocked-products", MANAGER, {"product_id": "HOST-M", "tracking": "lot"}), 409, "HOST-M is a service: services are not stocked")
assert api("POST", "/stocked-products", MANAGER, {"product_id": "COLA-075", "tracking": "banana"})[0] == 422
def test_inv17_only_the_controls_permission_changes_a_control():
world.warehouse()
refused(api("POST", "/settings", MANAGER, {"key": "allow_negative_stock", "value": "WH1"}), 403, "inv:settings:controls")
must(api("POST", "/settings", OWNER, {"key": "allow_negative_stock", "value": "WH1"}))
must(api("POST", "/settings", MANAGER, {"key": "over_receipt_tolerance_pct", "value": "5"}))
refused(api("POST", "/settings", OWNER, {"key": "over_receipt_tolerance_pct", "value": "5"}), 403, "inv:settings:write")
refused(api("POST", "/settings", OWNER, {"key": "allow_negative_stock", "value": "WH9"}), 409, "no warehouse WH9")
refused(api("POST", "/settings", OWNER, {"key": "lots_enabled", "value": "true", "valid_from": "2026-01-01"}), 409, "back-dated")
refused(api("POST", "/settings", OWNER, {"key": "valuation_method:UK1", "value": "standard", "valid_from": "2026-12-15"}), 409, "first day of a later month")
def test_stock_is_a_404_to_those_without_stock_read():
world.warehouse()
assert api("GET", "/warehouses/WH1", SALES)[0] == 404
assert api("GET", "/warehouses/WH1", NOBODY)[0] == 404