dingdanquanliucheng/backend/tests/test_orders.py

396 lines
16 KiB
Python
Raw Normal View History

2026-06-14 16:20:04 +08:00
"""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
assert data["data"]["order_status"] == "draft"
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)
resp = client.post(f"/api/orders/{order_id}/submit", 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, make_product):
"""ORD-008: 重复提交应返回错误。"""
order_id = self._create_order(client, salesman_headers, make_product)
client.post(f"/api/orders/{order_id}/submit", headers=salesman_headers)
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. 创建草稿
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"] == "draft"
# 2. 提交审核
submit_resp = client.post(f"/api/orders/{order_id}/submit", headers=salesman_headers)
assert submit_resp.json()["data"]["order_status"] == "pending_approve"
# 3. 审批通过
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