Add the Healthcare platform backend (phases 13.0–13.7) with Alembic migration revision fix, production deploy script, validation reports, shared UI exports, and loyalty list page client directives so Next.js build succeeds. Co-authored-by: Cursor <cursoragent@cursor.com>
56 lines
1.7 KiB
Python
56 lines
1.7 KiB
Python
"""Reusable validators for Delivery foundation."""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
from shared.exceptions import AppError
|
|
|
|
CODE_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_\-]{1,49}$")
|
|
|
|
|
|
class ValidationError(AppError):
|
|
def __init__(self, message: str, details: dict[str, Any] | None = None):
|
|
super().__init__(
|
|
message=message,
|
|
error_code="validation_error",
|
|
status_code=422,
|
|
details=details or {},
|
|
)
|
|
|
|
|
|
def validate_non_empty(value: str | None, field: str) -> str:
|
|
if value is None or not str(value).strip():
|
|
raise ValidationError(f"{field} الزامی است", {"field": field})
|
|
return str(value).strip()
|
|
|
|
|
|
def validate_code(code: str) -> str:
|
|
code = validate_non_empty(code, "code")
|
|
if not CODE_RE.match(code):
|
|
raise ValidationError(
|
|
"کد نامعتبر است",
|
|
{"field": "code", "pattern": CODE_RE.pattern},
|
|
)
|
|
return code
|
|
|
|
|
|
def validate_currency_code(code: str) -> str:
|
|
code = validate_non_empty(code, "currency_code").upper()
|
|
if len(code) != 3:
|
|
raise ValidationError("currency_code باید ۳ حرفی باشد", {"field": "currency_code"})
|
|
return code
|
|
|
|
|
|
def ensure_optimistic_version(entity: Any, expected: int) -> None:
|
|
current = getattr(entity, "version", None)
|
|
if current is None:
|
|
return
|
|
if current != expected:
|
|
raise AppError(
|
|
"نسخه موجودیت همخوان نیست",
|
|
error_code="version_conflict",
|
|
status_code=409,
|
|
details={"expected": expected, "actual": current},
|
|
)
|