Files

150 lines
5.9 KiB
Python
Raw Permalink 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.
"""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"