From dc055284059a2dba9875e091dab1568ea31ff4a8 Mon Sep 17 00:00:00 2001 From: Noloquideus Date: Wed, 10 Jun 2026 17:01:51 +0300 Subject: [PATCH] feat: Add new command --- src/application/commands/__init__.py | 2 ++ .../commands/purchase_request_commands.py | 36 +++++++++++++++++++ src/presentation/dependencies/commands.py | 8 +++++ src/presentation/routing/purchase_requests.py | 14 ++++++++ src/presentation/schemas/organization.py | 14 ++++++++ 5 files changed, 74 insertions(+) diff --git a/src/application/commands/__init__.py b/src/application/commands/__init__.py index 783293c..0907f29 100644 --- a/src/application/commands/__init__.py +++ b/src/application/commands/__init__.py @@ -23,6 +23,7 @@ from src.application.commands.organization_wallet_commands import ( from src.application.commands.purchase_request_commands import ( ListPurchaseRequestsCommand, GetPurchaseRequestCommand, + UpdatePurchaseRequestCommand, UpdatePurchaseRequestStatusCommand, SetPurchaseRequestQuoteCommand, ) @@ -41,6 +42,7 @@ __all__ = [ 'UpdateOrganizationCommand', 'ListPurchaseRequestsCommand', 'GetPurchaseRequestCommand', + 'UpdatePurchaseRequestCommand', 'UpdatePurchaseRequestStatusCommand', 'SetPurchaseRequestQuoteCommand', 'ListOrganizationDocumentsCommand', diff --git a/src/application/commands/purchase_request_commands.py b/src/application/commands/purchase_request_commands.py index 8f3a2ad..8f76769 100644 --- a/src/application/commands/purchase_request_commands.py +++ b/src/application/commands/purchase_request_commands.py @@ -81,10 +81,46 @@ class UpdatePurchaseRequestStatusCommand: values['tx_hash'] = tx_hash if status == 'completed': values['completed_at'] = datetime.now(timezone.utc) + else: + values['completed_at'] = None return await self._unit_of_work.purchase_request_repository.update(request_id, values=values) +class UpdatePurchaseRequestCommand: + ALLOWED_FIELDS = frozenset({ + 'status', + 'usdt_amount', + 'rub_amount', + 'exchange_rate', + 'service_fee_percent', + 'comment', + 'admin_comment', + 'target_wallet_chain', + 'target_wallet_address', + 'tx_hash', + 'assigned_to', + }) + + def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger): + self._unit_of_work = unit_of_work + self._logger = logger + + @transactional + async def __call__(self, request_id: str, *, values: dict[str, Any]) -> PurchaseRequestEntity: + filtered = {k: v for k, v in values.items() if k in self.ALLOWED_FIELDS} + status = filtered.get('status') + if status is not None: + if status not in VALID_STATUSES: + raise ApplicationException(status_code=400, message='Invalid status') + if status == 'completed': + filtered['completed_at'] = datetime.now(timezone.utc) + else: + filtered['completed_at'] = None + + return await self._unit_of_work.purchase_request_repository.update(request_id, values=filtered) + + class SetPurchaseRequestQuoteCommand: def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger): self._unit_of_work = unit_of_work diff --git a/src/presentation/dependencies/commands.py b/src/presentation/dependencies/commands.py index 99fb402..5cd04be 100644 --- a/src/presentation/dependencies/commands.py +++ b/src/presentation/dependencies/commands.py @@ -22,6 +22,7 @@ from src.application.commands import ( SearchPartiesCommand, SetPurchaseRequestQuoteCommand, UpdateOrganizationCommand, + UpdatePurchaseRequestCommand, UpdatePurchaseRequestStatusCommand, PutOrganizationDocumentCommand, ) @@ -186,6 +187,13 @@ def get_update_purchase_request_status_command( return UpdatePurchaseRequestStatusCommand(uow, logger) +def get_update_purchase_request_command( + uow: IUnitOfWork = Depends(get_unit_of_work), + logger: ILogger = Depends(get_logger), +) -> UpdatePurchaseRequestCommand: + return UpdatePurchaseRequestCommand(uow, logger) + + def get_set_purchase_request_quote_command( uow: IUnitOfWork = Depends(get_unit_of_work), logger: ILogger = Depends(get_logger), diff --git a/src/presentation/routing/purchase_requests.py b/src/presentation/routing/purchase_requests.py index 0e43a1d..3d1d408 100644 --- a/src/presentation/routing/purchase_requests.py +++ b/src/presentation/routing/purchase_requests.py @@ -4,6 +4,7 @@ from src.application.commands import ( GetPurchaseRequestCommand, ListPurchaseRequestsCommand, SetPurchaseRequestQuoteCommand, + UpdatePurchaseRequestCommand, UpdatePurchaseRequestStatusCommand, ) from src.application.domain.dto import AdminAuthContext @@ -12,6 +13,7 @@ from src.presentation.dependencies.commands import ( get_get_purchase_request_command, get_list_purchase_requests_command, get_set_purchase_request_quote_command, + get_update_purchase_request_command, get_update_purchase_request_status_command, ) from src.presentation.schemas.mappers import purchase_request_to_response @@ -19,6 +21,7 @@ from src.presentation.schemas.organization import ( PurchaseRequestListResponse, PurchaseRequestResponse, SetPurchaseRequestQuoteBody, + UpdatePurchaseRequestBody, UpdatePurchaseRequestStatusBody, ) @@ -56,6 +59,17 @@ async def get_purchase_request( return purchase_request_to_response(item) +@purchase_requests_router.patch('/{request_id}', response_model=PurchaseRequestResponse) +async def update_purchase_request( + request_id: str, + body: UpdatePurchaseRequestBody, + auth: AdminAuthContext = Depends(require_admin_role('compliance', 'superadmin')), + command: UpdatePurchaseRequestCommand = Depends(get_update_purchase_request_command), +): + item = await command(request_id, values=body.model_dump(exclude_unset=True)) + return purchase_request_to_response(item) + + @purchase_requests_router.patch('/{request_id}/status', response_model=PurchaseRequestResponse) async def update_purchase_request_status( request_id: str, diff --git a/src/presentation/schemas/organization.py b/src/presentation/schemas/organization.py index 3c327a5..dde9b63 100644 --- a/src/presentation/schemas/organization.py +++ b/src/presentation/schemas/organization.py @@ -121,6 +121,20 @@ class UpdatePurchaseRequestStatusBody(BaseModel): tx_hash: str | None = None +class UpdatePurchaseRequestBody(BaseModel): + status: str | None = None + usdt_amount: Decimal | None = Field(default=None, gt=0) + 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=None, 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 SetPurchaseRequestQuoteBody(BaseModel): rub_amount: Decimal = Field(gt=0) exchange_rate: Decimal = Field(gt=0)