feat(backend): harden pricing mutation contract and sync backend docs

- add typed response schemas for pricing write endpoints
- add stale draft version guard for pricing mutations
- unify pricing API contract around expected_scheme_version_id
- update API route map
- add smoke regression checklist for backend routes and artifact flows
This commit is contained in:
greebo
2026-03-19 19:11:33 +03:00
parent c7c9184a71
commit fbeac890be
5 changed files with 197 additions and 215 deletions

View File

@@ -1,22 +1,4 @@
from decimal import Decimal, InvalidOperation
from pydantic import BaseModel, Field, field_validator
def _validate_decimal_amount(value: Decimal) -> Decimal:
try:
normalized = Decimal(value)
except (InvalidOperation, TypeError, ValueError) as exc:
raise ValueError("Некорректная сумма") from exc
if not normalized.is_finite():
raise ValueError("Некорректная сумма")
return normalized
class DeleteResponse(BaseModel):
status: str
from pydantic import BaseModel, Field
class PricingCategoryCreateRequest(BaseModel):
@@ -29,6 +11,22 @@ class PricingCategoryUpdateRequest(BaseModel):
code: str | None = Field(default=None, max_length=128)
class PriceRuleCreateRequest(BaseModel):
pricing_category_id: str = Field(..., max_length=32)
target_type: str = Field(..., pattern="^(seat|group|sector)$")
target_ref: str = Field(..., min_length=1, max_length=128)
amount: str = Field(..., min_length=1, max_length=32)
currency: str = Field(default="RUB", min_length=3, max_length=8)
class PriceRuleUpdateRequest(BaseModel):
pricing_category_id: str = Field(..., max_length=32)
target_type: str = Field(..., pattern="^(seat|group|sector)$")
target_ref: str = Field(..., min_length=1, max_length=128)
amount: str = Field(..., min_length=1, max_length=32)
currency: str = Field(default="RUB", min_length=3, max_length=8)
class PricingCategoryItem(BaseModel):
pricing_category_id: str
scheme_id: str
@@ -37,6 +35,22 @@ class PricingCategoryItem(BaseModel):
created_at: str
class PriceRuleItem(BaseModel):
price_rule_id: str
scheme_id: str
pricing_category_id: str | None
target_type: str
target_ref: str
amount: str
currency: str
created_at: str
class PricingBundleResponse(BaseModel):
categories: list[PricingCategoryItem]
rules: list[PriceRuleItem]
class PricingCategoryCreateResponse(BaseModel):
pricing_category_id: str
scheme_id: str
@@ -51,50 +65,13 @@ class PricingCategoryUpdateResponse(BaseModel):
code: str | None
class PriceRuleCreateRequest(BaseModel):
pricing_category_id: str | None = Field(default=None, max_length=32)
target_type: str = Field(..., pattern="^(seat|group|sector)$")
target_ref: str = Field(..., min_length=1, max_length=128)
amount: Decimal
currency: str = Field(default="RUB", min_length=3, max_length=8)
@field_validator("amount")
@classmethod
def validate_amount(cls, value: Decimal) -> Decimal:
return _validate_decimal_amount(value)
class PriceRuleUpdateRequest(BaseModel):
pricing_category_id: str | None = Field(default=None, max_length=32)
target_type: str = Field(..., pattern="^(seat|group|sector)$")
target_ref: str = Field(..., min_length=1, max_length=128)
amount: Decimal
currency: str = Field(default="RUB", min_length=3, max_length=8)
@field_validator("amount")
@classmethod
def validate_amount(cls, value: Decimal) -> Decimal:
return _validate_decimal_amount(value)
class PriceRuleItem(BaseModel):
price_rule_id: str
scheme_id: str
pricing_category_id: str | None
target_type: str
target_ref: str
amount: Decimal | str
currency: str
created_at: str
class PriceRuleCreateResponse(BaseModel):
price_rule_id: str
scheme_id: str
pricing_category_id: str | None
pricing_category_id: str
target_type: str
target_ref: str
amount: Decimal
amount: str
currency: str
@@ -104,10 +81,16 @@ class PriceRuleUpdateResponse(BaseModel):
pricing_category_id: str | None
target_type: str
target_ref: str
amount: Decimal
amount: str
currency: str
class DeleteResponse(BaseModel):
deleted: bool
pricing_category_id: str | None = None
price_rule_id: str | None = None
class EffectiveSeatPriceResponse(BaseModel):
scheme_id: str
scheme_version_id: str
@@ -117,15 +100,5 @@ class EffectiveSeatPriceResponse(BaseModel):
matched_rule_level: str
matched_target_ref: str
pricing_category_id: str | None
amount: Decimal | str
amount: str
currency: str
class SchemePricingResponse(BaseModel):
categories: list[PricingCategoryItem]
rules: list[PriceRuleItem]
class PricingBundleResponse(BaseModel):
categories: list[PricingCategoryItem]
rules: list[PriceRuleItem]