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[0].client_name == "Hunter" assert products[1].default_bag_size is None assert products[1].is_bulka_default is True # Every costing SKU is selectable in the throughput picker, even hidden ones. assert products[1].active is True assert products[1].client_name == "Hunter" 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