339 lines
8.2 KiB
Python
339 lines
8.2 KiB
Python
from datetime import datetime
|
||
import enum
|
||
from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar
|
||
|
||
FormPhaseRole = Literal["DFIP", "EXECUTOR_RF"]
|
||
|
||
|
||
from pydantic import BaseModel, ConfigDict, EmailStr, Field
|
||
|
||
T = TypeVar("T")
|
||
|
||
ProjectTypeLiteral = Literal[
|
||
"Открытие ВСП",
|
||
"Закрытие ВСП",
|
||
"Переезд ВСП",
|
||
"Реновация РФ",
|
||
"Открытие УРМ",
|
||
]
|
||
|
||
VspFormatLiteral = Literal[
|
||
"Флагманский",
|
||
"Типовой",
|
||
"Розничный",
|
||
"МСБ",
|
||
"Лёгкий",
|
||
"Мини",
|
||
"Розничный-киоск",
|
||
"МБО",
|
||
"Офис самообслуживания",
|
||
"Другое",
|
||
]
|
||
|
||
PlacementTypeLiteral = Literal[
|
||
"Собственность",
|
||
"Аренда",
|
||
"Субаренда",
|
||
]
|
||
|
||
|
||
class ResponseBase(BaseModel):
|
||
success: Optional[bool] = Field(True)
|
||
message: Optional[str] = None
|
||
|
||
|
||
class BaseSingleResponse(ResponseBase, Generic[T]):
|
||
result: Optional[T] = None
|
||
|
||
|
||
class BaseListResponse(ResponseBase, Generic[T]):
|
||
result: Optional[list[T]] = None
|
||
count: Optional[int] = None
|
||
|
||
|
||
class UserBase(BaseModel):
|
||
email: EmailStr = Field(..., examples=["user@example.com"])
|
||
username: str = Field(
|
||
...,
|
||
min_length=3,
|
||
max_length=64,
|
||
pattern=r"^[A-Za-z0-9_.-]+$",
|
||
examples=["user_1"],
|
||
)
|
||
full_name: Optional[str] = Field(
|
||
None,
|
||
max_length=255,
|
||
pattern=r"^[^<>]*$",
|
||
examples=["Иван Иванов"],
|
||
)
|
||
role_id: int = 3
|
||
|
||
|
||
class UserCreate(UserBase):
|
||
password: str | None = Field(default=None, examples=["pass123"])
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"email": "user@example.com",
|
||
"username": "user_1",
|
||
"full_name": "Иван Иванов",
|
||
"role_id": 3,
|
||
"password": "pass123",
|
||
}
|
||
}
|
||
)
|
||
|
||
|
||
class UserUpdate(BaseModel):
|
||
email: Optional[EmailStr] = Field(default=None, examples=["user@example.com"])
|
||
username: Optional[str] = Field(
|
||
None,
|
||
min_length=3,
|
||
max_length=64,
|
||
pattern=r"^[A-Za-z0-9_.-]+$",
|
||
examples=["user_1"],
|
||
)
|
||
full_name: Optional[str] = Field(
|
||
None,
|
||
max_length=255,
|
||
pattern=r"^[^<>]*$",
|
||
examples=["Иван Иванов"],
|
||
)
|
||
role_id: Optional[int] = None
|
||
is_active: Optional[bool] = True
|
||
|
||
|
||
class UserInDB(UserBase):
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
id: int
|
||
created_at: datetime
|
||
updated_at: Optional[datetime] = None
|
||
is_active: bool
|
||
|
||
|
||
class User(UserInDB):
|
||
pass
|
||
|
||
|
||
class UserResponse(ResponseBase):
|
||
result: Optional[UserInDB] = None
|
||
|
||
|
||
class UserListResponse(ResponseBase):
|
||
result: Optional[list[User]] = None
|
||
|
||
|
||
class SSPBase(BaseModel):
|
||
title: str
|
||
is_ssp: bool = True
|
||
is_active: bool = True
|
||
|
||
|
||
class SSPInDB(SSPBase):
|
||
model_config = ConfigDict(from_attributes=True)
|
||
id: int
|
||
|
||
|
||
class SSPIDList(BaseModel):
|
||
ssp_ids: list[int]
|
||
|
||
|
||
class UserSSPLinkBase(BaseModel):
|
||
user_id: int
|
||
ssp: list[SSPInDB]
|
||
|
||
|
||
class UserSSPLinkResponse(ResponseBase):
|
||
result: Optional[UserSSPLinkBase] = None
|
||
|
||
|
||
class Token(BaseModel):
|
||
access_token: str
|
||
token_type: str = "bearer"
|
||
expires_in: Optional[int] = None
|
||
refresh_token: Optional[str] = None
|
||
|
||
|
||
class LoginRequest(BaseModel):
|
||
username: str
|
||
password: str
|
||
|
||
|
||
class RefreshRequest(BaseModel):
|
||
refresh_token: str
|
||
|
||
|
||
class FormTypeSchemaEnum(str, enum.Enum):
|
||
FORM_1 = "FORM_1"
|
||
FORM_2 = "FORM_2"
|
||
FORM_3 = "FORM_3"
|
||
FORM_4 = "FORM_4"
|
||
|
||
|
||
class OrgUnitSchema(BaseModel):
|
||
id: int
|
||
title: str
|
||
is_active: bool
|
||
is_ssp: bool
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
class BudgetFormResponse(BaseModel):
|
||
id: int
|
||
created_at: datetime
|
||
created_by: int | None
|
||
updated_at: datetime
|
||
form_type_code: FormTypeSchemaEnum
|
||
year: int
|
||
org_unit_id: int
|
||
org_unit: OrgUnitSchema | None = None
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
class SheetFormTypeResponse(BaseModel):
|
||
form_type: FormTypeSchemaEnum
|
||
sheets: list[str]
|
||
|
||
|
||
class DirectionSchemaEnum(str, enum.Enum):
|
||
SUPPORT = "Support"
|
||
DEVELOPMENT = "Development"
|
||
|
||
|
||
class SheetResponse(BaseModel):
|
||
row_type: str
|
||
depth: int
|
||
data: dict
|
||
sort_order: int | None = None
|
||
|
||
|
||
class CellPatch(BaseModel):
|
||
line_id: int
|
||
column: str
|
||
value: Any = None
|
||
|
||
|
||
class CellsPatch(BaseModel):
|
||
changes: list[CellPatch]
|
||
|
||
|
||
class AddForm3LineBody(BaseModel):
|
||
expense_item_id: int
|
||
|
||
|
||
class UpdProjectBody(BaseModel):
|
||
column: str
|
||
value: Any = None
|
||
|
||
|
||
class AddProjectBody(BaseModel):
|
||
name: str = Field(
|
||
...,
|
||
max_length=30,
|
||
pattern=r"^[^+\-\/\\=&*\s]{1,30}$",
|
||
)
|
||
year: int
|
||
branch_id: int
|
||
level: Literal["project", "program"] = "project"
|
||
parent_id: Optional[int] = None
|
||
ssp_id: Optional[int] = None
|
||
project_type: Optional[ProjectTypeLiteral] = None
|
||
vsp_format: Optional[VspFormatLiteral] = None
|
||
placement_type: Optional[PlacementTypeLiteral] = None
|
||
object_address: Optional[str] = None
|
||
staff_count: Optional[int] = None
|
||
total_area: Optional[float] = None
|
||
|
||
|
||
class AddLineSchema(BaseModel):
|
||
expense_item_id: Optional[int] = None
|
||
item_id: Optional[str] = None
|
||
section_code: Optional[str] = None
|
||
direction: Optional[DirectionSchemaEnum] = None
|
||
name: Optional[str] = None
|
||
internal_order: Optional[str] = None
|
||
vsp_id: Optional[int] = None
|
||
project_id: Optional[int] = None
|
||
justification: Optional[str] = None
|
||
contract_number: Optional[str] = None
|
||
contract_end_date: Optional[datetime] = None
|
||
|
||
|
||
class FormPhaseResponse(BaseModel):
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
budget_form_id: int
|
||
sheet: str
|
||
phase_code: str
|
||
role: str
|
||
column_keys: list[str]
|
||
opens_at: datetime
|
||
closes_at: datetime
|
||
|
||
|
||
class FormPhaseCreate(BaseModel):
|
||
sheet: str
|
||
phase_code: str
|
||
role: FormPhaseRole
|
||
column_keys: list[str]
|
||
opens_at: datetime
|
||
closes_at: datetime
|
||
|
||
|
||
class FormPhaseUpdate(BaseModel):
|
||
role: FormPhaseRole | None = None
|
||
column_keys: list[str] | None = None
|
||
opens_at: datetime | None = None
|
||
closes_at: datetime | None = None
|
||
|
||
|
||
class AuditLogBase(BaseModel):
|
||
"""Базовая схема записи аудита (единый формат вывода как у auditlog)."""
|
||
|
||
entity: str = Field(..., description="Тип сущности")
|
||
entity_id: Optional[int] = Field(None, description="ID сущности")
|
||
action: str = Field(..., description="Действие")
|
||
payload_json: Optional[Dict[str, Any]] = None
|
||
|
||
|
||
class AuditLogInDB(AuditLogBase):
|
||
"""Схема записи аудита в базе данных."""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
id: int = Field(..., description="ID записи")
|
||
user_id: Optional[int] = Field(None, description="ID пользователя")
|
||
at: datetime = Field(..., description="Дата/время события")
|
||
|
||
|
||
class AuditLog(AuditLogInDB):
|
||
"""Схема записи аудита для ответа API."""
|
||
|
||
user: Optional[User] = None
|
||
|
||
|
||
class AuditLogListResponse(ResponseBase):
|
||
"""Схема всех записей аудита для ответа API."""
|
||
|
||
result: List[AuditLog] = Field(..., description="Вывод записей аудита")
|
||
|
||
|
||
class AuditLogQueryParams(BaseModel):
|
||
"""Query-параметры для фильтрации журнала аудита."""
|
||
|
||
page: int = Field(1, ge=1, description="Номер страницы")
|
||
limit: int = Field(
|
||
20, ge=1, le=100, description="Количество записей на странице"
|
||
)
|
||
user_id: Optional[int] = Field(None, description="ID пользователя")
|
||
org_unit_id: Optional[int] = Field(None, description="ID ССП")
|
||
task_id: Optional[int] = Field(None, description="ID задачи")
|
||
form_id: Optional[int] = Field(None, description="ID формы")
|
||
event_type: Optional[str] = Field(None, description="Тип события")
|
||
date_from: Optional[datetime] = Field(None, description="Дата начала (ISO 8601)")
|
||
date_to: Optional[datetime] = Field(None, description="Дата окончания (ISO 8601)")
|