diff --git a/finance/views.py b/finance/views.py index 5918efb..49dcf8d 100644 --- a/finance/views.py +++ b/finance/views.py @@ -7,6 +7,7 @@ import math import tempfile import uuid from datetime import timedelta +from decimal import Decimal from functools import wraps from pathlib import Path @@ -15,6 +16,7 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.models import User from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError +from django.core.serializers.json import DjangoJSONEncoder from django.db import IntegrityError, connection, transaction from django.http import FileResponse, HttpResponse, JsonResponse from django.middleware.csrf import get_token @@ -294,7 +296,7 @@ def query(request, dataset_id): ds = dataset(request, dataset_id, True) pref, _ = preferences(request, ds) limit(f"analysis:{request.user.pk}", 120, 60) - return JsonResponse(analyze(ds, request.member, body(request), pref)) + return JsonResponse(analyze(ds, request.member, body(request), pref), encoder=AmountJSONEncoder) def amount_text(value): @@ -305,6 +307,19 @@ def amount_text(value): return "0" if text == "-0" else text +class AmountJSONEncoder(DjangoJSONEncoder): + """分析接口的 Decimal 统一经 amount_text 输出。 + + PostgreSQL 把 NUMERIC(44,24) 原样读回,DjangoJSONEncoder 默认 str() 会把 + 存储尺度尾零直接暴露给前端;SQLite 无此尺度,两库表现需一致。 + """ + + def default(self, o): + if isinstance(o, Decimal): + return amount_text(o) + return super().default(o) + + @endpoint(("POST",)) def evidence(request, dataset_id): ds = dataset(request, dataset_id, True) diff --git a/tests/test_finance.py b/tests/test_finance.py index fafdeb3..3048467 100644 --- a/tests/test_finance.py +++ b/tests/test_finance.py @@ -192,6 +192,34 @@ def test_evidence_amounts_trim_storage_scale(workspace): assert row["gross"] == "50" +def test_query_amounts_trim_storage_scale(workspace): + from decimal import Decimal + + from finance.views import AmountJSONEncoder + + assert ( + AmountJSONEncoder().encode({"current": Decimal("1000.000000000000000000000000")}) + == '{"current": "1000"}' + ) + + ds, _, client, *_ = workspace + response = client.post( + f"/api/datasets/{ds.pk}/query", + data={ + "start": "2026-08", + "end": "2026-08", + "comparison": "yoy", + "group": "department", + "metric": "revenue", + }, + content_type="application/json", + ) + assert response.status_code == 200 + summary = response.json()["summary"] + assert summary["revenue"]["current"] == "90" + assert summary["revenue"]["baseline"] == "150" + + def test_settings_revision_and_invalid_metric_are_rejected(workspace): ds, _, client, *_ = workspace url = f"/api/datasets/{ds.pk}/settings"