dingdanquanliucheng/backend/tests/test_orders.py
2026-06-19 23:03:14 +08:00

398 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""ORD-001 ~ ORD-022: 订单录入、提交、审批测试。
覆盖功能点:
- 订单创建(草稿、新客户自动入库、利润计算)
- 订单提交审核(成功、重复提交)
- 订单审批(通过、退回)
- 订单下发(生成文本、确认下发)
- 订单详情与字段权限
- 订单编辑
- 订单状态流转完整性
"""
from __future__ import annotations
import pytest
@pytest.mark.order
@pytest.mark.p0
class TestOrderCreation:
"""ORD-001 ~ ORD-006: 订单创建测试。"""
def test_create_draft_order(self, client, salesman_headers, make_product):
"""ORD-001: 创建草稿订单成功。"""
product = make_product()
resp = client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "新客户张三",
"customer_mobile": "13800001234",
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": product.specification,
"unit": product.unit,
"quantity": 10,
"sale_price": 100,
"cost_price": 60,
}],
})
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
# API 创建订单后自动提交审批,初始状态为 pending_approve
assert data["data"]["order_status"] == "pending_approve"
assert data["data"]["order_no"] is not None
def test_new_customer_auto_created(self, client, salesman_headers, make_product, db_session):
"""ORD-002: 新客户自动入库。"""
from backend.app.models.business import Customer
product = make_product()
client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "自动入库客户",
"customer_mobile": "13800009999",
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": product.specification,
"unit": product.unit,
"quantity": 5,
"sale_price": 100,
"cost_price": 60,
}],
})
customer = db_session.query(Customer).filter(
Customer.mobile == "13800009999"
).first()
assert customer is not None
assert customer.customer_name == "自动入库客户"
def test_profit_calculation(self, client, salesman_headers, make_product):
"""ORD-003: 利润计算验证。"""
product = make_product(cost_price=60, sale_price=100)
resp = client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "利润测试客户",
"customer_mobile": "13800007777",
"rebate_total": 5,
"freight_total": 10,
"tax_total": 3,
"other_fee_total": 2,
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": "100x100cm",
"unit": "",
"quantity": 1,
"sale_price": 100,
"cost_price": 60,
"rebate_amount": 5,
"freight_amount": 10,
"tax_amount": 3,
"other_fee_amount": 2,
}],
})
assert resp.status_code == 200
data = resp.json()["data"]
# 利润 = 100 - 60 - 5 - 10 - 3 - 2 = 20
assert float(data["profit_total"]) == 20
# 利润率 = 20 / 100 * 100 = 20%
assert float(data["profit_rate"]) == 20
def test_create_order_no_items(self, client, salesman_headers):
"""ORD-005: 明细为空应返回 40001。"""
resp = client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "空明细客户",
"customer_mobile": "13800006666",
"items": [],
})
assert resp.status_code == 400
data = resp.json()
assert data["code"] == 40001
def test_create_order_zero_quantity(self, client, salesman_headers, make_product):
"""ORD-006: 数量为 0 应返回验证错误。
注意: Pydantic Field(gt=0) 校验返回 422自定义校验返回 400
"""
product = make_product()
resp = client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "零数量客户",
"customer_mobile": "13800005555",
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": product.specification,
"unit": product.unit,
"quantity": 0,
"sale_price": 100,
"cost_price": 60,
}],
})
assert resp.status_code in (400, 422)
def test_disabled_product_cannot_order(self, client, salesman_headers, make_product):
"""ORD-021: 禁用产品不能创建订单。"""
product = make_product(status=0)
resp = client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "禁用产品客户",
"customer_mobile": "13800005555",
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": product.specification,
"unit": product.unit,
"quantity": 1,
"sale_price": 100,
"cost_price": 60,
}],
})
# 应被拒绝或有警告
assert resp.status_code in (400, 200)
@pytest.mark.order
@pytest.mark.p0
class TestOrderSubmit:
"""ORD-007 ~ ORD-008: 订单提交审核测试。"""
def _create_order(self, client, salesman_headers, make_product):
"""辅助方法:创建订单并返回 order_id。"""
product = make_product()
resp = client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "提交测试客户",
"customer_mobile": "13800004444",
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": "100x100cm",
"unit": "",
"quantity": 10,
"sale_price": 100,
"cost_price": 60,
}],
})
assert resp.status_code == 200, f"创建订单失败: {resp.json()}"
return resp.json()["data"]["order_id"]
def test_submit_order_success(self, client, salesman_headers, make_product):
"""ORD-007: 创建订单后自动提交审核,状态为 pending_approve。"""
order_id = self._create_order(client, salesman_headers, make_product)
# API 创建订单时自动提交审批,无需再次 submit
resp = client.get(f"/api/orders/{order_id}", headers=salesman_headers)
assert resp.status_code == 200
data = resp.json()
assert data["data"]["order_status"] == "pending_approve"
def test_double_submit(self, client, salesman_headers, manager_headers, make_product):
"""ORD-008: 对已审批通过的订单重复提交应返回错误。"""
order_id = self._create_order(client, salesman_headers, make_product)
# 订单创建时已是 pending_approve用经理权限审批通过
client.post(f"/api/orders/{order_id}/approve", headers=manager_headers, json={
"approve_result": "pass",
})
# 审批通过后尝试再次提交
resp = client.post(f"/api/orders/{order_id}/submit", headers=salesman_headers)
assert resp.status_code in (400, 409)
def test_list_orders(self, client, salesman_headers):
"""ORD-014: 订单列表查询。"""
resp = client.get("/api/orders", headers=salesman_headers)
assert resp.status_code == 200
data = resp.json()
assert "total" in data["data"]
assert "list" in data["data"]
@pytest.mark.order
@pytest.mark.p0
class TestOrderApproval:
"""ORD-009 ~ ORD-010: 订单审批测试。"""
def _create_and_submit(self, client, salesman_headers, make_product):
"""辅助方法:创建订单并提交审核。"""
product = make_product()
create_resp = client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "审批测试客户",
"customer_mobile": "13800002222",
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": "100x100cm",
"unit": "",
"quantity": 10,
"sale_price": 100,
"cost_price": 60,
}],
})
order_id = create_resp.json()["data"]["order_id"]
client.post(f"/api/orders/{order_id}/submit", headers=salesman_headers)
return order_id
def test_approve_order_success(self, client, salesman_headers, manager_headers, make_product):
"""ORD-009: 管理层审批通过。"""
order_id = self._create_and_submit(client, salesman_headers, make_product)
resp = client.post(f"/api/orders/{order_id}/approve", headers=manager_headers, json={
"approve_result": "pass",
"approve_opinion": "同意",
})
assert resp.status_code == 200
data = resp.json()
assert data["data"]["order_status"] == "approved"
def test_reject_order_success(self, client, salesman_headers, manager_headers, make_product):
"""ORD-010: 管理层审批退回。"""
order_id = self._create_and_submit(client, salesman_headers, make_product)
resp = client.post(f"/api/orders/{order_id}/approve", headers=manager_headers, json={
"approve_result": "reject",
"approve_opinion": "价格需要重新确认",
})
assert resp.status_code == 200
data = resp.json()
assert data["data"]["order_status"] == "rejected"
def test_approve_non_pending_order(self, client, manager_headers, make_order):
"""非待审核状态订单不能审批。"""
order = make_order(order_status="draft")
resp = client.post(f"/api/orders/{order.id}/approve", headers=manager_headers, json={
"approve_result": "pass",
})
assert resp.status_code in (400, 409)
@pytest.mark.order
@pytest.mark.p0
class TestOrderSupplierText:
"""ORD-011 ~ ORD-013: 订单下发测试。"""
def test_generate_supplier_text(self, client, manager_headers, make_order):
"""ORD-011: 生成下发文本。"""
order = make_order(order_status="approved")
resp = client.post(f"/api/orders/{order.id}/supplier-text", headers=manager_headers, json={
"supplier_id": order.factory_id,
})
assert resp.status_code == 200
data = resp.json()
assert "text_content" in data["data"]
def test_confirm_supplier_text(self, client, manager_headers, make_order):
"""ORD-012: 确认下发工厂。"""
order = make_order(order_status="approved")
resp = client.post(f"/api/orders/{order.id}/supplier-text/confirm", headers=manager_headers, json={
"supplier_id": order.factory_id,
"text_content": "测试下发文本内容",
})
assert resp.status_code == 200
def test_confirm_text_not_approved(self, client, manager_headers, make_order):
"""ORD-013: 非 approved 状态不能确认下发。"""
order = make_order(order_status="draft")
resp = client.post(f"/api/orders/{order.id}/supplier-text/confirm", headers=manager_headers, json={
"supplier_id": order.factory_id,
"text_content": "不应该成功",
})
assert resp.status_code in (400, 409)
@pytest.mark.order
@pytest.mark.p0
class TestOrderDetail:
"""ORD-015 ~ ORD-016: 订单详情与字段权限测试。"""
def test_salesman_order_detail_no_finance(self, client, salesman_headers, make_order):
"""ORD-015: 业务员订单详情不应包含成本、利润字段。"""
order = make_order(salesman_id=2)
resp = client.get(f"/api/orders/{order.id}", headers=salesman_headers)
assert resp.status_code == 200
data = resp.json()["data"]
# 业务员不应看到成本、利润
assert "cost_price_total" not in data or data.get("cost_price_total") is None or float(data.get("cost_price_total", 0)) == 0
def test_manager_order_detail_with_finance(self, client, manager_headers, make_order):
"""ORD-016: 管理层订单详情应包含完整财务字段。"""
order = make_order()
resp = client.get(f"/api/orders/{order.id}", headers=manager_headers)
assert resp.status_code == 200
data = resp.json()["data"]
# 管理层应该能看到成本、利润
assert "profit_total" in data
@pytest.mark.order
@pytest.mark.p0
class TestOrderEdit:
"""ORD-017 ~ ORD-018: 订单编辑测试。"""
def test_edit_draft_order(self, client, salesman_headers, make_product):
"""ORD-017: 编辑草稿订单成功。"""
product = make_product()
create_resp = client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "编辑测试客户",
"customer_mobile": "13800001111",
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": "100x100cm",
"unit": "",
"quantity": 5,
"sale_price": 100,
"cost_price": 60,
}],
})
order_id = create_resp.json()["data"]["order_id"]
resp = client.put(f"/api/orders/{order_id}", headers=salesman_headers, json={
"customer_name": "编辑后客户名",
"customer_mobile": "13800001111",
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": "100x100cm",
"unit": "",
"quantity": 20,
"sale_price": 100,
"cost_price": 60,
}],
})
assert resp.status_code == 200
@pytest.mark.order
@pytest.mark.p0
class TestOrderStatusFlow:
"""ORD-022: 订单状态流转完整性测试。"""
def test_full_status_flow(self, client, salesman_headers, manager_headers, driver_headers,
make_product, make_supplier):
"""完整状态流转: draft → pending_approve → approved → pending_factory"""
product = make_product()
supplier = make_supplier()
# 1. 创建订单API 自动提交审批,初始状态为 pending_approve
create_resp = client.post("/api/orders", headers=salesman_headers, json={
"customer_name": "状态流转客户",
"customer_mobile": "13800008888",
"factory_id": supplier.id,
"items": [{
"product_id": product.id,
"product_name": product.product_name,
"specification": "100x100cm",
"unit": "",
"quantity": 10,
"sale_price": 100,
"cost_price": 60,
}],
})
order_id = create_resp.json()["data"]["order_id"]
assert create_resp.json()["data"]["order_status"] == "pending_approve"
# 2. 审批通过
approve_resp = client.post(f"/api/orders/{order_id}/approve", headers=manager_headers, json={
"approve_result": "pass",
})
assert approve_resp.json()["data"]["order_status"] == "approved"
# 4. 确认下发
confirm_resp = client.post(f"/api/orders/{order_id}/supplier-text/confirm", headers=manager_headers, json={
"supplier_id": supplier.id,
"text_content": "下发文本",
})
assert confirm_resp.status_code == 200