96 lines
3.7 KiB
Python
96 lines
3.7 KiB
Python
|
|
from datetime import date
|
||
|
|
|
||
|
|
from fastapi.testclient import TestClient
|
||
|
|
from sqlalchemy import create_engine
|
||
|
|
from sqlalchemy.orm import Session, sessionmaker
|
||
|
|
|
||
|
|
from app.core.config import settings
|
||
|
|
from app.db.session import Base
|
||
|
|
from app.main import app
|
||
|
|
from app.models.assumption import FreightCostRule, PackagingCostRule, ProcessCostRule
|
||
|
|
from app.models.mix import Mix, MixIngredient
|
||
|
|
from app.models.product import Product
|
||
|
|
from app.models.raw_material import RawMaterial, RawMaterialPriceVersion
|
||
|
|
from app.services.costing_engine import calculate_mix_cost, calculate_product_cost, calculate_raw_material_cost
|
||
|
|
|
||
|
|
|
||
|
|
def build_session() -> Session:
|
||
|
|
engine = create_engine("sqlite:///:memory:")
|
||
|
|
Base.metadata.create_all(bind=engine)
|
||
|
|
session = sessionmaker(bind=engine, expire_on_commit=False)()
|
||
|
|
return session
|
||
|
|
|
||
|
|
|
||
|
|
def test_calculate_raw_material_cost():
|
||
|
|
raw_material = RawMaterial(name="Maize", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
||
|
|
price = RawMaterialPriceVersion(market_value=500, waste_percentage=0.02, effective_date=date(2026, 4, 1))
|
||
|
|
|
||
|
|
result = calculate_raw_material_cost(raw_material, price)
|
||
|
|
|
||
|
|
assert result.loss_cost == 10.0
|
||
|
|
assert result.cost_per_unit == 510.0
|
||
|
|
assert result.cost_per_kg == 0.51
|
||
|
|
|
||
|
|
|
||
|
|
def test_mix_and_product_cost_breakdown():
|
||
|
|
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)))
|
||
|
|
db.add_all([maize, barley])
|
||
|
|
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.commit()
|
||
|
|
|
||
|
|
mix_result = calculate_mix_cost(db, mix.id)
|
||
|
|
product_result = calculate_product_cost(db, product.id)
|
||
|
|
|
||
|
|
assert mix_result["total_mix_kg"] == 280
|
||
|
|
assert mix_result["mix_cost_per_kg"] == 0.5114
|
||
|
|
assert product_result["finished_product_delivered"] == 14.208
|
||
|
|
assert product_result["distributor_price"] == 18.3329
|
||
|
|
assert product_result["wholesale_price"] == 17.3268
|
||
|
|
|
||
|
|
|
||
|
|
def test_root_and_login_endpoints():
|
||
|
|
client = TestClient(app)
|
||
|
|
|
||
|
|
root_response = client.get("/")
|
||
|
|
assert root_response.status_code == 200
|
||
|
|
assert root_response.json()["endpoints"]["login"] == "/api/auth/login"
|
||
|
|
|
||
|
|
login_response = client.post(
|
||
|
|
"/api/auth/login",
|
||
|
|
json={"email": settings.operator_email, "password": settings.operator_password},
|
||
|
|
)
|
||
|
|
assert login_response.status_code == 200
|
||
|
|
assert login_response.json()["email"] == settings.operator_email
|