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

667 lines
30 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.

"""定价引擎测试 — 覆盖报价计算、定价规则CRUD、供应商成本、价格层级。
测试用例:
- PRICE-001 ~ PRICE-007: 定价规则 CRUD
- PRICE-010 ~ PRICE-020: 报价计算(面积/重量/按件/按米、附加费、税、条件公式)
- PRICE-030 ~ PRICE-033: 供应商成本 CRUD
- PRICE-040 ~ PRICE-043: 价格层级 CRUD
"""
from __future__ import annotations
import json
import pytest
# ============================================================
# 定价引擎单元测试(直接调用 PricingEngine不走 API
# ============================================================
@pytest.mark.pricing
class TestPricingEngineUnit:
"""定价引擎核心计算逻辑的单元测试。"""
def _make_rule(self, **overrides):
"""创建模拟定价规则对象。"""
class FakeRule:
pass
rule = FakeRule()
rule.base_unit_price = overrides.get("base_unit_price", 50.0)
rule.pricing_type = overrides.get("pricing_type", "area")
rule.pricing_unit = overrides.get("pricing_unit", "")
rule.pricing_inputs = overrides.get("pricing_inputs", json.dumps([
{"key": "length", "type": "number", "default_unit": "m"},
{"key": "width", "type": "number", "default_unit": "m"},
]))
rule.formula_expr = overrides.get("formula_expr", "$input.length_m * $input.width_m * $rule.base_unit_price")
rule.formula_constants = overrides.get("formula_constants", None)
rule.surcharge_json = overrides.get("surcharge_json", None)
rule.formula_note = overrides.get("formula_note", None)
rule.tax_rate = overrides.get("tax_rate", 0)
rule.tax_inclusive = overrides.get("tax_inclusive", 0)
return rule
def test_area_pricing_basic(self):
"""PRICE-010: 面积定价 — 2m x 1.2m x ¥50/㎡ = ¥120"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1.2})
assert result["base_cost"] == 120.0
assert result["cost_price"] == 120.0
assert result["total_surcharge"] == 0
def test_area_pricing_cm_unit(self):
"""单位换算: cm → m, 200cm x 120cm x ¥50/㎡ = ¥120"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0)
result = pricing_engine.calculate(rule, {
"length": 200, "length_unit": "cm",
"width": 120, "width_unit": "cm",
})
assert result["base_cost"] == 120.0
def test_area_pricing_mm_unit(self):
"""单位换算: mm → m, 2000mm x 1200mm x ¥50/㎡ = ¥120"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0)
result = pricing_engine.calculate(rule, {
"length": 2000, "length_unit": "mm",
"width": 1200, "width_unit": "mm",
})
assert result["base_cost"] == 120.0
def test_formula_with_constants(self):
"""PRICE-019: 含常量的公式计算, $const.rate 作为乘数"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
formula_expr="$input.length_m * $input.width_m * $rule.base_unit_price * $const.rate",
formula_constants=json.dumps({"rate": 1.5}),
)
# 1*1*50*1.5 = 75
result = pricing_engine.calculate(rule, {"length": 1, "width": 1})
assert result["base_cost"] == 75.0
def test_surcharge_per_sqm(self):
"""PRICE-012: 按面积附加费, 2m*1.2m=2.4㎡, ¥10/㎡ = ¥24"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}),
)
result = pricing_engine.calculate(rule, {
"length": 2, "width": 1.2, "surcharge_urgent": True,
})
assert result["total_surcharge"] == 24.0
assert len(result["surcharge_items"]) == 1
assert result["surcharge_items"][0]["key"] == "urgent"
def test_surcharge_per_piece(self):
"""PRICE-013: 按件附加费, 10件 x ¥5/件 = ¥50"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"packaging": {"name": "包装费", "method": "per_piece", "price": 5, "input_key": "qty"}
}),
)
result = pricing_engine.calculate(rule, {
"length": 2, "width": 1.2, "qty": 10, "surcharge_packaging": True,
})
assert result["total_surcharge"] == 50.0
def test_surcharge_per_linear_m(self):
"""PRICE-014: 按米附加费, 20m x ¥3/m = ¥60"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"edge": {"name": "包边费", "method": "per_linear_m", "price": 3, "input_key": "edge_length"}
}),
)
result = pricing_engine.calculate(rule, {
"length": 2, "width": 1.2, "edge_length": 20, "surcharge_edge": True,
})
assert result["total_surcharge"] == 60.0
def test_surcharge_not_enabled(self):
"""未启用的附加费不计入"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}),
)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1.2})
assert result["total_surcharge"] == 0
assert len(result["surcharge_items"]) == 0
def test_tax_exclusive(self):
"""PRICE-017: 不含税定价, cost=100, tax_rate=13%, tax_amount=13, price_in_tax=113"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0, tax_rate=13, tax_inclusive=0)
# 2m x 1m x 50 = 100
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["cost_price"] == 100.0
assert result["tax_rate"] == 13
assert result["price_ex_tax"] == 100.0
assert result["tax_amount"] == 13.0
assert result["price_in_tax"] == 113.0
def test_tax_inclusive(self):
"""PRICE-016: 含税定价, cost=113(含税), tax_rate=13%, price_ex_tax≈100"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=56.5, tax_rate=13, tax_inclusive=1)
# 2m x 1m x 56.5 = 113
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["cost_price"] == 113.0
assert result["price_in_tax"] == 113.0
assert result["price_ex_tax"] == 100.0
assert result["tax_amount"] == 13.0
def test_no_tax(self):
"""无税率时, tax_amount=0, price_in_tax=cost_price"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0, tax_rate=0)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["tax_amount"] == 0
assert result["price_in_tax"] == result["cost_price"]
def test_empty_inputs(self):
"""空输入返回 0"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule()
result = pricing_engine.calculate(rule, {})
assert result["base_cost"] == 0.0
def test_invalid_formula_returns_zero(self):
"""无效公式返回 0, 不抛异常"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(formula_expr="$nonexistent.field * ???")
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["base_cost"] == 0.0
def test_formula_detail_text(self):
"""公式说明包含关键信息"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1.2})
assert "2.0m" in result["formula_detail"]
assert "1.2m" in result["formula_detail"]
assert "120.00" in result["formula_detail"]
def test_formula_detail_with_surcharge(self):
"""公式说明包含附加费项"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}),
)
result = pricing_engine.calculate(rule, {
"length": 2, "width": 1.2, "surcharge_urgent": True,
})
assert "加急费" in result["formula_detail"]
def test_formula_detail_with_tax(self):
"""公式说明包含税率信息"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0, tax_rate=13, tax_inclusive=0)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert "13" in result["formula_detail"]
assert "" in result["formula_detail"]
def test_get_surcharge_options(self):
"""附加费选项列表"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10},
"pack": {"name": "包装费", "method": "per_piece", "price": 5, "input_key": "qty"},
}))
options = pricing_engine.get_available_surcharge_options(rule)
assert len(options) == 2
assert options[0]["key"] == "urgent"
assert options[1]["key"] == "pack"
def test_conditional_formula(self):
"""条件公式: ${1 > 0} ? 100 : 50 → 100"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
formula_expr="${$input.length_m > 1} ? $input.length_m * $rule.base_unit_price : $rule.base_unit_price",
)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["base_cost"] == 100.0
def test_conditional_formula_false_branch(self):
"""条件公式假分支: length=0.5 <= 1, 走 50"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
formula_expr="${$input.length_m > 1} ? $input.length_m * $rule.base_unit_price : $rule.base_unit_price",
)
result = pricing_engine.calculate(rule, {"length": 0.5, "width": 1})
assert result["base_cost"] == 50.0
# ============================================================
# 定价规则 CRUD API 测试
# ============================================================
@pytest.mark.pricing
@pytest.mark.p1
class TestPricingRuleCRUD:
"""PRICE-001 ~ PRICE-007: 定价规则 API 测试。"""
def _create_rule(self, client, headers, product_id=1, **overrides):
"""辅助: 创建定价规则。"""
payload = {
"product_id": product_id,
"product_name": f"产品{product_id}",
"pricing_type": "area",
"base_unit_price": 50.0,
"pricing_inputs": json.dumps([
{"key": "length", "type": "number", "default_unit": "m"},
{"key": "width", "type": "number", "default_unit": "m"},
]),
"formula_expr": "$input.length_m * $input.width_m * $rule.base_unit_price",
}
payload.update(overrides)
resp = client.post("/api/pricing-rules", headers=headers, json=payload)
return resp
def test_create_pricing_rule(self, client, admin_headers, make_product):
"""PRICE-001: 创建面积定价规则。"""
product = make_product()
resp = self._create_rule(client, admin_headers, product_id=product.id)
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
assert data["data"]["product_id"] == product.id
assert data["data"]["pricing_type"] == "area"
assert data["data"]["base_unit_price"] == 50.0
def test_create_duplicate_rule_rejected(self, client, admin_headers, make_product):
"""PRICE-001b: 同产品重复创建应拒绝。"""
product = make_product()
self._create_rule(client, admin_headers, product_id=product.id)
resp = self._create_rule(client, admin_headers, product_id=product.id)
assert resp.status_code == 400
def test_list_pricing_rules(self, client, admin_headers, make_product):
"""PRICE-007: 查询定价规则列表。"""
product = make_product()
self._create_rule(client, admin_headers, product_id=product.id)
resp = client.get("/api/pricing-rules", headers=admin_headers)
assert resp.status_code == 200
data = resp.json()
assert data["data"]["total"] >= 1
def test_list_pricing_rules_by_product(self, client, admin_headers, make_product):
"""按产品ID筛选定价规则。"""
product = make_product()
self._create_rule(client, admin_headers, product_id=product.id)
resp = client.get(f"/api/pricing-rules?product_id={product.id}", headers=admin_headers)
assert resp.status_code == 200
assert resp.json()["data"]["total"] >= 1
def test_get_pricing_rule(self, client, admin_headers, make_product):
"""按产品ID获取定价规则。"""
product = make_product()
self._create_rule(client, admin_headers, product_id=product.id)
resp = client.get(f"/api/pricing-rules/{product.id}", headers=admin_headers)
assert resp.status_code == 200
assert resp.json()["data"]["product_id"] == product.id
def test_get_pricing_rule_not_found(self, client, admin_headers):
"""查询不存在的产品定价规则返回 404。"""
resp = client.get("/api/pricing-rules/99999", headers=admin_headers)
assert resp.status_code == 404
def test_update_pricing_rule(self, client, admin_headers, make_product):
"""PRICE-005: 更新定价规则。"""
product = make_product()
create_resp = self._create_rule(client, admin_headers, product_id=product.id)
rule_id = create_resp.json()["data"]["id"]
resp = client.put(f"/api/pricing-rules/{rule_id}", headers=admin_headers, json={
"base_unit_price": 80.0,
})
assert resp.status_code == 200
assert resp.json()["data"]["base_unit_price"] == 80.0
def test_update_pricing_rule_not_found(self, client, admin_headers):
"""更新不存在的定价规则返回 404。"""
resp = client.put("/api/pricing-rules/99999", headers=admin_headers, json={
"base_unit_price": 80.0,
})
assert resp.status_code == 404
def test_delete_pricing_rule(self, client, admin_headers, make_product):
"""PRICE-006: 删除定价规则(软删除)。"""
product = make_product()
create_resp = self._create_rule(client, admin_headers, product_id=product.id)
rule_id = create_resp.json()["data"]["id"]
resp = client.delete(f"/api/pricing-rules/{rule_id}", headers=admin_headers)
assert resp.status_code == 200
assert resp.json()["data"]["deleted"] is True
# 删除后查询应返回 404
resp = client.get(f"/api/pricing-rules/{product.id}", headers=admin_headers)
assert resp.status_code == 404
def test_salesman_cannot_create_rule(self, client, salesman_headers, make_product):
"""业务员不能创建定价规则。"""
product = make_product()
resp = self._create_rule(client, salesman_headers, product_id=product.id)
assert resp.status_code == 403
def test_salesman_cannot_update_rule(self, client, salesman_headers, admin_headers, make_product):
"""业务员不能更新定价规则。"""
product = make_product()
create_resp = self._create_rule(client, admin_headers, product_id=product.id)
rule_id = create_resp.json()["data"]["id"]
resp = client.put(f"/api/pricing-rules/{rule_id}", headers=salesman_headers, json={
"base_unit_price": 80.0,
})
assert resp.status_code == 403
# ============================================================
# 报价计算 API 测试
# ============================================================
@pytest.mark.pricing
@pytest.mark.p1
class TestQuotationCalculation:
"""PRICE-010 ~ PRICE-020: 报价计算 API 测试。"""
def _setup_rule(self, client, headers, db_session, product, **overrides):
"""辅助: 在数据库中创建定价规则。"""
from backend.app.models.business import ProductPricingRule
defaults = {
"product_id": product.id,
"product_name": product.product_name,
"pricing_type": "area",
"base_unit_price": 50.0,
"pricing_unit": "",
"pricing_inputs": json.dumps([
{"key": "length", "type": "number", "default_unit": "m"},
{"key": "width", "type": "number", "default_unit": "m"},
]),
"formula_expr": "$input.length_m * $input.width_m * $rule.base_unit_price",
"tax_rate": 0,
"tax_inclusive": 0,
"status": 1,
"deleted": 0,
}
defaults.update(overrides)
rule = ProductPricingRule(**defaults)
db_session.add(rule)
db_session.flush()
return rule
def test_calculate_area(self, client, admin_headers, db_session, make_product):
"""PRICE-010: 面积报价计算, 2m x 1.2m x ¥50 = ¥120。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product, base_unit_price=50.0)
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1.2},
})
assert resp.status_code == 200
data = resp.json()["data"]
assert data["base_cost"] == 120.0
assert data["cost_price"] == 120.0
def test_calculate_with_surcharge(self, client, admin_headers, db_session, make_product):
"""报价计算含附加费。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product,
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}))
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1.2},
"surcharge_selections": {"surcharge_urgent": True},
})
assert resp.status_code == 200
data = resp.json()["data"]
assert data["total_surcharge"] == 24.0
assert data["cost_price"] == 144.0
def test_calculate_with_tax_exclusive(self, client, admin_headers, db_session, make_product):
"""PRICE-017: 不含税报价计算, 100 + 13% = 113。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product,
base_unit_price=50.0, tax_rate=13, tax_inclusive=0)
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
data = resp.json()["data"]
assert data["tax_rate"] == 13
assert data["price_ex_tax"] == 100.0
assert data["tax_amount"] == 13.0
assert data["price_in_tax"] == 113.0
def test_calculate_with_tax_inclusive(self, client, admin_headers, db_session, make_product):
"""PRICE-016: 含税报价计算。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product,
base_unit_price=56.5, tax_rate=13, tax_inclusive=1)
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
data = resp.json()["data"]
assert data["price_in_tax"] == 113.0
assert data["price_ex_tax"] == 100.0
def test_calculate_no_rule_returns_404(self, client, admin_headers, make_product):
"""PRICE-020: 无定价规则的产品返回 404。"""
product = make_product()
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
assert resp.status_code == 404
def test_calculate_with_price_tiers(self, client, admin_headers, db_session, make_product):
"""PRICE-018: 含价格层级的报价。"""
from backend.app.models.business import ProductPriceTier
product = make_product()
self._setup_rule(client, admin_headers, db_session, product, base_unit_price=50.0)
tier = ProductPriceTier(
product_id=product.id, tier_code="vip", tier_name="VIP",
price=45.0, price_unit="", status=1, deleted=0,
)
db_session.add(tier)
db_session.flush()
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
data = resp.json()["data"]
assert "vip" in data["sale_price_tier"]
assert data["sale_price_tier"]["vip"] == 45.0
def test_calculate_formula_detail(self, client, admin_headers, db_session, make_product):
"""报价结果包含公式说明。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product, base_unit_price=50.0)
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1.2},
})
data = resp.json()["data"]
assert "formula_detail" in data
assert "120.00" in data["formula_detail"]
def test_calculate_available_surcharge_options(self, client, admin_headers, db_session, make_product):
"""报价结果包含可用附加费选项。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product,
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}))
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
data = resp.json()["data"]
assert len(data["available_surcharge_options"]) == 1
assert data["available_surcharge_options"][0]["key"] == "urgent"
def test_salesman_can_calculate(self, client, salesman_headers, admin_headers, db_session, make_product):
"""业务员可以调用报价计算。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product, base_unit_price=50.0)
resp = client.post("/api/quotation/calculate", headers=salesman_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
assert resp.status_code == 200
# ============================================================
# 供应商成本 CRUD API 测试
# ============================================================
@pytest.mark.pricing
@pytest.mark.p1
class TestSupplierCostCRUD:
"""PRICE-030 ~ PRICE-033: 供应商成本 API 测试。"""
def _create_cost(self, client, headers, product_id, supplier_id, **overrides):
payload = {
"product_id": product_id,
"supplier_id": supplier_id,
"cost_price": 35.0,
"cost_unit": "",
}
payload.update(overrides)
return client.post("/api/supplier-costs", headers=headers, json=payload)
def test_create_supplier_cost(self, client, admin_headers, make_product, make_supplier):
"""PRICE-030: 创建供应商成本。"""
product = make_product()
supplier = make_supplier()
resp = self._create_cost(client, admin_headers, product.id, supplier.id)
assert resp.status_code == 200
assert resp.json()["data"]["cost_price"] == 35.0
def test_list_supplier_costs(self, client, admin_headers, make_product, make_supplier):
"""PRICE-031: 查询供应商成本列表。"""
product = make_product()
supplier = make_supplier()
self._create_cost(client, admin_headers, product.id, supplier.id)
resp = client.get(f"/api/supplier-costs?product_id={product.id}", headers=admin_headers)
assert resp.status_code == 200
assert len(resp.json()["data"]["list"]) >= 1
def test_update_supplier_cost(self, client, admin_headers, make_product, make_supplier):
"""PRICE-032: 更新供应商成本。"""
product = make_product()
supplier = make_supplier()
create_resp = self._create_cost(client, admin_headers, product.id, supplier.id)
cost_id = create_resp.json()["data"]["id"]
resp = client.put(f"/api/supplier-costs/{cost_id}", headers=admin_headers, json={
"cost_price": 40.0,
})
assert resp.status_code == 200
assert resp.json()["data"]["cost_price"] == 40.0
def test_delete_supplier_cost(self, client, admin_headers, make_product, make_supplier):
"""PRICE-033: 删除供应商成本(软删除)。"""
product = make_product()
supplier = make_supplier()
create_resp = self._create_cost(client, admin_headers, product.id, supplier.id)
cost_id = create_resp.json()["data"]["id"]
resp = client.delete(f"/api/supplier-costs/{cost_id}", headers=admin_headers)
assert resp.status_code == 200
# 删除后列表应为空
resp = client.get(f"/api/supplier-costs?product_id={product.id}", headers=admin_headers)
assert len(resp.json()["data"]["list"]) == 0
def test_salesman_cannot_create_cost(self, client, salesman_headers, make_product, make_supplier):
"""业务员不能创建供应商成本。"""
product = make_product()
supplier = make_supplier()
resp = self._create_cost(client, salesman_headers, product.id, supplier.id)
assert resp.status_code == 403
# ============================================================
# 价格层级 CRUD API 测试
# ============================================================
@pytest.mark.pricing
@pytest.mark.p1
class TestPriceTierCRUD:
"""PRICE-040 ~ PRICE-043: 价格层级 API 测试。"""
def _create_tier(self, client, headers, product_id, **overrides):
payload = {
"product_id": product_id,
"tier_code": "vip",
"tier_name": "VIP客户",
"price": 45.0,
"price_unit": "",
}
payload.update(overrides)
return client.post("/api/price-tiers", headers=headers, json=payload)
def test_create_price_tier(self, client, admin_headers, make_product):
"""PRICE-040: 创建价格层级。"""
product = make_product()
resp = self._create_tier(client, admin_headers, product.id)
assert resp.status_code == 200
assert resp.json()["data"]["tier_code"] == "vip"
assert resp.json()["data"]["price"] == 45.0
def test_list_price_tiers(self, client, admin_headers, make_product):
"""PRICE-041: 查询价格层级列表。"""
product = make_product()
self._create_tier(client, admin_headers, product.id)
resp = client.get(f"/api/price-tiers?product_id={product.id}", headers=admin_headers)
assert resp.status_code == 200
assert len(resp.json()["data"]["list"]) >= 1
def test_update_price_tier(self, client, admin_headers, make_product):
"""PRICE-042: 更新价格层级。"""
product = make_product()
create_resp = self._create_tier(client, admin_headers, product.id)
tier_id = create_resp.json()["data"]["id"]
resp = client.put(f"/api/price-tiers/{tier_id}", headers=admin_headers, json={
"price": 42.0,
})
assert resp.status_code == 200
assert resp.json()["data"]["price"] == 42.0
def test_delete_price_tier(self, client, admin_headers, make_product):
"""PRICE-043: 删除价格层级(软删除)。"""
product = make_product()
create_resp = self._create_tier(client, admin_headers, product.id)
tier_id = create_resp.json()["data"]["id"]
resp = client.delete(f"/api/price-tiers/{tier_id}", headers=admin_headers)
assert resp.status_code == 200
# 删除后列表应为空
resp = client.get(f"/api/price-tiers?product_id={product.id}", headers=admin_headers)
assert len(resp.json()["data"]["list"]) == 0
def test_salesman_can_list_tiers(self, client, salesman_headers, admin_headers, make_product):
"""业务员可以查看价格层级。"""
product = make_product()
self._create_tier(client, admin_headers, product.id)
resp = client.get(f"/api/price-tiers?product_id={product.id}", headers=salesman_headers)
assert resp.status_code == 200
def test_salesman_cannot_create_tier(self, client, salesman_headers, make_product):
"""业务员不能创建价格层级。"""
product = make_product()
resp = self._create_tier(client, salesman_headers, product.id)
assert resp.status_code == 403