from app.services.product_costing_service import ( ProductCostAssumptions, ProductCostInputItem, calculate_product_cost_item, ) def assumptions() -> ProductCostAssumptions: return ProductCostAssumptions( grading_per_kg=0.05, cracking_per_kg=0.03, process_costs={ "Bagging + Grading": 0.04, "Standard Bagging": 0.02, "PHF Horse Mixes": 0.01, "Peckish": 0.08, "Hay & Straw": 0.09, }, client_margins={ "Specialty": {"distributor_margin": 0.2, "wholesale_margin": 0.1}, "Peckish": {"distributor_margin": 0.25, "wholesale_margin": 0.15}, "Hay & Straw": {"distributor_margin": 0.3, "wholesale_margin": 0.2}, "Straight Grain": {"distributor_margin": 0.1, "wholesale_margin": 0.08}, "PHF Horse Mixes": {"distributor_margin": 0.18, "wholesale_margin": 0.12}, }, bag_costs={ "20kg_bag": 0.6, "bulka_bag": 22.0, "own_bag_credit": 0.2, "1_5kg_bagging": 0.35, "peckish_bag": 0.4, }, freight_costs={ "freight_per_pallet": 80.0, "peckish_freight_per_pallet": 96.0, "hay_straw_freight_per_pallet": 120.0, }, ) def item(**overrides) -> ProductCostInputItem: values = { "client_category": "Specialty", "product_name": "Pigeon Mix 20kg", "mix_product_name": "Pigeon Mix", "unit_type": "Standard", "own_bag": None, "unit_kg": 20.0, "items_per_pallet": 40, "bagging_process": "Bagging + Grading", "manual_distributor_margin": None, "manual_wholesale_margin": None, } values.update(overrides) return ProductCostInputItem(**values) def test_standard_product_uses_per_kg_components_unit_bag_freight_and_default_margins(): result = calculate_product_cost_item(item(), assumptions(), 0.5) assert result.grading_cost_per_kg == 0.05 assert result.bagging_cost_per_kg == 0.04 assert result.bag_cost_per_unit == 0.6 assert result.freight_cost_per_unit == 2.0 assert result.finished_product_delivered_cost == 14.4 assert result.distributor_price == 18.0 assert result.wholesale_price == 16.0 assert result.warnings == [] def test_bulka_uses_per_kg_delivered_formula_and_bulka_bag_and_freight_divided_by_unit_kg(): result = calculate_product_cost_item(item(unit_type="Bulka", unit_kg=1000, items_per_pallet=1), assumptions(), 0.5) assert result.bag_cost_per_unit == 0.022 assert result.freight_cost_per_unit == 0.08 assert result.finished_product_delivered_cost == 0.692 def test_per_unit_uses_per_kg_delivered_formula_but_standard_pallet_freight(): result = calculate_product_cost_item(item(unit_type="Per Unit", unit_kg=1, items_per_pallet=10), assumptions(), 0.5) assert result.freight_cost_per_unit == 8.0 assert result.finished_product_delivered_cost == 8.59 def test_peckish_uses_peckish_bag_freight_and_zero_grading(): result = calculate_product_cost_item( item(client_category="Peckish", bagging_process="Peckish", items_per_pallet=24), assumptions(), 0.5, ) assert result.grading_cost_per_kg == 0 assert result.bagging_cost_per_kg == 0.08 assert result.bag_cost_per_unit == 0.4 assert result.freight_cost_per_unit == 4.0 def test_hay_and_straw_uses_hay_freight_and_zero_grading(): result = calculate_product_cost_item( item(client_category="Hay & Straw", bagging_process="Hay & Straw", items_per_pallet=30), assumptions(), 0.5, ) assert result.grading_cost_per_kg == 0 assert result.freight_cost_per_unit == 4.0 def test_phf_horse_mixes_have_zero_grading(): result = calculate_product_cost_item( item(client_category="PHF Horse Mixes", bagging_process="PHF Horse Mixes"), assumptions(), 0.5, ) assert result.grading_cost_per_kg == 0 def test_own_bag_subtracts_credit_and_no_bag_sets_bag_cost_to_zero(): own_bag = calculate_product_cost_item(item(own_bag="Yes"), assumptions(), 0.5) no_bag = calculate_product_cost_item(item(own_bag="No Bag"), assumptions(), 0.5) assert own_bag.bag_cost_per_unit == 0.4 assert no_bag.bag_cost_per_unit == 0 def test_one_point_five_kg_branch_multiplies_pack_formula_by_eight(): result = calculate_product_cost_item(item(unit_type="1.5 kg", unit_kg=1.5), assumptions(), 0.5) assert result.bag_cost_per_unit == 0.35 assert result.finished_product_delivered_cost == 10.84 def test_cracked_product_adds_cracking_cost_and_manual_margins_override_defaults(): result = calculate_product_cost_item( item(product_name="Cracked Maize 20kg", manual_distributor_margin=0.1, manual_wholesale_margin=0.05), assumptions(), 0.5, ) assert result.cracking_cost_per_kg == 0.03 assert result.distributor_price == 16.6667 assert result.wholesale_price == 15.8 def test_straight_grain_bulka_wholesale_rounds_up_to_two_decimals(): result = calculate_product_cost_item( item(client_category="Straight Grain", unit_type="Bulka", unit_kg=1000, items_per_pallet=1), assumptions(), 0.5, ) assert result.wholesale_price == 0.76 def test_missing_lookup_and_invalid_inputs_generate_warnings_without_prices(): result = calculate_product_cost_item( item(unit_kg=None, items_per_pallet=0, manual_distributor_margin=1.2), assumptions(), None, ) assert "Missing mix/product cost lookup" in result.warnings assert "Missing unit kg" in result.warnings assert "Missing pallet quantity" in result.warnings assert "Invalid distributor margin" in result.warnings assert result.finished_product_delivered_cost is None assert result.distributor_price is None