feat(backend): add sellability reason codes and string price serialization to test seat preview

extend test seat preview with explicit sellability reason codes

serialize preview price amount as string for a stable API contract
improve diagnosis of non-sellable states for preview consumers
This commit is contained in:
greebo
2026-03-19 20:07:19 +03:00
parent af175d88dd
commit aab5a51654
4 changed files with 43 additions and 14 deletions

View File

@@ -30,12 +30,6 @@ async def preview_test_seat(
seat_id=seat_id,
)
if not seat.seat_id:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Невозможно построить preview: у места отсутствует seat_id",
)
matched_rule_level = None
matched_target_ref = None
pricing_category_id = None
@@ -43,6 +37,27 @@ async def preview_test_seat(
currency = None
has_price = False
if not seat.seat_id:
return TestSeatPreviewResponse(
scheme_id=scheme.scheme_id,
scheme_version_id=version.scheme_version_id,
seat_id=seat.seat_id,
element_id=seat.element_id,
sector_id=seat.sector_id,
group_id=seat.group_id,
row_label=seat.row_label,
seat_number=seat.seat_number,
selectable=False,
has_price=False,
matched_rule_level=None,
matched_target_ref=None,
pricing_category_id=None,
amount=None,
currency=None,
reason_code="missing_seat_id",
reason_message="Seat is not sellable because seat_id is missing.",
)
try:
matched_rule_level, rule = await find_effective_price_rule(
scheme_id=scheme.scheme_id,
@@ -52,7 +67,7 @@ async def preview_test_seat(
)
matched_target_ref = rule["target_ref"]
pricing_category_id = rule["pricing_category_id"]
amount = rule["amount"]
amount = str(rule["amount"])
currency = rule["currency"]
has_price = True
except HTTPException as exc:
@@ -66,15 +81,25 @@ async def preview_test_seat(
)
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Не удалось построить preview: {exc.__class__.__name__}: {exc}",
detail={
"code": "test_preview_failed",
"message": f"Не удалось построить preview: {exc.__class__.__name__}: {exc}",
},
)
if has_price:
reason_code = "ok"
reason_message = "Seat is sellable."
else:
reason_code = "no_price_rule"
reason_message = "Seat is not sellable because no effective price rule was found."
return TestSeatPreviewResponse(
scheme_id=scheme.scheme_id,
scheme_version_id=version.scheme_version_id,
seat_id=seat.seat_id,
element_id=seat.element_id,
sector_id=seat.sector_id,
sector_id=seat.seat_id and seat.sector_id,
group_id=seat.group_id,
row_label=seat.row_label,
seat_number=seat.seat_number,
@@ -85,4 +110,6 @@ async def preview_test_seat(
pricing_category_id=pricing_category_id,
amount=amount,
currency=currency,
reason_code=reason_code,
reason_message=reason_message,
)

View File

@@ -1,12 +1,10 @@
from decimal import Decimal
from pydantic import BaseModel
class TestSeatPreviewResponse(BaseModel):
scheme_id: str
scheme_version_id: str
seat_id: str
seat_id: str | None
element_id: str | None
sector_id: str | None
group_id: str | None
@@ -17,5 +15,7 @@ class TestSeatPreviewResponse(BaseModel):
matched_rule_level: str | None
matched_target_ref: str | None
pricing_category_id: str | None
amount: Decimal | None
amount: str | None
currency: str | None
reason_code: str
reason_message: str