Files
TimeTracker/tests/test_models/test_purchase_order.py
T
Dries Peeters 4e2183caed fix(inventory): harden purchase-order creation and stock idempotency
Prevent 500s during first/parallel purchase-order creation by using collision-safe PO numbering, explicit validation, and reliable commit/error handling in web and API flows. Add regressions for purchase-order edge cases and invoice stock-reduction idempotency to catch adjacent inventory failures before release.
2026-03-26 14:52:06 +01:00

157 lines
5.1 KiB
Python

"""Tests for PurchaseOrder model"""
import pytest
pytestmark = [pytest.mark.unit, pytest.mark.models]
from decimal import Decimal
from datetime import date, datetime
from app import db
from app.models import PurchaseOrder, PurchaseOrderItem, Supplier, StockItem, Warehouse, User
@pytest.fixture
def test_user(db_session):
"""Create a test user"""
user = User(username="testuser", role="admin")
db_session.add(user)
db_session.commit()
return user
@pytest.fixture
def test_supplier(db_session, test_user):
"""Create a test supplier"""
supplier = Supplier(code="SUP-001", name="Test Supplier", created_by=test_user.id)
db_session.add(supplier)
db_session.commit()
return supplier
@pytest.fixture
def test_warehouse(db_session, test_user):
"""Create a test warehouse"""
warehouse = Warehouse(name="Test Warehouse", code="WH-001", created_by=test_user.id)
db_session.add(warehouse)
db_session.commit()
return warehouse
@pytest.fixture
def test_stock_item(db_session, test_user):
"""Create a test stock item"""
item = StockItem(sku="ITEM-001", name="Test Item", created_by=test_user.id, default_cost=Decimal("5.00"))
db_session.add(item)
db_session.commit()
return item
@pytest.fixture
def test_purchase_order(db_session, test_supplier, test_user):
"""Create a test purchase order"""
po = PurchaseOrder(
po_number="PO-TEST-001",
supplier_id=test_supplier.id,
order_date=date.today(),
created_by=test_user.id,
currency_code="EUR",
)
db_session.add(po)
db_session.commit()
return po
class TestPurchaseOrder:
"""Test PurchaseOrder model"""
def test_create_purchase_order(self, db_session, test_supplier, test_user):
"""Test creating a purchase order"""
po = PurchaseOrder(
po_number="PO-001",
supplier_id=test_supplier.id,
order_date=date.today(),
created_by=test_user.id,
currency_code="EUR",
)
db_session.add(po)
db_session.commit()
assert po.id is not None
assert po.po_number == "PO-001"
assert po.status == "draft"
assert po.supplier_id == test_supplier.id
def test_purchase_order_with_items(self, db_session, test_purchase_order, test_stock_item, test_warehouse):
"""Test purchase order with items"""
item = PurchaseOrderItem(
purchase_order_id=test_purchase_order.id,
description="Test Item",
quantity_ordered=Decimal("10.00"),
unit_cost=Decimal("5.00"),
stock_item_id=test_stock_item.id,
warehouse_id=test_warehouse.id,
)
db_session.add(item)
test_purchase_order.calculate_totals()
db_session.commit()
assert test_purchase_order.items.count() == 1
assert test_purchase_order.total_amount == Decimal("50.00")
def test_purchase_order_receive(self, db_session, test_purchase_order, test_stock_item, test_warehouse):
"""Test receiving a purchase order"""
# Add item to PO
item = PurchaseOrderItem(
purchase_order_id=test_purchase_order.id,
description="Test Item",
quantity_ordered=Decimal("10.00"),
unit_cost=Decimal("5.00"),
stock_item_id=test_stock_item.id,
warehouse_id=test_warehouse.id,
)
db_session.add(item)
test_purchase_order.calculate_totals()
db_session.commit()
# Receive the PO
test_purchase_order.mark_as_received(date.today())
db_session.commit()
assert test_purchase_order.status == "received"
assert test_purchase_order.received_date == date.today()
def test_purchase_order_cancel(self, db_session, test_purchase_order):
"""Test cancelling a purchase order"""
test_purchase_order.cancel()
db_session.commit()
assert test_purchase_order.status == "cancelled"
def test_purchase_order_to_dict(self, db_session, test_purchase_order):
"""Test purchase order to_dict method"""
data = test_purchase_order.to_dict()
assert data["po_number"] == "PO-TEST-001"
assert data["status"] == "draft"
assert "created_at" in data
assert "items" in data
def test_purchase_order_none_safe_normalization(self, db_session, test_supplier, test_user):
"""Model constructor should reject missing required string fields cleanly."""
with pytest.raises(ValueError):
PurchaseOrder(
po_number=None,
supplier_id=test_supplier.id,
order_date=date.today(),
created_by=test_user.id,
currency_code=None,
)
def test_purchase_order_item_description_required(self, db_session, test_purchase_order):
with pytest.raises(ValueError):
PurchaseOrderItem(
purchase_order_id=test_purchase_order.id,
description=" ",
quantity_ordered=Decimal("1.00"),
unit_cost=Decimal("1.00"),
)