baseline: 批次A-D 成果 + membership 半成品(测试红)

This commit is contained in:
agent
2026-09-11 23:11:35 +08:00
commit b3f3095d53
311 changed files with 40540 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
"""P0 #5 信用额度落地集成测试。
覆盖:限额内通过、超限拒绝、force 放行+预警通知、接近 90% 预警、无限额不校验。
"""
import pytest
from datetime import date
from decimal import Decimal
from model_bakery import baker
from apps.catalog.models import Product
from apps.inventory.models import Warehouse
from apps.partner.models import Customer
from apps.finance.models import Receivable
from apps.notify.models import Notification
from apps.sales import services as sales_services
from apps.sales.services import CreditLimitExceeded
@pytest.fixture
def warehouse(db, tenant):
return baker.make(Warehouse, tenant=tenant, code="WH01", name="主仓")
@pytest.fixture
def product(db, tenant):
return baker.make(Product, tenant=tenant, code="P001", name="商品A",
sale_price=Decimal("100"))
@pytest.fixture
def customer(db, tenant):
return baker.make(Customer, tenant=tenant, code="C001", name="受限客户",
credit_limit=Decimal("1000"))
def _stock_in(tenant, warehouse, product, qty):
from apps.inventory import services as inv_services
inv_services.inbound(
tenant=tenant, warehouse=warehouse, product=product,
quantity=Decimal(str(qty)), unit_cost=Decimal("50"),
)
def _make_receivable(tenant, customer, amount, paid=0):
return baker.make(
Receivable, tenant=tenant, customer=customer, bill_no="RC-OLD",
bill_date=date.today(), total_amount=Decimal(str(amount)),
paid_amount=Decimal(str(paid)), status="partial" if paid else "open",
)
def test_confirm_within_limit_passes(tenant, warehouse, customer, product):
_make_receivable(tenant, customer, 400)
_stock_in(tenant, warehouse, product, 10)
bill = sales_services.create_sales_bill(
tenant=tenant, customer=customer, warehouse=warehouse,
lines=[{"product": product, "quantity": 5, "unit_price": 100}], # 500
)
sales_services.confirm_sales_bill(bill)
assert bill.state == "confirmed"
def test_confirm_over_limit_rejects_and_rolls_back(tenant, warehouse, customer, product):
_make_receivable(tenant, customer, 700)
_stock_in(tenant, warehouse, product, 10)
bill = sales_services.create_sales_bill(
tenant=tenant, customer=customer, warehouse=warehouse,
lines=[{"product": product, "quantity": 5, "unit_price": 100}], # 500 → 1200 > 1000
)
from apps.inventory.models import Stock
with pytest.raises(CreditLimitExceeded):
sales_services.confirm_sales_bill(bill)
# 事务回滚:库存未扣、应收未生成、状态未变
assert bill.state == "draft"
assert Stock.objects.get(product=product).on_hand == Decimal("10")
assert not Receivable.objects.filter(source_ref=bill.bill_no).exists()
def test_confirm_over_limit_force_passes_with_alert(tenant, warehouse, customer, product):
_make_receivable(tenant, customer, 700)
_stock_in(tenant, warehouse, product, 10)
bill = sales_services.create_sales_bill(
tenant=tenant, customer=customer, warehouse=warehouse,
lines=[{"product": product, "quantity": 5, "unit_price": 100}],
)
sales_services.confirm_sales_bill(bill, force=True)
assert bill.state == "confirmed"
assert Notification.objects.filter(
tenant=tenant, extra_data__forced=True
).count() == 1
def test_confirm_near_limit_warns(tenant, warehouse, customer, product):
"""占用达 90% 发预警但不阻断:400 旧账 + 500 新单 = 900 = 90%。"""
_make_receivable(tenant, customer, 400)
_stock_in(tenant, warehouse, product, 10)
bill = sales_services.create_sales_bill(
tenant=tenant, customer=customer, warehouse=warehouse,
lines=[{"product": product, "quantity": 5, "unit_price": 100}],
)
sales_services.confirm_sales_bill(bill)
assert bill.state == "confirmed"
assert Notification.objects.filter(
tenant=tenant, title__contains="信用额度即将用尽"
).count() == 1
def test_no_limit_no_check(tenant, warehouse, product):
"""credit_limit=0 表示不启用管控。"""
c = baker.make(Customer, tenant=tenant, code="C002", name="不限客户",
credit_limit=Decimal("0"))
_stock_in(tenant, warehouse, product, 10)
bill = sales_services.create_sales_bill(
tenant=tenant, customer=c, warehouse=warehouse,
lines=[{"product": product, "quantity": 10, "unit_price": 100}], # 1000 无额度
)
sales_services.confirm_sales_bill(bill)
assert bill.state == "confirmed"
def test_rounded_bill_counts_net_amount(tenant, warehouse, customer, product):
"""抹零后按净额计入占用:旧账 44 + 行合计 1000.56 抹零后 1000 = 1044 ≤ 1000?不,
这里验证"净额而非毛额"计入:旧账 600 + 1000.56→1000 = 1600 > 1000 必须拒绝;
若按毛额计入 600+1000.56 更超。改用不超限场景:旧账 0 + 999 抹零… 直接验证
旧账 44 + 1000.56 抹成 1000 = 1044 超限 → 用 force 之外,改为验证边界:
旧账 0.56,本单 1000.56 抹到 1000,合计 1000.56 → 超 1000 拒绝;
再把旧账清 0.44(收 0.12)后合计 1000.00 恰好通过。
"""
from decimal import Decimal as D
recv = _make_receivable(tenant, customer, 1)
_stock_in(tenant, warehouse, product, 10)
bill = sales_services.create_sales_bill(
tenant=tenant, customer=customer, warehouse=warehouse,
lines=[{"product": product, "quantity": 10, "unit_price": 100.05}],
round_to="1",
)
# 10×100.05 = 1000.5 → 抹到 1000
assert bill.total_amount == D("1000")
assert bill.round_off == D("0.5")
# outstanding = 1 + 1000 = 1001 > 1000 → 拒绝
with pytest.raises(CreditLimitExceeded):
sales_services.confirm_sales_bill(bill)
# 客户还掉 1 元旧账 → outstanding 1000 = 限额,恰好通过
recv.paid_amount = D("1")
recv.status = "paid"
recv.save()
sales_services.confirm_sales_bill(bill)
assert bill.state == "confirmed"