feat(T06): Verkaufsmodul + Rechtsdokumente + DATEV-Export + Sales UI
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
"""Tests for DATEV export module: export creation, CSV format, date range validation."""
|
||||
|
||||
import csv
|
||||
import io
|
||||
import uuid
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from httpx import AsyncClient
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.sale import Sale
|
||||
from app.models.vehicle import Vehicle
|
||||
from app.models.contact import Contact
|
||||
from app.utils.datev import generate_datev_csv, validate_datev_csv, DATEV_HEADERS
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_vehicle_for_datev(db_session: AsyncSession) -> Vehicle:
|
||||
"""Create a test vehicle for DATEV tests."""
|
||||
vehicle = Vehicle(
|
||||
make="MAN",
|
||||
model="TGX",
|
||||
fin="WMA12345678901234",
|
||||
year=2021,
|
||||
vehicle_type="lkw",
|
||||
price=Decimal("55000.00"),
|
||||
availability="available",
|
||||
condition="used",
|
||||
)
|
||||
db_session.add(vehicle)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(vehicle)
|
||||
return vehicle
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_buyer_for_datev(db_session: AsyncSession) -> Contact:
|
||||
"""Create a test buyer contact for DATEV tests."""
|
||||
contact = Contact(
|
||||
company_name="DATEV Test Buyer GmbH",
|
||||
role="kaeufer",
|
||||
address_country="DE",
|
||||
vat_id="DE111222333",
|
||||
address_street="Testweg 3",
|
||||
address_zip="67890",
|
||||
address_city="Hamburg",
|
||||
)
|
||||
db_session.add(contact)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(contact)
|
||||
return contact
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_completed_sales(db_session: AsyncSession, test_vehicle_for_datev, test_buyer_for_datev) -> list[Sale]:
|
||||
"""Create test completed sales within a date range."""
|
||||
sales = []
|
||||
for i, (day, price) in enumerate([(5, Decimal("30000.00")), (10, Decimal("25000.00")), (15, Decimal("40000.00"))]):
|
||||
sale = Sale(
|
||||
vehicle_id=test_vehicle_for_datev.id,
|
||||
buyer_contact_id=test_buyer_for_datev.id,
|
||||
sale_price=price,
|
||||
sale_date=date(2025, 1, day),
|
||||
status="completed",
|
||||
is_gwg=False,
|
||||
)
|
||||
db_session.add(sale)
|
||||
sales.append(sale)
|
||||
await db_session.commit()
|
||||
for s in sales:
|
||||
await db_session.refresh(s)
|
||||
return sales
|
||||
|
||||
|
||||
class TestDATEVExportAPI:
|
||||
"""Test DATEV export API endpoints."""
|
||||
|
||||
async def test_create_export(self, admin_client: AsyncClient, test_completed_sales):
|
||||
"""POST /datev/export with valid date range returns 201."""
|
||||
response = await admin_client.post("/api/v1/datev/export", json={
|
||||
"start_date": "2025-01-01",
|
||||
"end_date": "2025-01-31",
|
||||
})
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["start_date"] == "2025-01-01"
|
||||
assert data["end_date"] == "2025-01-31"
|
||||
assert data["file_path"] is not None
|
||||
assert float(data["total_amount"]) == 95000.00
|
||||
|
||||
async def test_create_export_invalid_date_range(self, admin_client: AsyncClient):
|
||||
"""POST /datev/export with start > end returns 422."""
|
||||
response = await admin_client.post("/api/v1/datev/export", json={
|
||||
"start_date": "2025-12-31",
|
||||
"end_date": "2025-01-01",
|
||||
})
|
||||
assert response.status_code == 422
|
||||
|
||||
async def test_list_exports(self, admin_client: AsyncClient, test_completed_sales):
|
||||
"""GET /datev/exports returns list of exports."""
|
||||
# First create an export
|
||||
await admin_client.post("/api/v1/datev/export", json={
|
||||
"start_date": "2025-01-01",
|
||||
"end_date": "2025-01-31",
|
||||
})
|
||||
|
||||
response = await admin_client.get("/api/v1/datev/exports")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
assert len(data["items"]) >= 1
|
||||
assert "start_date" in data["items"][0]
|
||||
assert "end_date" in data["items"][0]
|
||||
|
||||
async def test_download_export_csv(self, admin_client: AsyncClient, test_completed_sales):
|
||||
"""GET /datev/exports/:id/download returns CSV content."""
|
||||
# Create export
|
||||
create_response = await admin_client.post("/api/v1/datev/export", json={
|
||||
"start_date": "2025-01-01",
|
||||
"end_date": "2025-01-31",
|
||||
})
|
||||
assert create_response.status_code == 201
|
||||
export_id = create_response.json()["id"]
|
||||
|
||||
# Download
|
||||
response = await admin_client.get(f"/api/v1/datev/exports/{export_id}/download")
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/csv; charset=utf-8"
|
||||
|
||||
# Parse CSV and verify headers
|
||||
csv_content = response.text
|
||||
reader = csv.reader(io.StringIO(csv_content), delimiter=";")
|
||||
header = next(reader)
|
||||
assert header == DATEV_HEADERS
|
||||
|
||||
# Verify at least one data row
|
||||
rows = list(reader)
|
||||
assert len(rows) >= 1
|
||||
for row in rows:
|
||||
assert len(row) == 6
|
||||
|
||||
async def test_download_export_not_found(self, admin_client: AsyncClient):
|
||||
"""GET /datev/exports/:nonexistent/download returns 404."""
|
||||
response = await admin_client.get(f"/api/v1/datev/exports/{uuid.uuid4()}/download")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
class TestDATEVCSVFormat:
|
||||
"""Test DATEV CSV format helper functions."""
|
||||
|
||||
def test_datev_csv_headers(self):
|
||||
"""DATEV CSV has correct headers."""
|
||||
assert DATEV_HEADERS == ["Datum", "Konto", "Gegenkonto", "Betrag", "Belegfeld", "Buchungstext"]
|
||||
|
||||
def test_generate_datev_csv_empty(self):
|
||||
"""Generate DATEV CSV with no sales returns only headers."""
|
||||
csv_content = generate_datev_csv([])
|
||||
assert validate_datev_csv(csv_content)
|
||||
|
||||
reader = csv.reader(io.StringIO(csv_content), delimiter=";")
|
||||
header = next(reader)
|
||||
assert header == DATEV_HEADERS
|
||||
# No data rows
|
||||
rows = list(reader)
|
||||
assert len(rows) == 0
|
||||
|
||||
def test_generate_datev_csv_with_sales(self, test_completed_sales):
|
||||
"""Generate DATEV CSV with sales produces correct rows."""
|
||||
# test_completed_sales is a fixture but we need to call it differently for sync test
|
||||
# Instead, create mock objects
|
||||
class MockVehicle:
|
||||
make = "MAN"
|
||||
model = "TGX"
|
||||
fin = "WMA12345678901234"
|
||||
|
||||
class MockSale:
|
||||
def __init__(self, sale_id, sale_date, sale_price):
|
||||
self.id = sale_id
|
||||
self.sale_date = sale_date
|
||||
self.sale_price = sale_price
|
||||
self.vehicle = MockVehicle()
|
||||
|
||||
sales = [
|
||||
MockSale(uuid.uuid4(), date(2025, 1, 5), Decimal("30000.00")),
|
||||
MockSale(uuid.uuid4(), date(2025, 1, 10), Decimal("25000.00")),
|
||||
]
|
||||
|
||||
csv_content = generate_datev_csv(sales)
|
||||
assert validate_datev_csv(csv_content)
|
||||
|
||||
reader = csv.reader(io.StringIO(csv_content), delimiter=";")
|
||||
header = next(reader)
|
||||
assert header == DATEV_HEADERS
|
||||
|
||||
rows = list(reader)
|
||||
assert len(rows) == 2
|
||||
|
||||
# Check first row format
|
||||
row1 = rows[0]
|
||||
assert row1[0] == "05.01.2025" # Datum in DD.MM.YYYY
|
||||
assert row1[1] == "1200" # Konto
|
||||
assert row1[2] == "8400" # Gegenkonto
|
||||
assert "," in row1[3] # Betrag with comma separator
|
||||
assert row1[4] # Belegfeld (short UUID)
|
||||
assert "MAN" in row1[5] # Buchungstext contains vehicle make
|
||||
|
||||
def test_validate_datev_csv_invalid_headers(self):
|
||||
"""Validate DATEV CSV with wrong headers returns False."""
|
||||
csv_content = "Wrong;Headers;Here\nval1;val2;val3"
|
||||
assert not validate_datev_csv(csv_content)
|
||||
|
||||
def test_validate_datev_csv_empty(self):
|
||||
"""Validate empty CSV returns False."""
|
||||
assert not validate_datev_csv("")
|
||||
assert not validate_datev_csv(" ")
|
||||
|
||||
def test_validate_datev_csv_valid(self):
|
||||
"""Validate correct DATEV CSV returns True."""
|
||||
csv_content = "Datum;Konto;Gegenkonto;Betrag;Belegfeld;Buchungstext\n05.01.2025;1200;8400;30000,00;ABC12345;MAN TGX"
|
||||
assert validate_datev_csv(csv_content)
|
||||
|
||||
def test_datev_csv_amount_format(self):
|
||||
"""DATEV CSV amount uses comma as decimal separator."""
|
||||
class MockVehicle:
|
||||
make = "VW"
|
||||
model = "Crafter"
|
||||
fin = "WVW12345678901234"
|
||||
|
||||
class MockSale:
|
||||
def __init__(self):
|
||||
self.id = uuid.uuid4()
|
||||
self.sale_date = date(2025, 3, 15)
|
||||
self.sale_price = Decimal("12345.67")
|
||||
self.vehicle = MockVehicle()
|
||||
|
||||
csv_content = generate_datev_csv([MockSale()])
|
||||
reader = csv.reader(io.StringIO(csv_content), delimiter=";")
|
||||
next(reader) # skip header
|
||||
row = next(reader)
|
||||
assert row[3] == "12345,67" # Comma as decimal separator
|
||||
|
||||
|
||||
class TestDATEVExportService:
|
||||
"""Test DATEV export service directly."""
|
||||
|
||||
async def test_create_export_no_sales_in_range(self, db_session: AsyncSession):
|
||||
"""Creating export with no sales in range produces empty CSV with 0 total."""
|
||||
from app.services import datev_service
|
||||
|
||||
export = await datev_service.create_export(
|
||||
db_session,
|
||||
start_date=date(2025, 6, 1),
|
||||
end_date=date(2025, 6, 30),
|
||||
)
|
||||
assert export.total_amount == Decimal("0")
|
||||
assert export.file_path is not None
|
||||
|
||||
async def test_list_exports_pagination(self, db_session: AsyncSession):
|
||||
"""List exports with pagination."""
|
||||
from app.services import datev_service
|
||||
|
||||
# Create a few exports
|
||||
for _ in range(3):
|
||||
await datev_service.create_export(
|
||||
db_session,
|
||||
start_date=date(2025, 1, 1),
|
||||
end_date=date(2025, 1, 31),
|
||||
)
|
||||
|
||||
exports, total = await datev_service.list_exports(db_session, page=1, page_size=2)
|
||||
assert total >= 3
|
||||
assert len(exports) <= 2
|
||||
@@ -0,0 +1,325 @@
|
||||
"""Tests for sales module: CRUD, contract PDF, GwG logic, vehicle status changes."""
|
||||
|
||||
import uuid
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from httpx import AsyncClient
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.sale import Sale
|
||||
from app.models.vehicle import Vehicle
|
||||
from app.models.contact import Contact
|
||||
from app.utils.contract_pdf import build_contract_html
|
||||
from app.utils.datev import generate_datev_csv, validate_datev_csv, DATEV_HEADERS
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_vehicle(db_session: AsyncSession) -> Vehicle:
|
||||
"""Create a test vehicle."""
|
||||
vehicle = Vehicle(
|
||||
make="Mercedes",
|
||||
model="Actros",
|
||||
fin="WDB96312345678901",
|
||||
year=2020,
|
||||
vehicle_type="lkw",
|
||||
price=Decimal("45000.00"),
|
||||
availability="available",
|
||||
condition="used",
|
||||
)
|
||||
db_session.add(vehicle)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(vehicle)
|
||||
return vehicle
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_buyer(db_session: AsyncSession) -> Contact:
|
||||
"""Create a test buyer contact."""
|
||||
contact = Contact(
|
||||
company_name="Test Buyer GmbH",
|
||||
role="kaeufer",
|
||||
address_country="DE",
|
||||
vat_id="DE123456789",
|
||||
address_street="Teststr. 1",
|
||||
address_zip="12345",
|
||||
address_city="Berlin",
|
||||
email="buyer@test.com",
|
||||
)
|
||||
db_session.add(contact)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(contact)
|
||||
return contact
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_seller(db_session: AsyncSession) -> Contact:
|
||||
"""Create a test seller contact."""
|
||||
contact = Contact(
|
||||
company_name="Test Seller GmbH",
|
||||
role="verkaeufer",
|
||||
address_country="DE",
|
||||
vat_id="DE987654321",
|
||||
address_street="Sellerstr. 2",
|
||||
address_zip="54321",
|
||||
address_city="München",
|
||||
email="seller@test.com",
|
||||
)
|
||||
db_session.add(contact)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(contact)
|
||||
return contact
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_sale(db_session: AsyncSession, test_vehicle: Vehicle, test_buyer: Contact) -> Sale:
|
||||
"""Create a test sale."""
|
||||
sale = Sale(
|
||||
vehicle_id=test_vehicle.id,
|
||||
buyer_contact_id=test_buyer.id,
|
||||
sale_price=Decimal("45000.00"),
|
||||
sale_date=date(2025, 1, 15),
|
||||
status="completed",
|
||||
is_gwg=False,
|
||||
)
|
||||
db_session.add(sale)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(sale)
|
||||
return sale
|
||||
|
||||
|
||||
class TestSaleCRUD:
|
||||
"""Test sale CRUD operations via API."""
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def setup_data(self, db_session, test_vehicle, test_buyer):
|
||||
"""Ensure vehicle and buyer exist."""
|
||||
return test_vehicle, test_buyer
|
||||
|
||||
async def test_list_sales_empty(self, admin_client: AsyncClient):
|
||||
"""GET /sales with no sales returns empty list."""
|
||||
response = await admin_client.get("/api/v1/sales/")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["items"] == []
|
||||
assert data["total"] == 0
|
||||
assert data["page"] == 1
|
||||
assert data["page_size"] == 20
|
||||
|
||||
async def test_create_sale(self, admin_client: AsyncClient, test_vehicle, test_buyer):
|
||||
"""POST /sales with valid data returns 201."""
|
||||
response = await admin_client.post("/api/v1/sales/", json={
|
||||
"vehicle_id": str(test_vehicle.id),
|
||||
"buyer_contact_id": str(test_buyer.id),
|
||||
"sale_price": "45000.00",
|
||||
"sale_date": "2025-01-15",
|
||||
"status": "draft",
|
||||
"is_gwg": False,
|
||||
})
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["vehicle_id"] == str(test_vehicle.id)
|
||||
assert data["buyer_contact_id"] == str(test_buyer.id)
|
||||
assert data["sale_price"] == "45000.00"
|
||||
assert data["status"] == "draft"
|
||||
assert data["is_gwg"] is False
|
||||
|
||||
async def test_create_sale_without_vehicle_id(self, admin_client: AsyncClient, test_buyer):
|
||||
"""POST /sales without vehicle_id returns 422."""
|
||||
response = await admin_client.post("/api/v1/sales/", json={
|
||||
"buyer_contact_id": str(test_buyer.id),
|
||||
"sale_price": "45000.00",
|
||||
})
|
||||
assert response.status_code == 422
|
||||
|
||||
async def test_create_sale_without_buyer_contact_id(self, admin_client: AsyncClient, test_vehicle):
|
||||
"""POST /sales without buyer_contact_id returns 422."""
|
||||
response = await admin_client.post("/api/v1/sales/", json={
|
||||
"vehicle_id": str(test_vehicle.id),
|
||||
"sale_price": "45000.00",
|
||||
})
|
||||
assert response.status_code == 422
|
||||
|
||||
async def test_get_sale_by_id(self, admin_client: AsyncClient, test_sale):
|
||||
"""GET /sales/:id returns sale detail with nested vehicle and contact."""
|
||||
response = await admin_client.get(f"/api/v1/sales/{test_sale.id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == str(test_sale.id)
|
||||
assert data["vehicle"]["make"] == "Mercedes"
|
||||
assert data["buyer"]["company_name"] == "Test Buyer GmbH"
|
||||
|
||||
async def test_get_sale_not_found(self, admin_client: AsyncClient):
|
||||
"""GET /sales/:nonexistent returns 404."""
|
||||
response = await admin_client.get(f"/api/v1/sales/{uuid.uuid4()}")
|
||||
assert response.status_code == 404
|
||||
|
||||
async def test_update_sale(self, admin_client: AsyncClient, test_sale):
|
||||
"""PUT /sales/:id updates sale fields."""
|
||||
response = await admin_client.put(f"/api/v1/sales/{test_sale.id}", json={
|
||||
"sale_price": "42000.00",
|
||||
"status": "completed",
|
||||
})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["sale_price"] == "42000.00"
|
||||
assert data["status"] == "completed"
|
||||
|
||||
async def test_delete_sale_cancels_and_restores_vehicle(self, admin_client: AsyncClient, test_sale, db_session):
|
||||
"""DELETE /sales/:id cancels sale and restores vehicle status to 'available'."""
|
||||
# First set vehicle to sold (as create_sale would)
|
||||
vehicle = await db_session.get(Vehicle, test_sale.vehicle_id)
|
||||
vehicle.availability = "sold"
|
||||
await db_session.commit()
|
||||
|
||||
response = await admin_client.delete(f"/api/v1/sales/{test_sale.id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "cancelled"
|
||||
|
||||
# Verify vehicle status restored
|
||||
await db_session.refresh(vehicle)
|
||||
assert vehicle.availability == "available"
|
||||
|
||||
async def test_list_sales_with_filter(self, admin_client: AsyncClient, test_sale):
|
||||
"""GET /sales?status=completed filters by status."""
|
||||
response = await admin_client.get("/api/v1/sales/?status=completed")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
for item in data["items"]:
|
||||
assert item["status"] == "completed"
|
||||
|
||||
async def test_list_sales_with_date_filter(self, admin_client: AsyncClient, test_sale):
|
||||
"""GET /sales?date_from=2025-01-01&date_to=2025-12-31 filters by date range."""
|
||||
response = await admin_client.get("/api/v1/sales/?date_from=2025-01-01&date_to=2025-12-31")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
|
||||
|
||||
class TestSaleVehicleStatus:
|
||||
"""Test vehicle status changes on sale create/delete."""
|
||||
|
||||
async def test_create_sale_sets_vehicle_sold(self, admin_client: AsyncClient, test_vehicle, test_buyer, db_session):
|
||||
"""Creating a sale sets vehicle availability to 'sold'."""
|
||||
response = await admin_client.post("/api/v1/sales/", json={
|
||||
"vehicle_id": str(test_vehicle.id),
|
||||
"buyer_contact_id": str(test_buyer.id),
|
||||
"sale_price": "45000.00",
|
||||
"sale_date": "2025-01-15",
|
||||
"status": "draft",
|
||||
})
|
||||
assert response.status_code == 201
|
||||
|
||||
# Verify vehicle status
|
||||
await db_session.refresh(test_vehicle)
|
||||
assert test_vehicle.availability == "sold"
|
||||
|
||||
async def test_cancel_sale_restores_vehicle_available(self, admin_client: AsyncClient, test_sale, db_session):
|
||||
"""Cancelling a sale restores vehicle availability to 'available'."""
|
||||
# Set vehicle to sold first
|
||||
vehicle = await db_session.get(Vehicle, test_sale.vehicle_id)
|
||||
vehicle.availability = "sold"
|
||||
await db_session.commit()
|
||||
|
||||
response = await admin_client.delete(f"/api/v1/sales/{test_sale.id}")
|
||||
assert response.status_code == 200
|
||||
|
||||
await db_session.refresh(vehicle)
|
||||
assert vehicle.availability == "available"
|
||||
|
||||
|
||||
class TestContractPDF:
|
||||
"""Test contract PDF generation and GwG logic."""
|
||||
|
||||
async def test_regenerate_contract(self, admin_client: AsyncClient, test_sale):
|
||||
"""POST /sales/:id/contract regenerates contract PDF."""
|
||||
with patch("app.services.sale_service.generate_contract_pdf") as mock_pdf:
|
||||
mock_pdf.return_value = "/tmp/contracts/test_contract.pdf"
|
||||
response = await admin_client.post(f"/api/v1/sales/{test_sale.id}/contract")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["sale_id"] == str(test_sale.id)
|
||||
assert "contract_pdf_path" in data
|
||||
|
||||
async def test_download_contract_not_found(self, admin_client: AsyncClient, test_sale):
|
||||
"""GET /sales/:id/contract returns 404 if no PDF generated."""
|
||||
response = await admin_client.get(f"/api/v1/sales/{test_sale.id}/contract")
|
||||
assert response.status_code == 404
|
||||
|
||||
async def test_download_contract_pdf(self, admin_client: AsyncClient, test_sale, db_session):
|
||||
"""GET /sales/:id/contract returns PDF content."""
|
||||
# Create a fake PDF file
|
||||
import os
|
||||
os.makedirs("/tmp/contracts", exist_ok=True)
|
||||
pdf_path = f"/tmp/contracts/contract_{test_sale.id}.pdf"
|
||||
with open(pdf_path, "wb") as f:
|
||||
f.write(b"%PDF-1.4 fake pdf content")
|
||||
|
||||
test_sale.contract_pdf_path = pdf_path
|
||||
db_session.add(test_sale)
|
||||
await db_session.commit()
|
||||
|
||||
response = await admin_client.get(f"/api/v1/sales/{test_sale.id}/contract")
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/pdf"
|
||||
|
||||
# Cleanup
|
||||
if os.path.exists(pdf_path):
|
||||
os.remove(pdf_path)
|
||||
|
||||
def test_gwg_clause_in_contract_html(self, test_sale, test_vehicle, test_buyer):
|
||||
"""GwG clause appears in contract HTML when is_gwg=true and price <= 800."""
|
||||
test_sale.is_gwg = True
|
||||
test_sale.sale_price = Decimal("500.00")
|
||||
test_sale.vehicle = test_vehicle
|
||||
test_sale.buyer = test_buyer
|
||||
|
||||
html = build_contract_html(test_sale)
|
||||
assert "Geringwertige Wirtschaftsgüter" in html
|
||||
assert "§ 6 Abs. 2 EStG" in html
|
||||
|
||||
def test_gwg_clause_not_in_contract_when_price_too_high(self, test_sale, test_vehicle, test_buyer):
|
||||
"""GwG clause does NOT appear when price > 800 even if is_gwg=true."""
|
||||
test_sale.is_gwg = True
|
||||
test_sale.sale_price = Decimal("5000.00")
|
||||
test_sale.vehicle = test_vehicle
|
||||
test_sale.buyer = test_buyer
|
||||
|
||||
html = build_contract_html(test_sale)
|
||||
assert "Geringwertige Wirtschaftsgüter" not in html
|
||||
|
||||
def test_gwg_clause_not_in_contract_when_not_gwg(self, test_sale, test_vehicle, test_buyer):
|
||||
"""GwG clause does NOT appear when is_gwg=false."""
|
||||
test_sale.is_gwg = False
|
||||
test_sale.sale_price = Decimal("500.00")
|
||||
test_sale.vehicle = test_vehicle
|
||||
test_sale.buyer = test_buyer
|
||||
|
||||
html = build_contract_html(test_sale)
|
||||
assert "Geringwertige Wirtschaftsgüter" not in html
|
||||
|
||||
def test_contract_html_contains_ust_id_field(self, test_sale, test_vehicle, test_buyer):
|
||||
"""Contract HTML contains USt-IdNr. field."""
|
||||
test_sale.vehicle = test_vehicle
|
||||
test_sale.buyer = test_buyer
|
||||
|
||||
html = build_contract_html(test_sale)
|
||||
assert "USt-IdNr" in html
|
||||
assert "DE123456789" in html
|
||||
|
||||
|
||||
class TestUstIdVerification:
|
||||
"""Test USt-IdNr. verification endpoint."""
|
||||
|
||||
async def test_verify_ust_id_disabled(self, admin_client: AsyncClient, test_sale):
|
||||
"""POST /sales/:id/verify-ust-id returns not verified when BZSt API disabled."""
|
||||
response = await admin_client.post(f"/api/v1/sales/{test_sale.id}/verify-ust-id")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["verified"] is False
|
||||
assert data["message"] == "BZSt API not available"
|
||||
Reference in New Issue
Block a user