"""sales 集成测试:销售单过账 → 扣库存 + 生成应收。""" import pytest from decimal import Decimal from model_bakery import baker from rest_framework.test import APIClient from rest_framework_simplejwt.tokens import RefreshToken from apps.inventory import services as inv_services from apps.inventory.models import Stock from apps.finance.models import Receivable from apps.sales import services from apps.sales.models import SalesBill @pytest.fixture def auth_client(db, user, tenant): c = APIClient() refresh = RefreshToken.for_user(user) c.credentials( HTTP_AUTHORIZATION=f"Bearer {refresh.access_token}", HTTP_X_TENANT_ID=tenant.code, ) return c @pytest.fixture def customer(tenant): from apps.partner.models import Customer return baker.make(Customer, tenant=tenant, code="C001", name="客户A") @pytest.fixture def warehouse(tenant): from apps.inventory.models import Warehouse return baker.make(Warehouse, tenant=tenant, code="WH01", name="主仓") @pytest.fixture def product(tenant): from apps.catalog.models import Product return baker.make(Product, tenant=tenant, code="P001", name="商品A") # --- services 单元测试 --- @pytest.mark.django_db def test_create_sales_bill_calculates_total(tenant, customer, warehouse, product): bill = services.create_sales_bill( tenant=tenant, customer=customer, warehouse=warehouse, lines=[ {"product": product, "quantity": Decimal("5"), "unit_price": Decimal("12")}, {"product": product, "quantity": Decimal("3"), "unit_price": Decimal("12")}, ], ) assert bill.total_amount == Decimal("96") assert bill.state == "draft" assert bill.lines.count() == 2 @pytest.mark.django_db def test_confirm_sales_bill_deducts_stock_and_creates_receivable( tenant, customer, warehouse, product ): # 准备库存 20 inv_services.inbound( tenant=tenant, warehouse=warehouse, product=product, quantity=Decimal("20"), unit_cost=Decimal("8"), ) # 销售 7 bill = services.create_sales_bill( tenant=tenant, customer=customer, warehouse=warehouse, lines=[{"product": product, "quantity": Decimal("7"), "unit_price": Decimal("12")}], ) services.confirm_sales_bill(bill) # 库存 stock = Stock.objects.get(tenant=tenant, warehouse=warehouse, product=product) assert stock.on_hand == Decimal("13") # 应收 recv = Receivable.objects.get(tenant=tenant, source_ref=bill.bill_no) assert recv.total_amount == Decimal("84") assert recv.status == "open" # 单据状态 bill.refresh_from_db() assert bill.state == "confirmed" @pytest.mark.django_db def test_confirm_insufficient_stock_rolls_back(tenant, customer, warehouse, product): inv_services.inbound( tenant=tenant, warehouse=warehouse, product=product, quantity=Decimal("5"), ) bill = services.create_sales_bill( tenant=tenant, customer=customer, warehouse=warehouse, lines=[{"product": product, "quantity": Decimal("10"), "unit_price": Decimal("12")}], ) with pytest.raises(inv_services.InsufficientStock): services.confirm_sales_bill(bill) # 库存不变(事务回滚) stock = Stock.objects.get(tenant=tenant, warehouse=warehouse, product=product) assert stock.on_hand == Decimal("5") # 应收不生成 assert Receivable.objects.filter(tenant=tenant, source_ref=bill.bill_no).count() == 0 # 单据仍是 draft bill.refresh_from_db() assert bill.state == "draft" @pytest.mark.django_db def test_confirm_twice_raises(tenant, customer, warehouse, product): inv_services.inbound( tenant=tenant, warehouse=warehouse, product=product, quantity=Decimal("10"), ) bill = services.create_sales_bill( tenant=tenant, customer=customer, warehouse=warehouse, lines=[{"product": product, "quantity": Decimal("3"), "unit_price": Decimal("10")}], ) services.confirm_sales_bill(bill) with pytest.raises(ValueError): services.confirm_sales_bill(bill) # --- API 集成测试 --- @pytest.mark.django_db def test_api_list_sales_bills(auth_client, tenant, customer, warehouse): baker.make( SalesBill, tenant=tenant, customer=customer, warehouse=warehouse, bill_no="XS0001", total_amount=Decimal("100"), ) resp = auth_client.get("/api/v1/sales/bills/") assert resp.status_code == 200 assert resp.json()["count"] == 1 @pytest.mark.django_db def test_api_list_sales_orders(auth_client, tenant, customer, warehouse): from apps.sales.models import SalesOrder baker.make( SalesOrder, tenant=tenant, customer=customer, warehouse=warehouse, bill_no="SO0001", total_amount=Decimal("0"), ) resp = auth_client.get("/api/v1/sales/orders/") assert resp.status_code == 200 assert resp.json()["count"] == 1