Files
data-entry-app/backend/tests/test_throughput.py
T

240 lines
7.4 KiB
Python
Raw Normal View History

2026-05-31 20:19:44 +12:00
from datetime import date
from io import BytesIO
from openpyxl import Workbook
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session, sessionmaker
from app.db.session import Base
from app.models.mix import Mix
from app.models.product import Product
from app.models.raw_material import RawMaterial
from app.models.throughput import ProductionThroughput, ThroughputProduct
from app.seed import seed_throughput_products_from_costing
from app.services.throughput_service import (
calculate_kg,
import_names_sheet,
import_production_sheet,
normalise_staff_name,
qa_passed,
)
def _session() -> Session:
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(bind=engine)
return sessionmaker(bind=engine, expire_on_commit=False)()
def _costing_mix(db: Session, tenant_id: str = "hunter-premium-produce") -> Mix:
raw_material = RawMaterial(
tenant_id=tenant_id,
name="Maize",
unit_of_measure="tonne",
kg_per_unit=1000,
status="active",
)
db.add(raw_material)
db.flush()
mix = Mix(tenant_id=tenant_id, client_name="Hunter", name="Maize Mix")
db.add(mix)
db.flush()
return mix
def test_calculate_kg_bags():
assert calculate_kg(10, "bags", 20) == 200.0
def test_calculate_kg_kg_ignores_bag_size():
assert calculate_kg(550, "kg", None) == 550.0
assert calculate_kg(550, "kg", 20) == 550.0
def test_calculate_kg_zero_quantity():
assert calculate_kg(0, "bags", 20) == 0.0
assert calculate_kg(None, "bags", 20) == 0.0
def test_staff_name_normalisation():
assert normalise_staff_name(" Jake ") == "Jake"
assert normalise_staff_name("jake smith") == "jake smith"
assert normalise_staff_name("") is None
assert normalise_staff_name(None) is None
def test_qa_passed_flag():
entry = ProductionThroughput(
production_date=date(2026, 1, 1),
product_name_snapshot="X",
quantity=1,
quantity_type="bags",
scales_checked=True,
label_correct=True,
bag_sealed=True,
pallet_good_condition=True,
)
assert qa_passed(entry) is True
entry.bag_sealed = False
assert qa_passed(entry) is False
def _make_workbook() -> BytesIO:
wb = Workbook()
names = wb.active
names.title = "Names"
names.append(["Name", "Item ID"])
names.append(["Whole Wheat 20kg", 1001])
names.append(["Bulka Maize", 1002])
production = wb.create_sheet("Production")
production.append(["#VALUE!", "Operations Throughput"])
production.append([None] * 8 + ["TEST WEIGHT"])
production.append([
"DATE", "GRAIN", "BAG SIZE", "SCALES", "LABEL", "SEALED", "PALLET",
"BOX", 1, 2, 3, 4, 5, "QTY", "STAFF", "NOTES",
])
production.append([date(2026, 4, 1), "Whole Wheat 20kg", 20, True, True, True, True, None, None, None, None, None, None, 100, " Jake ", None])
production.append([date(2026, 4, 1), "Bulka Maize", None, True, True, False, True, "B7", None, None, None, None, None, 1500, "Alex", "ok"])
production.append([date(2026, 4, 2), "Whole Wheat 20kg", 20, False, True, True, True, None, None, None, None, None, None, 50, "Jake", None])
buf = BytesIO()
wb.save(buf)
buf.seek(0)
return buf
def test_import_names_and_production():
from openpyxl import load_workbook
db = _session()
wb = load_workbook(_make_workbook(), data_only=True)
created, _ = import_names_sheet(db, wb, "test-tenant")
assert created == 2
imported, skipped = import_production_sheet(db, wb, "test-tenant")
assert imported == 3
assert skipped == 0
entries = db.scalars(select(ProductionThroughput).order_by(ProductionThroughput.id)).all()
bags_entry = entries[0]
assert bags_entry.quantity_type == "bags"
assert bags_entry.calculated_kg == 2000.0
assert bags_entry.staff_name == "Jake" # whitespace trimmed
bulka_entry = entries[1]
assert bulka_entry.quantity_type == "kg"
assert bulka_entry.calculated_kg == 1500.0
assert qa_passed(bulka_entry) is False # bag_sealed was False
# Product master should have absorbed default_bag_size for the wheat product
wheat = db.scalar(
select(ThroughputProduct).where(ThroughputProduct.name == "Whole Wheat 20kg")
)
assert wheat is not None
assert wheat.default_bag_size == 20
def test_product_name_snapshot_preserved_when_product_renamed():
db = _session()
product = ThroughputProduct(tenant_id="t", name="Original Name", default_bag_size=20)
db.add(product)
db.flush()
entry = ProductionThroughput(
tenant_id="t",
production_date=date(2026, 4, 1),
product_id=product.id,
product_name_snapshot=product.name,
bag_size=20,
quantity=10,
quantity_type="bags",
calculated_kg=200,
)
db.add(entry)
db.flush()
product.name = "Renamed Product"
db.flush()
reloaded = db.scalar(select(ProductionThroughput).where(ProductionThroughput.id == entry.id))
assert reloaded.product_name_snapshot == "Original Name"
def test_seed_throughput_products_from_costing_products():
db = _session()
mix = _costing_mix(db)
db.add_all(
[
Product(
tenant_id="hunter-premium-produce",
client_name="Hunter",
item_id="1001",
name="Whole Wheat 20kg",
mix_id=mix.id,
sale_type="standard",
unit_of_measure="20kg bag",
visible=True,
),
Product(
tenant_id="hunter-premium-produce",
client_name="Hunter",
item_id="1002",
name="Bulka Maize",
mix_id=mix.id,
sale_type="bulka",
unit_of_measure="tonne",
visible=False,
),
]
)
db.flush()
report = seed_throughput_products_from_costing(db)
assert report == {"created": 2, "updated": 0, "skipped": 0}
products = db.scalars(select(ThroughputProduct).order_by(ThroughputProduct.item_id)).all()
assert [product.name for product in products] == ["Whole Wheat 20kg", "Bulka Maize"]
assert products[0].default_bag_size == 20
assert products[0].is_bulka_default is False
assert products[0].active is True
assert products[1].default_bag_size is None
assert products[1].is_bulka_default is True
assert products[1].active is False
def test_seed_throughput_products_from_costing_updates_existing_by_item_id():
db = _session()
mix = _costing_mix(db)
db.add(
Product(
tenant_id="hunter-premium-produce",
client_name="Hunter",
item_id="1001",
name="Updated Wheat 25kg",
mix_id=mix.id,
sale_type="standard",
unit_of_measure="25kg bag",
visible=True,
)
)
db.add(
ThroughputProduct(
tenant_id="hunter-premium-produce",
item_id="1001",
name="Old Wheat",
default_bag_size=20,
active=False,
notes="Seeded from costing products",
)
)
db.flush()
report = seed_throughput_products_from_costing(db)
assert report == {"created": 0, "updated": 1, "skipped": 0}
products = db.scalars(select(ThroughputProduct)).all()
assert len(products) == 1
assert products[0].name == "Updated Wheat 25kg"
assert products[0].default_bag_size == 25
assert products[0].active is True