tweaks
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from datetime import date
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import create_engine, inspect, text
|
||||
from sqlalchemy import create_engine, inspect, select, text
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
@@ -13,7 +13,7 @@ from app.models.assumption import FreightCostRule, PackagingCostRule, ProcessCos
|
||||
from app.models.client_access import ClientAccessAuditEvent, ClientAccount, ClientFeatureAccess, ClientUser
|
||||
from app.schemas.mix_calculator import MixCalculatorSessionCreate
|
||||
from app.models.mix import Mix, MixIngredient
|
||||
from app.models.product import Product
|
||||
from app.models.product import Product, ProductIngredient
|
||||
from app.models.raw_material import RawMaterial, RawMaterialPriceVersion
|
||||
from app.services.client_access_service import build_client_access_export, ensure_user_module_permissions, serialize_client_account
|
||||
from app.services.costing_engine import calculate_mix_cost, calculate_product_cost, calculate_raw_material_cost, serialize_raw_material
|
||||
@@ -97,12 +97,13 @@ def test_mix_and_product_cost_breakdown():
|
||||
assert product_result["wholesale_price"] == 17.3268
|
||||
|
||||
|
||||
def test_mix_calculator_preview_scales_saved_mix_and_warns_on_fractional_bags():
|
||||
def test_mix_calculator_preview_prefers_product_specific_ingredients_and_warns_on_fractional_bags():
|
||||
db = build_session()
|
||||
|
||||
maize = RawMaterial(name="Maize", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
||||
barley = RawMaterial(name="Barley", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
||||
db.add_all([maize, barley])
|
||||
wheat = RawMaterial(name="Wheat", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
||||
db.add_all([maize, barley, wheat])
|
||||
db.flush()
|
||||
|
||||
mix = Mix(tenant_id="specialty-feeds", client_name="Specialty Feeds", name="Pigeon Mix", status="active", version=1)
|
||||
@@ -128,6 +129,25 @@ def test_mix_calculator_preview_scales_saved_mix_and_warns_on_fractional_bags():
|
||||
bagging_process="standard_bagging",
|
||||
)
|
||||
db.add(product)
|
||||
db.flush()
|
||||
db.add_all(
|
||||
[
|
||||
ProductIngredient(
|
||||
tenant_id="specialty-feeds",
|
||||
product_id=product.id,
|
||||
raw_material_id=maize.id,
|
||||
quantity_kg=300,
|
||||
sort_order=1,
|
||||
),
|
||||
ProductIngredient(
|
||||
tenant_id="specialty-feeds",
|
||||
product_id=product.id,
|
||||
raw_material_id=wheat.id,
|
||||
quantity_kg=200,
|
||||
sort_order=2,
|
||||
),
|
||||
]
|
||||
)
|
||||
db.commit()
|
||||
|
||||
preview = calculate_mix_calculator_preview(
|
||||
@@ -145,8 +165,9 @@ def test_mix_calculator_preview_scales_saved_mix_and_warns_on_fractional_bags():
|
||||
|
||||
assert preview["batch_size_kg"] == 550
|
||||
assert preview["total_bags"] == 27.5
|
||||
assert preview["lines"][0]["required_kg"] == 353.5714
|
||||
assert preview["lines"][1]["required_kg"] == 196.4286
|
||||
assert [line["raw_material_name"] for line in preview["lines"]] == ["Maize", "Wheat"]
|
||||
assert preview["lines"][0]["required_kg"] == 330
|
||||
assert preview["lines"][1]["required_kg"] == 220
|
||||
assert len(preview["warnings"]) == 1
|
||||
assert "not a whole-bag quantity" in preview["warnings"][0]
|
||||
|
||||
@@ -155,7 +176,8 @@ def test_mix_calculator_options_hide_invisible_products_and_clients():
|
||||
db = build_session()
|
||||
|
||||
maize = RawMaterial(name="Maize", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
||||
db.add(maize)
|
||||
barley = RawMaterial(name="Barley", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
||||
db.add_all([maize, barley])
|
||||
db.flush()
|
||||
|
||||
visible_mix = Mix(tenant_id="hunter-premium-produce", client_name="Peckish", name="Visible Mix", status="active", version=1)
|
||||
@@ -197,12 +219,89 @@ def test_mix_calculator_options_hide_invisible_products_and_clients():
|
||||
),
|
||||
]
|
||||
)
|
||||
db.flush()
|
||||
|
||||
visible_product = db.scalar(select(Product).where(Product.name == "Visible Product"))
|
||||
assert visible_product is not None
|
||||
db.add_all(
|
||||
[
|
||||
ProductIngredient(
|
||||
tenant_id="hunter-premium-produce",
|
||||
product_id=visible_product.id,
|
||||
raw_material_id=maize.id,
|
||||
quantity_kg=12,
|
||||
sort_order=1,
|
||||
),
|
||||
ProductIngredient(
|
||||
tenant_id="hunter-premium-produce",
|
||||
product_id=visible_product.id,
|
||||
raw_material_id=barley.id,
|
||||
quantity_kg=8,
|
||||
sort_order=2,
|
||||
),
|
||||
]
|
||||
)
|
||||
db.commit()
|
||||
|
||||
options = build_mix_calculator_options(db, tenant_id="hunter-premium-produce")
|
||||
|
||||
assert options["clients"] == ["Peckish"]
|
||||
assert [product["product_name"] for product in options["products"]] == ["Visible Product"]
|
||||
assert options["products"][0]["mix_total_kg"] == 20
|
||||
|
||||
|
||||
def test_calculate_product_cost_prefers_product_specific_ingredients():
|
||||
db = build_session()
|
||||
|
||||
maize = RawMaterial(name="Maize", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
||||
maize.price_versions.append(RawMaterialPriceVersion(market_value=520, waste_percentage=0.02, effective_date=date(2026, 4, 1)))
|
||||
barley = RawMaterial(name="Barley", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
||||
barley.price_versions.append(RawMaterialPriceVersion(market_value=470, waste_percentage=0.015, effective_date=date(2026, 4, 1)))
|
||||
wheat = RawMaterial(name="Wheat", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
||||
wheat.price_versions.append(RawMaterialPriceVersion(market_value=600, waste_percentage=0.01, effective_date=date(2026, 4, 1)))
|
||||
db.add_all([maize, barley, wheat])
|
||||
db.flush()
|
||||
|
||||
mix = Mix(client_name="Specialty Feeds", name="Pigeon Mix", status="active", version=1)
|
||||
db.add(mix)
|
||||
db.flush()
|
||||
db.add_all(
|
||||
[
|
||||
MixIngredient(mix_id=mix.id, raw_material_id=maize.id, quantity_kg=180),
|
||||
MixIngredient(mix_id=mix.id, raw_material_id=barley.id, quantity_kg=100),
|
||||
]
|
||||
)
|
||||
db.add(ProcessCostRule(process_name="standard_bagging", grading_cost=0.055, bagging_cost=0.04, cracking_cost=0.0))
|
||||
db.add(PackagingCostRule(sale_type="standard", unit_of_measure="20kg bag", own_bag=False, bag_cost=0.63))
|
||||
db.add(FreightCostRule(sale_type="standard", unit_of_measure="20kg bag", cost_per_unit=1.45))
|
||||
db.flush()
|
||||
product = Product(
|
||||
client_name="Specialty Feeds",
|
||||
name="Specialty Pigeon Breeder 20kg",
|
||||
mix_id=mix.id,
|
||||
sale_type="standard",
|
||||
own_bag=False,
|
||||
unit_of_measure="20kg bag",
|
||||
items_per_pallet=50,
|
||||
bagging_process="standard_bagging",
|
||||
distributor_margin=0.225,
|
||||
wholesale_margin=0.18,
|
||||
)
|
||||
db.add(product)
|
||||
db.flush()
|
||||
db.add_all(
|
||||
[
|
||||
ProductIngredient(product_id=product.id, raw_material_id=maize.id, quantity_kg=300, sort_order=1),
|
||||
ProductIngredient(product_id=product.id, raw_material_id=wheat.id, quantity_kg=200, sort_order=2),
|
||||
]
|
||||
)
|
||||
db.commit()
|
||||
|
||||
product_result = calculate_product_cost(db, product.id)
|
||||
|
||||
assert product_result["finished_product_delivered"] == 15.192
|
||||
assert product_result["distributor_price"] == 19.6026
|
||||
assert product_result["wholesale_price"] == 18.5268
|
||||
|
||||
|
||||
def test_sync_product_visibility_hides_configured_clients():
|
||||
|
||||
Reference in New Issue
Block a user