TorbatYar/backend/services/accounting/app/tests/test_api.py
Mortezakoohjani 7953e47f8b Ship accounting UX: Rial integers, auto doc numbers, reverse-and-edit, real reports, customers nav.
Wire DocumentNumberSequence allocator, ledger-based subsidiary/detail reports, and posted-voucher reopen flow without mock ops forms.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 15:52:06 +03:30

299 lines
8.9 KiB
Python

import uuid
from datetime import date
import pytest
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers
@pytest.mark.asyncio
async def test_health(client):
resp = await client.get("/health")
assert resp.status_code == 200
assert resp.json()["service"] == "accounting-service"
@pytest.mark.asyncio
async def test_create_chart_of_accounts(client):
resp = await client.post(
"/api/v1/accounts/charts",
json={"name": "Default COA", "code": "COA-001", "is_default": True},
headers=tenant_headers(TENANT_A),
)
assert resp.status_code == 201
data = resp.json()
assert data["code"] == "COA-001"
assert data["tenant_id"] == str(TENANT_A)
@pytest.mark.asyncio
async def test_tenant_isolation_accounts(client):
chart_resp = await client.post(
"/api/v1/accounts/charts",
json={"name": "Tenant A COA", "code": "COA-A"},
headers=tenant_headers(TENANT_A),
)
assert chart_resp.status_code == 201
chart_id = chart_resp.json()["id"]
acc_resp = await client.post(
"/api/v1/accounts",
json={
"chart_id": chart_id,
"code": "1000",
"name": "Test Account",
"account_type": "asset",
"account_category": "current_asset",
},
headers=tenant_headers(TENANT_A),
)
assert acc_resp.status_code == 201
account_id = acc_resp.json()["id"]
resp_b_list = await client.get(
"/api/v1/accounts",
headers=tenant_headers(TENANT_B),
)
assert resp_b_list.status_code == 200
assert len(resp_b_list.json()) == 0
resp_b_get = await client.get(
f"/api/v1/accounts/{account_id}",
headers=tenant_headers(TENANT_B),
)
assert resp_b_get.status_code == 404
@pytest.mark.asyncio
async def test_posting_engine_balanced_voucher(client):
year_resp = await client.post(
"/api/v1/fiscal/years",
json={"name": "1403", "start_date": "2024-03-20", "end_date": "2025-03-19", "is_current": True},
headers=tenant_headers(TENANT_A),
)
assert year_resp.status_code == 201
year_id = year_resp.json()["id"]
period_resp = await client.post(
"/api/v1/fiscal/periods",
json={
"fiscal_year_id": year_id,
"name": "Month 1",
"period_number": 1,
"start_date": "2024-03-20",
"end_date": "2024-04-19",
"is_current": True,
},
headers=tenant_headers(TENANT_A),
)
assert period_resp.status_code == 201
period_id = period_resp.json()["id"]
chart_resp = await client.post(
"/api/v1/accounts/charts",
json={"name": "COA", "code": "COA-POST"},
headers=tenant_headers(TENANT_A),
)
chart_id = chart_resp.json()["id"]
acc1_resp = await client.post(
"/api/v1/accounts",
json={
"chart_id": chart_id,
"code": "1101",
"name": "Cash",
"account_type": "asset",
"account_category": "current_asset",
},
headers=tenant_headers(TENANT_A),
)
acc2_resp = await client.post(
"/api/v1/accounts",
json={
"chart_id": chart_id,
"code": "4101",
"name": "Revenue",
"account_type": "revenue",
"account_category": "operating_revenue",
},
headers=tenant_headers(TENANT_A),
)
acc1_id = acc1_resp.json()["id"]
acc2_id = acc2_resp.json()["id"]
voucher_resp = await client.post(
"/api/v1/posting/vouchers",
json={
"fiscal_period_id": period_id,
"voucher_number": "V-001",
"voucher_date": str(date.today()),
"description": "Test voucher",
"lines": [
{"account_id": acc1_id, "debit": "1000.0000", "credit": "0"},
{"account_id": acc2_id, "debit": "0", "credit": "1000.0000"},
],
},
headers=tenant_headers(TENANT_A),
)
assert voucher_resp.status_code == 201
voucher_id = voucher_resp.json()["id"]
post_resp = await client.post(
f"/api/v1/posting/vouchers/{voucher_id}/post",
json={},
headers=tenant_headers(TENANT_A),
)
assert post_resp.status_code == 200
assert post_resp.json()["status"] == "posted"
@pytest.mark.asyncio
async def test_unbalanced_voucher_rejected(client):
year_resp = await client.post(
"/api/v1/fiscal/years",
json={"name": "1404", "start_date": "2025-03-20", "end_date": "2026-03-19"},
headers=tenant_headers(TENANT_A),
)
year_id = year_resp.json()["id"]
period_resp = await client.post(
"/api/v1/fiscal/periods",
json={
"fiscal_year_id": year_id,
"name": "P1",
"period_number": 1,
"start_date": "2025-03-20",
"end_date": "2025-04-19",
"is_current": True,
},
headers=tenant_headers(TENANT_A),
)
period_id = period_resp.json()["id"]
chart_resp = await client.post(
"/api/v1/accounts/charts",
json={"name": "COA2", "code": "COA-UNBAL"},
headers=tenant_headers(TENANT_A),
)
chart_id = chart_resp.json()["id"]
acc_resp = await client.post(
"/api/v1/accounts",
json={
"chart_id": chart_id,
"code": "1102",
"name": "Bank",
"account_type": "asset",
"account_category": "current_asset",
},
headers=tenant_headers(TENANT_A),
)
acc_id = acc_resp.json()["id"]
voucher_resp = await client.post(
"/api/v1/posting/vouchers",
json={
"fiscal_period_id": period_id,
"voucher_number": "V-UNBAL",
"voucher_date": str(date.today()),
"lines": [
{"account_id": acc_id, "debit": "500", "credit": "0"},
{"account_id": acc_id, "debit": "0", "credit": "300"},
],
},
headers=tenant_headers(TENANT_A),
)
voucher_id = voucher_resp.json()["id"]
validate_resp = await client.post(
f"/api/v1/posting/vouchers/{voucher_id}/validate",
headers=tenant_headers(TENANT_A),
)
assert validate_resp.status_code == 422
@pytest.mark.asyncio
async def test_trial_balance(client):
tb_resp = await client.post(
"/api/v1/ledger/trial-balance",
params={"fiscal_period_id": str(uuid.uuid4())},
headers=tenant_headers(TENANT_A),
)
assert tb_resp.status_code == 200
assert "is_balanced" in tb_resp.json()
@pytest.mark.asyncio
async def test_subsidiary_ledger_report(client):
year_resp = await client.post(
"/api/v1/fiscal/years",
json={
"name": "1404-sub",
"start_date": "2025-03-21",
"end_date": "2026-03-20",
"is_current": True,
},
headers=tenant_headers(TENANT_A),
)
assert year_resp.status_code == 201
year_id = year_resp.json()["id"]
period_resp = await client.post(
"/api/v1/fiscal/periods",
json={
"fiscal_year_id": year_id,
"name": "Farvardin",
"period_number": 1,
"start_date": "2025-03-21",
"end_date": "2025-04-20",
"is_current": True,
},
headers=tenant_headers(TENANT_A),
)
assert period_resp.status_code == 201
period_id = period_resp.json()["id"]
chart_resp = await client.post(
"/api/v1/accounts/charts",
json={"name": "COA Sub", "code": "COA-SUB"},
headers=tenant_headers(TENANT_A),
)
chart_id = chart_resp.json()["id"]
await client.post(
"/api/v1/accounts",
json={
"chart_id": chart_id,
"code": "1101",
"name": "صندوق",
"account_type": "asset",
"account_category": "current_asset",
"level": 3,
"is_postable": True,
"is_group": False,
},
headers=tenant_headers(TENANT_A),
)
resp = await client.post(
"/api/v1/reporting/subsidiary-ledger",
params={"fiscal_period_id": period_id},
headers=tenant_headers(TENANT_A),
)
assert resp.status_code == 200
body = resp.json()
assert "id" in body
data = body["data"]
if isinstance(data, str):
import json
data = json.loads(data)
assert data["mode"] == "summary"
assert data["level_mode"] == "subsidiary"
assert isinstance(data["rows"], list)
assert any(r.get("account_code") == "1101" for r in data["rows"])
@pytest.mark.asyncio
async def test_requires_tenant_header(client):
resp = await client.get("/api/v1/accounts")
assert resp.status_code == 400