import pytest @pytest.mark.anyio async def test_inventory_list_returns_only_materials(client): resp = await client.get("/api/inventory") assert resp.status_code == 200 rows = resp.json() assert any(r["product_sku"] == "MAT-001" for r in rows) assert all(r["item_type"] == "material" for r in rows) @pytest.mark.anyio async def test_purchase_order_create_and_receive_flow(client): create_payload = { "supplier_id": 1, "expected_date": None, "remark": "po", "items": [{"product_id": 1, "quantity": 5, "unit_price": 9999.0, "remark": None}], } resp = await client.post("/api/purchase-orders", json=create_payload) assert resp.status_code == 201 order = resp.json() assert order["status"] == "draft" assert order["total_amount"] == 50.0 order_id = order["id"] detail = await client.get(f"/api/purchase-orders/{order_id}") assert detail.status_code == 200 item_id = detail.json()["items"][0]["id"] recv1 = await client.post( f"/api/purchase-orders/{order_id}/receive", json={"warehouse_id": 1, "items": [{"item_id": item_id, "receive_quantity": 3}], "remark": "r1"}, ) assert recv1.status_code == 200 assert recv1.json()["status"] == "partial_received" recv2 = await client.post( f"/api/purchase-orders/{order_id}/receive", json={"warehouse_id": 1, "items": [{"item_id": item_id, "receive_quantity": 2}], "remark": "r2"}, ) assert recv2.status_code == 200 assert recv2.json()["status"] == "received" @pytest.mark.anyio async def test_purchase_order_receive_overflow_rejected(client): create_payload = { "supplier_id": 1, "items": [{"product_id": 1, "quantity": 2}], } resp = await client.post("/api/purchase-orders", json=create_payload) assert resp.status_code == 201 order_id = resp.json()["id"] detail = await client.get(f"/api/purchase-orders/{order_id}") item_id = detail.json()["items"][0]["id"] recv = await client.post( f"/api/purchase-orders/{order_id}/receive", json={"warehouse_id": 1, "items": [{"item_id": item_id, "receive_quantity": 3}]}, ) assert recv.status_code == 400 @pytest.mark.anyio async def test_purchase_order_update_blocked_after_receive(client): create_payload = { "supplier_id": 1, "items": [{"product_id": 1, "quantity": 2}], } resp = await client.post("/api/purchase-orders", json=create_payload) assert resp.status_code == 201 order_id = resp.json()["id"] detail = await client.get(f"/api/purchase-orders/{order_id}") item_id = detail.json()["items"][0]["id"] recv = await client.post( f"/api/purchase-orders/{order_id}/receive", json={"warehouse_id": 1, "items": [{"item_id": item_id, "receive_quantity": 1}]}, ) assert recv.status_code == 200 update_payload = { "supplier_id": 1, "items": [{"product_id": 1, "quantity": 2}], } upd = await client.put(f"/api/purchase-orders/{order_id}", json=update_payload) assert upd.status_code == 400 @pytest.mark.anyio async def test_sales_order_bom_plan_and_auto_issue(client): create_payload = { "customer_id": 1, "items": [{"product_id": 2, "quantity": 1, "unit_price": 1000.0}], } resp = await client.post("/api/sales-orders", json=create_payload) assert resp.status_code == 201 order_id = resp.json()["id"] plan = await client.get(f"/api/sales-orders/{order_id}/production-plan") assert plan.status_code == 200 items = plan.json()["items"] assert len(items) == 1 assert items[0]["material_sku"] == "MAT-001" assert items[0]["required_quantity"] == 3 assert items[0]["shortage_quantity"] == 0 movements = await client.get("/api/stock-movements", params={"product_id": 1}) assert movements.status_code == 200 assert any(m["movement_type"] == "issue_to_production" for m in movements.json()) @pytest.mark.anyio async def test_sales_order_create_rejected_when_material_short(client): inv_list = await client.get("/api/inventory") inv_id = next(r["inventory_id"] for r in inv_list.json() if r["product_sku"] == "MAT-001") set_zero = await client.put( f"/api/inventory/{inv_id}", json={"quantity": 0, "locked_quantity": 0, "batch_number": None, "location": None}, ) assert set_zero.status_code == 200 create_payload = { "customer_id": 1, "items": [{"product_id": 2, "quantity": 1, "unit_price": 1000.0}], } resp = await client.post("/api/sales-orders", json=create_payload) assert resp.status_code == 400 @pytest.mark.anyio @pytest.mark.parametrize( "payload", [ {"customer_id": 1, "items": []}, {"customer_id": 1, "items": [{"product_id": 2, "quantity": 0, "unit_price": 1.0}]}, {"customer_id": 1, "items": [{"product_id": 2, "quantity": 1, "unit_price": -0.01}]}, ], ) async def test_sales_order_schema_validation(payload, client): resp = await client.post("/api/sales-orders", json=payload) assert resp.status_code == 422 @pytest.mark.anyio @pytest.mark.parametrize( "payload", [ {"customer_id": 1, "items": [{"product_id": None, "product_sku": None, "product_name": None, "quantity": 1, "unit_price": 1.0}]}, {"customer_id": 1, "items": [{"product_id": 99999, "quantity": 1, "unit_price": 1.0}]}, {"customer_id": 1, "items": [{"product_id": 1, "quantity": 1, "unit_price": 1.0}]}, ], ) async def test_sales_order_item_semantic_validation(payload, client): resp = await client.post("/api/sales-orders", json=payload) assert resp.status_code == 400 @pytest.mark.anyio @pytest.mark.parametrize( "payload", [ {"supplier_id": 1, "items": []}, {"supplier_id": 1, "items": [{"product_id": 1, "quantity": 0}]}, {"supplier_id": 99999, "items": [{"product_id": 1, "quantity": 1}]}, {"supplier_id": 1, "items": [{"product_id": 2, "quantity": 1}]}, ], ) async def test_purchase_order_validation(payload, client): resp = await client.post("/api/purchase-orders", json=payload) assert resp.status_code in {400, 422} @pytest.mark.anyio @pytest.mark.parametrize( "payload", [ {"status": "draft"}, {"status": "pending"}, {"status": "received"}, {"status": ""}, ], ) async def test_sales_order_status_enum_rejected(payload, client): create_payload = { "customer_id": 1, "items": [{"product_id": 2, "quantity": 1, "unit_price": 1000.0}], } resp = await client.post("/api/sales-orders", json=create_payload) assert resp.status_code == 201 order_id = resp.json()["id"] patch = await client.patch(f"/api/sales-orders/{order_id}/status", json=payload) assert patch.status_code == 400 @pytest.mark.anyio @pytest.mark.parametrize( "payload", [ {"warehouse_id": 1, "items": []}, {"warehouse_id": 1, "items": [{"item_id": 99999, "receive_quantity": 1}]}, {"warehouse_id": 99999, "items": [{"item_id": 1, "receive_quantity": 1}]}, {"warehouse_id": 1, "items": [{"item_id": 1, "receive_quantity": 0}]}, ], ) async def test_purchase_receive_validation(payload, client): create_payload = {"supplier_id": 1, "items": [{"product_id": 1, "quantity": 1}]} resp = await client.post("/api/purchase-orders", json=create_payload) assert resp.status_code == 201 order_id = resp.json()["id"] detail = await client.get(f"/api/purchase-orders/{order_id}") assert detail.status_code == 200 real_item_id = detail.json()["items"][0]["id"] normalized = payload.copy() if normalized.get("items"): normalized["items"] = [dict(normalized["items"][0])] if normalized["items"][0]["item_id"] == 1: normalized["items"][0]["item_id"] = real_item_id recv = await client.post(f"/api/purchase-orders/{order_id}/receive", json=normalized) assert recv.status_code in {400, 404, 422} @pytest.mark.anyio @pytest.mark.parametrize( "payload,expected", [ ({"product_id": 2, "warehouse_id": 1, "quantity": 1, "locked_quantity": 0}, 400), ({"product_id": 1, "warehouse_id": 1, "quantity": -1, "locked_quantity": 0}, 400), ({"product_id": 1, "warehouse_id": 1, "quantity": 1, "locked_quantity": 2}, 400), ({"product_id": 1, "warehouse_id": 99999, "quantity": 1, "locked_quantity": 0}, 404), ], ) async def test_inventory_create_validation(payload, expected, client): resp = await client.post("/api/inventory", json=payload) assert resp.status_code == expected @pytest.mark.anyio @pytest.mark.parametrize( "payload,expected", [ ({"product_id": 1, "warehouse_id": 1, "movement_type": "purchase_in", "quantity": 0}, 400), ({"product_id": 1, "warehouse_id": 1, "movement_type": "issue_to_production", "quantity": 999999}, 400), ({"product_id": 99999, "warehouse_id": 1, "movement_type": "purchase_in", "quantity": 1}, 404), ({"product_id": 1, "warehouse_id": 99999, "movement_type": "purchase_in", "quantity": 1}, 404), ], ) async def test_stock_movement_create_validation(payload, expected, client): resp = await client.post("/api/stock-movements", json=payload) assert resp.status_code == expected