feat: add search
This commit is contained in:
@@ -32,7 +32,7 @@ class ILegalEntityRepository(ABC):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def list_all(self, *, limit: int, offset: int) -> list[LegalEntityEntity]:
|
async def list_all(self, *, limit: int, offset: int, search: str | None = None) -> list[LegalEntityEntity]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -57,5 +57,5 @@ class ILegalEntityRepository(ABC):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def count_all(self) -> int:
|
async def count_all(self, *, search: str | None = None) -> int:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from abc import ABC, abstractmethod
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from src.application.domain.entities import UserEntity
|
from src.application.domain.entities import UserEntity
|
||||||
|
from src.application.domain.entities.organization import PartySearchEntity
|
||||||
|
|
||||||
|
|
||||||
class IUserRepository(ABC):
|
class IUserRepository(ABC):
|
||||||
@@ -25,3 +26,11 @@ class IUserRepository(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def exists_by_email(self, email: str) -> bool:
|
async def exists_by_email(self, email: str) -> bool:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def search_individuals(self, *, query: str, limit: int, offset: int) -> list[PartySearchEntity]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def count_individuals(self, *, query: str) -> int:
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from src.application.commands.put_organization_document import PutOrganizationDo
|
|||||||
from src.application.commands.organization_commands import (
|
from src.application.commands.organization_commands import (
|
||||||
ListOrganizationsCommand,
|
ListOrganizationsCommand,
|
||||||
GetOrganizationCommand,
|
GetOrganizationCommand,
|
||||||
|
SearchPartiesCommand,
|
||||||
UpdateOrganizationCommand,
|
UpdateOrganizationCommand,
|
||||||
)
|
)
|
||||||
from src.application.commands.organization_document_commands import (
|
from src.application.commands.organization_document_commands import (
|
||||||
@@ -36,6 +37,7 @@ __all__ = [
|
|||||||
'PutOrganizationDocumentCommand',
|
'PutOrganizationDocumentCommand',
|
||||||
'ListOrganizationsCommand',
|
'ListOrganizationsCommand',
|
||||||
'GetOrganizationCommand',
|
'GetOrganizationCommand',
|
||||||
|
'SearchPartiesCommand',
|
||||||
'UpdateOrganizationCommand',
|
'UpdateOrganizationCommand',
|
||||||
'ListPurchaseRequestsCommand',
|
'ListPurchaseRequestsCommand',
|
||||||
'GetPurchaseRequestCommand',
|
'GetPurchaseRequestCommand',
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from typing import Any
|
|||||||
|
|
||||||
from src.application.abstractions import IUnitOfWork
|
from src.application.abstractions import IUnitOfWork
|
||||||
from src.application.contracts import ILogger
|
from src.application.contracts import ILogger
|
||||||
from src.application.domain.entities.organization import LegalEntityEntity
|
from src.application.domain.entities.organization import LegalEntityEntity, PartySearchEntity
|
||||||
|
from src.application.domain.enums.account_type import AccountType
|
||||||
from src.infrastructure.database.decorators import transactional
|
from src.infrastructure.database.decorators import transactional
|
||||||
|
|
||||||
|
|
||||||
@@ -14,12 +15,65 @@ class ListOrganizationsCommand:
|
|||||||
self._logger = logger
|
self._logger = logger
|
||||||
|
|
||||||
@transactional
|
@transactional
|
||||||
async def __call__(self, *, limit: int = 50, offset: int = 0) -> tuple[list[LegalEntityEntity], int]:
|
async def __call__(
|
||||||
items = await self._unit_of_work.legal_entity_repository.list_all(limit=limit, offset=offset)
|
self,
|
||||||
total = await self._unit_of_work.legal_entity_repository.count_all()
|
*,
|
||||||
|
limit: int = 50,
|
||||||
|
offset: int = 0,
|
||||||
|
search: str | None = None,
|
||||||
|
) -> tuple[list[LegalEntityEntity], int]:
|
||||||
|
items = await self._unit_of_work.legal_entity_repository.list_all(
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
search=search,
|
||||||
|
)
|
||||||
|
total = await self._unit_of_work.legal_entity_repository.count_all(search=search)
|
||||||
return items, total
|
return items, total
|
||||||
|
|
||||||
|
|
||||||
|
class SearchPartiesCommand:
|
||||||
|
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
|
||||||
|
self._unit_of_work = unit_of_work
|
||||||
|
self._logger = logger
|
||||||
|
|
||||||
|
@transactional
|
||||||
|
async def __call__(self, *, query: str, limit: int = 50, offset: int = 0) -> tuple[list[PartySearchEntity], int]:
|
||||||
|
query = query.strip()
|
||||||
|
if not query:
|
||||||
|
return [], 0
|
||||||
|
|
||||||
|
fetch_limit = limit + offset
|
||||||
|
legal_items = await self._unit_of_work.legal_entity_repository.list_all(
|
||||||
|
limit=fetch_limit,
|
||||||
|
offset=0,
|
||||||
|
search=query,
|
||||||
|
)
|
||||||
|
individual_items = await self._unit_of_work.user_repository.search_individuals(
|
||||||
|
query=query,
|
||||||
|
limit=fetch_limit,
|
||||||
|
offset=0,
|
||||||
|
)
|
||||||
|
legal_total = await self._unit_of_work.legal_entity_repository.count_all(search=query)
|
||||||
|
individual_total = await self._unit_of_work.user_repository.count_individuals(query=query)
|
||||||
|
items = [self._legal_to_search_entity(item) for item in legal_items] + individual_items
|
||||||
|
items.sort(key=lambda item: item.created_at.timestamp() if item.created_at else 0, reverse=True)
|
||||||
|
return items[offset:offset + limit], legal_total + individual_total
|
||||||
|
|
||||||
|
def _legal_to_search_entity(self, item: LegalEntityEntity) -> PartySearchEntity:
|
||||||
|
return PartySearchEntity(
|
||||||
|
id=item.id,
|
||||||
|
account_type=AccountType.LEGAL_ENTITY,
|
||||||
|
user_id=item.user_id,
|
||||||
|
email=None,
|
||||||
|
name=item.name,
|
||||||
|
inn=item.inn,
|
||||||
|
phone=item.contact_phone,
|
||||||
|
status=item.status,
|
||||||
|
kyc_verified=item.kyc_verified,
|
||||||
|
created_at=item.created_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GetOrganizationCommand:
|
class GetOrganizationCommand:
|
||||||
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
|
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
|
||||||
self._unit_of_work = unit_of_work
|
self._unit_of_work = unit_of_work
|
||||||
|
|||||||
@@ -56,6 +56,20 @@ class OrganizationDocumentSlot:
|
|||||||
file_size_bytes: int | None = None
|
file_size_bytes: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PartySearchEntity:
|
||||||
|
id: str
|
||||||
|
account_type: str
|
||||||
|
user_id: str
|
||||||
|
email: str | None
|
||||||
|
name: str | None
|
||||||
|
inn: str | None
|
||||||
|
phone: str | None
|
||||||
|
status: str | None = None
|
||||||
|
kyc_verified: bool | None = None
|
||||||
|
created_at: datetime | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PurchaseRequestEntity:
|
class PurchaseRequestEntity:
|
||||||
id: str
|
id: str
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import func, select, update
|
from sqlalchemy import func, or_, select, update
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ from src.application.contracts import ILogger
|
|||||||
from src.application.domain.entities.organization import LegalEntityEntity
|
from src.application.domain.entities.organization import LegalEntityEntity
|
||||||
from src.application.domain.exceptions import ApplicationException
|
from src.application.domain.exceptions import ApplicationException
|
||||||
from src.application.domain.organization_documents import DOCUMENT_TYPE_TO_COLUMN, ORGANIZATION_DOCUMENT_TYPES
|
from src.application.domain.organization_documents import DOCUMENT_TYPE_TO_COLUMN, ORGANIZATION_DOCUMENT_TYPES
|
||||||
from src.infrastructure.database.models import LegalEntityModel
|
from src.infrastructure.database.models import LegalEntityModel, UserModel
|
||||||
|
|
||||||
|
|
||||||
class LegalEntityRepository(ILegalEntityRepository):
|
class LegalEntityRepository(ILegalEntityRepository):
|
||||||
@@ -49,6 +49,37 @@ class LegalEntityRepository(ILegalEntityRepository):
|
|||||||
updated_at=m.updated_at,
|
updated_at=m.updated_at,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _search_filter(self, search: str | None):
|
||||||
|
if not search or not search.strip():
|
||||||
|
return None
|
||||||
|
|
||||||
|
pattern = f'%{search.strip()}%'
|
||||||
|
full_name = func.concat_ws(
|
||||||
|
' ',
|
||||||
|
UserModel.last_name,
|
||||||
|
UserModel.first_name,
|
||||||
|
UserModel.middle_name,
|
||||||
|
)
|
||||||
|
return or_(
|
||||||
|
LegalEntityModel.id.ilike(pattern),
|
||||||
|
LegalEntityModel.name.ilike(pattern),
|
||||||
|
LegalEntityModel.short_name.ilike(pattern),
|
||||||
|
LegalEntityModel.inn.ilike(pattern),
|
||||||
|
LegalEntityModel.ogrn.ilike(pattern),
|
||||||
|
LegalEntityModel.kpp.ilike(pattern),
|
||||||
|
LegalEntityModel.legal_address.ilike(pattern),
|
||||||
|
LegalEntityModel.actual_address.ilike(pattern),
|
||||||
|
LegalEntityModel.contact_person.ilike(pattern),
|
||||||
|
LegalEntityModel.contact_phone.ilike(pattern),
|
||||||
|
UserModel.email.ilike(pattern),
|
||||||
|
UserModel.last_name.ilike(pattern),
|
||||||
|
UserModel.first_name.ilike(pattern),
|
||||||
|
UserModel.middle_name.ilike(pattern),
|
||||||
|
UserModel.phone.ilike(pattern),
|
||||||
|
UserModel.inn.ilike(pattern),
|
||||||
|
full_name.ilike(pattern),
|
||||||
|
)
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -102,14 +133,37 @@ class LegalEntityRepository(ILegalEntityRepository):
|
|||||||
raise ApplicationException(status_code=404, message='Organization not found')
|
raise ApplicationException(status_code=404, message='Organization not found')
|
||||||
return self._to_entity(m)
|
return self._to_entity(m)
|
||||||
|
|
||||||
async def list_all(self, *, limit: int, offset: int) -> list[LegalEntityEntity]:
|
async def list_all(
|
||||||
res = await self._session.execute(
|
self,
|
||||||
select(LegalEntityModel).order_by(LegalEntityModel.created_at.desc()).limit(limit).offset(offset)
|
*,
|
||||||
|
limit: int,
|
||||||
|
offset: int,
|
||||||
|
search: str | None = None,
|
||||||
|
) -> list[LegalEntityEntity]:
|
||||||
|
stmt = (
|
||||||
|
select(LegalEntityModel)
|
||||||
|
.join(UserModel, UserModel.id == LegalEntityModel.user_id)
|
||||||
|
.where(UserModel.is_deleted.is_(False))
|
||||||
|
.order_by(LegalEntityModel.created_at.desc())
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
)
|
)
|
||||||
|
search_filter = self._search_filter(search)
|
||||||
|
if search_filter is not None:
|
||||||
|
stmt = stmt.where(search_filter)
|
||||||
|
res = await self._session.execute(stmt)
|
||||||
return [self._to_entity(m) for m in res.scalars().all()]
|
return [self._to_entity(m) for m in res.scalars().all()]
|
||||||
|
|
||||||
async def count_all(self) -> int:
|
async def count_all(self, *, search: str | None = None) -> int:
|
||||||
res = await self._session.execute(select(func.count()).select_from(LegalEntityModel))
|
stmt = (
|
||||||
|
select(func.count(LegalEntityModel.id))
|
||||||
|
.join(UserModel, UserModel.id == LegalEntityModel.user_id)
|
||||||
|
.where(UserModel.is_deleted.is_(False))
|
||||||
|
)
|
||||||
|
search_filter = self._search_filter(search)
|
||||||
|
if search_filter is not None:
|
||||||
|
stmt = stmt.where(search_filter)
|
||||||
|
res = await self._session.execute(stmt)
|
||||||
return int(res.scalar_one())
|
return int(res.scalar_one())
|
||||||
|
|
||||||
async def update(self, organization_id: str, *, values: dict[str, Any]) -> LegalEntityEntity:
|
async def update(self, organization_id: str, *, values: dict[str, Any]) -> LegalEntityEntity:
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ from __future__ import annotations
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from fastapi import status
|
from fastapi import status
|
||||||
from sqlalchemy import select
|
from sqlalchemy import func, or_, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||||
|
|
||||||
from src.application.abstractions.repositories import IUserRepository
|
from src.application.abstractions.repositories import IUserRepository
|
||||||
from src.application.contracts import ILogger
|
from src.application.contracts import ILogger
|
||||||
from src.application.domain.entities import UserEntity
|
from src.application.domain.entities import UserEntity
|
||||||
|
from src.application.domain.entities.organization import PartySearchEntity
|
||||||
from src.application.domain.enums.account_type import AccountType
|
from src.application.domain.enums.account_type import AccountType
|
||||||
from src.application.domain.exceptions import ApplicationException
|
from src.application.domain.exceptions import ApplicationException
|
||||||
from src.infrastructure.database.models import UserModel
|
from src.infrastructure.database.models import UserModel
|
||||||
@@ -92,3 +93,69 @@ class UserRepository(IUserRepository):
|
|||||||
stmt = select(UserModel.id).where(UserModel.email == email, UserModel.is_deleted.is_(False)).limit(1)
|
stmt = select(UserModel.id).where(UserModel.email == email, UserModel.is_deleted.is_(False)).limit(1)
|
||||||
result = await self._session.execute(stmt)
|
result = await self._session.execute(stmt)
|
||||||
return result.scalar_one_or_none() is not None
|
return result.scalar_one_or_none() is not None
|
||||||
|
|
||||||
|
def _individual_search_filter(self, query: str):
|
||||||
|
pattern = f'%{query.strip()}%'
|
||||||
|
full_name = func.concat_ws(
|
||||||
|
' ',
|
||||||
|
UserModel.last_name,
|
||||||
|
UserModel.first_name,
|
||||||
|
UserModel.middle_name,
|
||||||
|
)
|
||||||
|
return or_(
|
||||||
|
UserModel.id.ilike(pattern),
|
||||||
|
UserModel.email.ilike(pattern),
|
||||||
|
UserModel.last_name.ilike(pattern),
|
||||||
|
UserModel.first_name.ilike(pattern),
|
||||||
|
UserModel.middle_name.ilike(pattern),
|
||||||
|
UserModel.phone.ilike(pattern),
|
||||||
|
UserModel.passport_data.ilike(pattern),
|
||||||
|
UserModel.inn.ilike(pattern),
|
||||||
|
UserModel.erc20.ilike(pattern),
|
||||||
|
full_name.ilike(pattern),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _to_search_entity(self, user: UserModel) -> PartySearchEntity:
|
||||||
|
name = ' '.join(
|
||||||
|
part for part in (user.last_name, user.first_name, user.middle_name)
|
||||||
|
if part
|
||||||
|
) or None
|
||||||
|
return PartySearchEntity(
|
||||||
|
id=user.id,
|
||||||
|
account_type=user.account_type,
|
||||||
|
user_id=user.id,
|
||||||
|
email=user.email,
|
||||||
|
name=name,
|
||||||
|
inn=user.inn,
|
||||||
|
phone=user.phone,
|
||||||
|
status=None,
|
||||||
|
kyc_verified=user.kyc_verified,
|
||||||
|
created_at=user.created_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def search_individuals(self, *, query: str, limit: int, offset: int) -> list[PartySearchEntity]:
|
||||||
|
stmt = (
|
||||||
|
select(UserModel)
|
||||||
|
.where(
|
||||||
|
UserModel.is_deleted.is_(False),
|
||||||
|
UserModel.account_type == AccountType.INDIVIDUAL,
|
||||||
|
self._individual_search_filter(query),
|
||||||
|
)
|
||||||
|
.order_by(UserModel.created_at.desc())
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
)
|
||||||
|
result = await self._session.execute(stmt)
|
||||||
|
return [self._to_search_entity(user) for user in result.scalars().all()]
|
||||||
|
|
||||||
|
async def count_individuals(self, *, query: str) -> int:
|
||||||
|
stmt = (
|
||||||
|
select(func.count(UserModel.id))
|
||||||
|
.where(
|
||||||
|
UserModel.is_deleted.is_(False),
|
||||||
|
UserModel.account_type == AccountType.INDIVIDUAL,
|
||||||
|
self._individual_search_filter(query),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result = await self._session.execute(stmt)
|
||||||
|
return int(result.scalar_one())
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from src.application.commands import (
|
|||||||
GetOrganizationDocumentCommand,
|
GetOrganizationDocumentCommand,
|
||||||
ListOrganizationDocumentsCommand,
|
ListOrganizationDocumentsCommand,
|
||||||
ListPurchaseRequestsCommand,
|
ListPurchaseRequestsCommand,
|
||||||
|
SearchPartiesCommand,
|
||||||
SetPurchaseRequestQuoteCommand,
|
SetPurchaseRequestQuoteCommand,
|
||||||
UpdateOrganizationCommand,
|
UpdateOrganizationCommand,
|
||||||
UpdatePurchaseRequestStatusCommand,
|
UpdatePurchaseRequestStatusCommand,
|
||||||
@@ -129,6 +130,13 @@ def get_list_organizations_command(
|
|||||||
return ListOrganizationsCommand(uow, logger)
|
return ListOrganizationsCommand(uow, logger)
|
||||||
|
|
||||||
|
|
||||||
|
def get_search_parties_command(
|
||||||
|
uow: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
|
logger: ILogger = Depends(get_logger),
|
||||||
|
) -> SearchPartiesCommand:
|
||||||
|
return SearchPartiesCommand(uow, logger)
|
||||||
|
|
||||||
|
|
||||||
def get_get_organization_command(
|
def get_get_organization_command(
|
||||||
uow: IUnitOfWork = Depends(get_unit_of_work),
|
uow: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
logger: ILogger = Depends(get_logger),
|
logger: ILogger = Depends(get_logger),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from src.application.commands import (
|
|||||||
GetOrganizationSecretKeysCommand,
|
GetOrganizationSecretKeysCommand,
|
||||||
ListOrganizationWalletsCommand,
|
ListOrganizationWalletsCommand,
|
||||||
ListOrganizationsCommand,
|
ListOrganizationsCommand,
|
||||||
|
SearchPartiesCommand,
|
||||||
UpdateOrganizationCommand,
|
UpdateOrganizationCommand,
|
||||||
)
|
)
|
||||||
from src.application.domain.dto import AdminAuthContext
|
from src.application.domain.dto import AdminAuthContext
|
||||||
@@ -20,12 +21,14 @@ from src.presentation.dependencies.commands import (
|
|||||||
get_get_organization_secret_keys_command,
|
get_get_organization_secret_keys_command,
|
||||||
get_list_organization_wallets_command,
|
get_list_organization_wallets_command,
|
||||||
get_list_organizations_command,
|
get_list_organizations_command,
|
||||||
|
get_search_parties_command,
|
||||||
get_update_organization_command,
|
get_update_organization_command,
|
||||||
)
|
)
|
||||||
from src.presentation.schemas.mappers import (
|
from src.presentation.schemas.mappers import (
|
||||||
create_wallets_to_response,
|
create_wallets_to_response,
|
||||||
mnemonic_to_response,
|
mnemonic_to_response,
|
||||||
organization_to_response,
|
organization_to_response,
|
||||||
|
party_search_to_response,
|
||||||
secret_key_to_response,
|
secret_key_to_response,
|
||||||
wallet_to_response,
|
wallet_to_response,
|
||||||
)
|
)
|
||||||
@@ -35,6 +38,7 @@ from src.presentation.schemas.organization import (
|
|||||||
MnemonicResponse,
|
MnemonicResponse,
|
||||||
OrganizationListResponse,
|
OrganizationListResponse,
|
||||||
OrganizationResponse,
|
OrganizationResponse,
|
||||||
|
PartySearchListResponse,
|
||||||
SecretKeyResponse,
|
SecretKeyResponse,
|
||||||
UpdateOrganizationRequest,
|
UpdateOrganizationRequest,
|
||||||
WalletResponse,
|
WalletResponse,
|
||||||
@@ -47,10 +51,11 @@ organizations_router = APIRouter(prefix='/organizations', tags=['organizations']
|
|||||||
async def list_organizations(
|
async def list_organizations(
|
||||||
limit: int = Query(default=50, ge=1, le=200),
|
limit: int = Query(default=50, ge=1, le=200),
|
||||||
offset: int = Query(default=0, ge=0),
|
offset: int = Query(default=0, ge=0),
|
||||||
|
q: str | None = Query(default=None, min_length=1, max_length=255),
|
||||||
auth: AdminAuthContext = Depends(require_admin_access),
|
auth: AdminAuthContext = Depends(require_admin_access),
|
||||||
command: ListOrganizationsCommand = Depends(get_list_organizations_command),
|
command: ListOrganizationsCommand = Depends(get_list_organizations_command),
|
||||||
):
|
):
|
||||||
items, total = await command(limit=limit, offset=offset)
|
items, total = await command(limit=limit, offset=offset, search=q)
|
||||||
return OrganizationListResponse(
|
return OrganizationListResponse(
|
||||||
items=[organization_to_response(x) for x in items],
|
items=[organization_to_response(x) for x in items],
|
||||||
total=total,
|
total=total,
|
||||||
@@ -82,6 +87,21 @@ async def create_organization(
|
|||||||
return organization_to_response(org)
|
return organization_to_response(org)
|
||||||
|
|
||||||
|
|
||||||
|
@organizations_router.get('/search', response_model=PartySearchListResponse)
|
||||||
|
async def search_parties(
|
||||||
|
q: str = Query(min_length=1, max_length=255),
|
||||||
|
limit: int = Query(default=50, ge=1, le=200),
|
||||||
|
offset: int = Query(default=0, ge=0),
|
||||||
|
auth: AdminAuthContext = Depends(require_admin_access),
|
||||||
|
command: SearchPartiesCommand = Depends(get_search_parties_command),
|
||||||
|
):
|
||||||
|
items, total = await command(query=q, limit=limit, offset=offset)
|
||||||
|
return PartySearchListResponse(
|
||||||
|
items=[party_search_to_response(x) for x in items],
|
||||||
|
total=total,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@organizations_router.get('/{organization_id}', response_model=OrganizationResponse)
|
@organizations_router.get('/{organization_id}', response_model=OrganizationResponse)
|
||||||
async def get_organization(
|
async def get_organization(
|
||||||
organization_id: str,
|
organization_id: str,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from src.application.domain.entities.organization import (
|
|||||||
LegalEntityEntity,
|
LegalEntityEntity,
|
||||||
OrganizationDocumentSlot,
|
OrganizationDocumentSlot,
|
||||||
OrganizationWalletEntity,
|
OrganizationWalletEntity,
|
||||||
|
PartySearchEntity,
|
||||||
PurchaseRequestEntity,
|
PurchaseRequestEntity,
|
||||||
)
|
)
|
||||||
from src.presentation.schemas.organization import (
|
from src.presentation.schemas.organization import (
|
||||||
@@ -11,6 +12,7 @@ from src.presentation.schemas.organization import (
|
|||||||
DocumentResponse,
|
DocumentResponse,
|
||||||
MnemonicResponse,
|
MnemonicResponse,
|
||||||
OrganizationResponse,
|
OrganizationResponse,
|
||||||
|
PartySearchResponse,
|
||||||
PurchaseRequestResponse,
|
PurchaseRequestResponse,
|
||||||
SecretKeyResponse,
|
SecretKeyResponse,
|
||||||
WalletResponse,
|
WalletResponse,
|
||||||
@@ -41,6 +43,21 @@ def organization_to_response(entity: LegalEntityEntity) -> OrganizationResponse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def party_search_to_response(entity: PartySearchEntity) -> PartySearchResponse:
|
||||||
|
return PartySearchResponse(
|
||||||
|
id=entity.id,
|
||||||
|
account_type=entity.account_type,
|
||||||
|
user_id=entity.user_id,
|
||||||
|
email=entity.email,
|
||||||
|
name=entity.name,
|
||||||
|
inn=entity.inn,
|
||||||
|
phone=entity.phone,
|
||||||
|
status=entity.status,
|
||||||
|
kyc_verified=entity.kyc_verified,
|
||||||
|
created_at=entity.created_at.isoformat() if entity.created_at else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def wallet_to_response(entity: OrganizationWalletEntity) -> WalletResponse:
|
def wallet_to_response(entity: OrganizationWalletEntity) -> WalletResponse:
|
||||||
return WalletResponse(
|
return WalletResponse(
|
||||||
id=entity.id,
|
id=entity.id,
|
||||||
|
|||||||
@@ -62,6 +62,24 @@ class OrganizationListResponse(BaseModel):
|
|||||||
total: int
|
total: int
|
||||||
|
|
||||||
|
|
||||||
|
class PartySearchResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
account_type: str
|
||||||
|
user_id: str
|
||||||
|
email: str | None
|
||||||
|
name: str | None
|
||||||
|
inn: str | None
|
||||||
|
phone: str | None
|
||||||
|
status: str | None
|
||||||
|
kyc_verified: bool | None
|
||||||
|
created_at: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class PartySearchListResponse(BaseModel):
|
||||||
|
items: list[PartySearchResponse]
|
||||||
|
total: int
|
||||||
|
|
||||||
|
|
||||||
class WalletResponse(BaseModel):
|
class WalletResponse(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
chain: str
|
chain: str
|
||||||
|
|||||||
Reference in New Issue
Block a user