feat: add create request

This commit is contained in:
2026-06-10 18:54:30 +03:00
parent dc05528405
commit 5e085ae67e
7 changed files with 114 additions and 0 deletions

View File

@@ -5,6 +5,10 @@ from src.application.domain.entities.organization import PurchaseRequestEntity
class IPurchaseRequestRepository(ABC):
@abstractmethod
async def create(self, values: dict[str, Any]) -> PurchaseRequestEntity:
raise NotImplementedError
@abstractmethod
async def get_by_id(self, request_id: str) -> PurchaseRequestEntity:
raise NotImplementedError

View File

@@ -21,6 +21,7 @@ from src.application.commands.organization_wallet_commands import (
ListOrganizationWalletsCommand,
)
from src.application.commands.purchase_request_commands import (
CreatePurchaseRequestCommand,
ListPurchaseRequestsCommand,
GetPurchaseRequestCommand,
UpdatePurchaseRequestCommand,
@@ -40,6 +41,7 @@ __all__ = [
'GetOrganizationCommand',
'SearchPartiesCommand',
'UpdateOrganizationCommand',
'CreatePurchaseRequestCommand',
'ListPurchaseRequestsCommand',
'GetPurchaseRequestCommand',
'UpdatePurchaseRequestCommand',

View File

@@ -54,6 +54,54 @@ class GetPurchaseRequestCommand:
return await self._unit_of_work.purchase_request_repository.get_by_id(request_id)
class CreatePurchaseRequestCommand:
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
self._unit_of_work = unit_of_work
self._logger = logger
@transactional
async def __call__(
self,
*,
organization_id: str,
usdt_amount: Decimal,
status: str = 'submitted',
rub_amount: Decimal | None = None,
exchange_rate: Decimal | None = None,
service_fee_percent: Decimal | None = None,
comment: str | None = None,
admin_comment: str | None = None,
target_wallet_chain: str | None = 'ETH',
target_wallet_address: str | None = None,
tx_hash: str | None = None,
assigned_to: str | None = None,
) -> PurchaseRequestEntity:
if status not in VALID_STATUSES:
raise ApplicationException(status_code=400, message='Invalid status')
await self._unit_of_work.legal_entity_repository.get_by_id(organization_id)
values: dict[str, Any] = {
'organization_id': organization_id,
'status': status,
'usdt_amount': usdt_amount,
'rub_amount': rub_amount,
'exchange_rate': exchange_rate,
'service_fee_percent': service_fee_percent,
'comment': comment,
'admin_comment': admin_comment,
'target_wallet_chain': target_wallet_chain,
'target_wallet_address': target_wallet_address,
'tx_hash': tx_hash,
'assigned_to': assigned_to,
}
if status == 'completed':
values['completed_at'] = datetime.now(timezone.utc)
created = await self._unit_of_work.purchase_request_repository.create(values)
self._logger.info(f'Purchase request created id={created.id} org={organization_id}')
return created
class UpdatePurchaseRequestStatusCommand:
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
self._unit_of_work = unit_of_work

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from typing import Any
from ulid import ULID
from sqlalchemy import func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import SQLAlchemyError
@@ -45,6 +46,16 @@ class PurchaseRequestRepository(IPurchaseRequestRepository):
stmt = stmt.where(PurchaseRequestModel.organization_id == organization_id)
return stmt
async def create(self, values: dict[str, Any]) -> PurchaseRequestEntity:
model = PurchaseRequestModel(id=str(ULID()), **values)
self._session.add(model)
try:
await self._session.flush()
return self._to_entity(model)
except SQLAlchemyError as exc:
self._logger.exception(str(exc))
raise ApplicationException(status_code=500, message='Database error')
async def get_by_id(self, request_id: str) -> PurchaseRequestEntity:
res = await self._session.execute(select(PurchaseRequestModel).where(PurchaseRequestModel.id == request_id))
m = res.scalar_one_or_none()

View File

@@ -7,6 +7,7 @@ from src.application.commands import (
AdminLoginCommand,
AdminLogoutCommand,
AdminJwtRefreshCommand,
CreatePurchaseRequestCommand,
GetAdminMeCommand,
CreateOrganizationCommand,
CreateOrganizationWalletsCommand,
@@ -173,6 +174,13 @@ def get_list_purchase_requests_command(
return ListPurchaseRequestsCommand(uow, logger)
def get_create_purchase_request_command(
uow: IUnitOfWork = Depends(get_unit_of_work),
logger: ILogger = Depends(get_logger),
) -> CreatePurchaseRequestCommand:
return CreatePurchaseRequestCommand(uow, logger)
def get_get_purchase_request_command(
uow: IUnitOfWork = Depends(get_unit_of_work),
logger: ILogger = Depends(get_logger),

View File

@@ -1,6 +1,7 @@
from fastapi import APIRouter, Depends, Query, status
from src.application.commands import (
CreatePurchaseRequestCommand,
GetPurchaseRequestCommand,
ListPurchaseRequestsCommand,
SetPurchaseRequestQuoteCommand,
@@ -10,6 +11,7 @@ from src.application.commands import (
from src.application.domain.dto import AdminAuthContext
from src.presentation.decorators.admin_auth import require_admin_access, require_admin_role
from src.presentation.dependencies.commands import (
get_create_purchase_request_command,
get_get_purchase_request_command,
get_list_purchase_requests_command,
get_set_purchase_request_quote_command,
@@ -18,6 +20,7 @@ from src.presentation.dependencies.commands import (
)
from src.presentation.schemas.mappers import purchase_request_to_response
from src.presentation.schemas.organization import (
CreatePurchaseRequestBody,
PurchaseRequestListResponse,
PurchaseRequestResponse,
SetPurchaseRequestQuoteBody,
@@ -49,6 +52,29 @@ async def list_purchase_requests(
)
@purchase_requests_router.post('', response_model=PurchaseRequestResponse, status_code=status.HTTP_201_CREATED)
async def create_purchase_request(
body: CreatePurchaseRequestBody,
auth: AdminAuthContext = Depends(require_admin_role('compliance', 'superadmin')),
command: CreatePurchaseRequestCommand = Depends(get_create_purchase_request_command),
):
item = await command(
organization_id=body.organization_id,
usdt_amount=body.usdt_amount,
status=body.status,
rub_amount=body.rub_amount,
exchange_rate=body.exchange_rate,
service_fee_percent=body.service_fee_percent,
comment=body.comment,
admin_comment=body.admin_comment,
target_wallet_chain=body.target_wallet_chain,
target_wallet_address=body.target_wallet_address,
tx_hash=body.tx_hash,
assigned_to=body.assigned_to,
)
return purchase_request_to_response(item)
@purchase_requests_router.get('/{request_id}', response_model=PurchaseRequestResponse)
async def get_purchase_request(
request_id: str,

View File

@@ -114,6 +114,21 @@ class DocumentResponse(BaseModel):
download_url: str | None = None
class CreatePurchaseRequestBody(BaseModel):
organization_id: str
usdt_amount: Decimal = Field(gt=0)
status: str = 'submitted'
rub_amount: Decimal | None = Field(default=None, gt=0)
exchange_rate: Decimal | None = Field(default=None, gt=0)
service_fee_percent: Decimal | None = Field(default=None, ge=0)
comment: str | None = None
admin_comment: str | None = None
target_wallet_chain: str | None = Field(default='ETH', max_length=16)
target_wallet_address: str | None = Field(default=None, max_length=128)
tx_hash: str | None = Field(default=None, max_length=128)
assigned_to: str | None = None
class UpdatePurchaseRequestStatusBody(BaseModel):
status: str
admin_comment: str | None = None