from datetime import datetime from uuid import UUID from pydantic import BaseModel, Field class PermissionCreate(BaseModel): software_id: UUID permission_key: str = Field(min_length=1, max_length=128) display_name: str = Field(min_length=1, max_length=255) description: str | None = Field(default=None, max_length=2000) requires_approval: bool = True is_active: bool = True sort_order: int = 0 class PermissionUpdate(BaseModel): # Cap matches the String(255) column + the PermissionCreate field; # without it an overlong patch 1406s at the DB instead of 422ing. display_name: str | None = Field(default=None, min_length=1, max_length=255) description: str | None = Field(default=None, max_length=2000) requires_approval: bool | None = None is_active: bool | None = None sort_order: int | None = None class PermissionResponse(BaseModel): id: UUID software_id: UUID software_name: str | None = None permission_key: str display_name: str description: str | None requires_approval: bool is_active: bool sort_order: int created_at: datetime updated_at: datetime class UserPermissionRequest(BaseModel): permission_id: UUID reason: str = Field(min_length=1, max_length=2000) class UserPermissionResponse(BaseModel): id: UUID user_id: UUID user_name: str | None = None user_email: str | None = None user_employee_no: str | None = None user_employment_status: str | None = None permission_id: UUID permission_name: str permission_key: str software_id: UUID software_name: str | None = None status: str reason: str | None approval_request_id: UUID | None provisioning_task_id: UUID | None external_ref: str | None granted_at: datetime | None revoked_at: datetime | None created_at: datetime updated_at: datetime # ── In-app approval + validity (migration 606) ── requires_approval: bool = False expires_at: datetime | None = None approver_person_id: UUID | None = None approver_name: str | None = None decided_at: datetime | None = None decision_note: str | None = None #: Who may decide a ``pending_approval`` row (software owner / IT contact); #: empty means only IT (global_admin) can — the approver card is then #: routed to IT. pending_approver_labels: list[str] = Field(default_factory=list) #: True when the caller may approve / reject this row. can_decide: bool = False approval_waiting_days: int | None = None class ApprovalDecisionRequest(BaseModel): note: str | None = Field(default=None, max_length=2000) #: Validity in days the approver grants (NULL = open-ended / policy default). valid_days: int | None = Field(default=None, ge=1, le=1095) class PermissionApprovalSettingsBody(BaseModel): reminder_days: list[int] = Field(min_length=1, max_length=6) escalate_to_it_after_days: int = Field(ge=1, le=60) default_validity_days: int | None = Field(default=None, ge=7, le=1095) notify_holder_on_expiry: bool class AdminGrantRequest(BaseModel): """Admin grants a permission directly, bypassing the approval flow.""" user_id: UUID permission_id: UUID reason: str | None = Field(None, max_length=2000) #: Validity in days (migration 606). NULL = policy default / open-ended. valid_days: int | None = Field(default=None, ge=1, le=1095) class AdminRevokeRequest(BaseModel): reason: str | None = Field(None, max_length=2000) class AdminBatchRevokeRequest(BaseModel): grant_ids: list[UUID] = Field(min_length=1, max_length=500) reason: str | None = Field(None, max_length=2000)