From 627d3eb1591c9c623849145a6b477ef4d9d42a17 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Sat, 25 Apr 2026 23:27:56 -0500 Subject: [PATCH] feat: add file report and transition filing status operations with corresponding models --- .../extensions_robo_ledger/op_file_report.py | 277 +++++++++++++++++ .../op_transition_filing_status.py | 281 ++++++++++++++++++ robosystems_client/clients/ledger_client.py | 51 ++++ .../graphql/queries/ledger/__init__.py | 69 +++++ robosystems_client/models/__init__.py | 4 + .../models/file_report_request.py | 67 +++++ .../transition_filing_status_request.py | 75 +++++ 7 files changed, 824 insertions(+) create mode 100644 robosystems_client/api/extensions_robo_ledger/op_file_report.py create mode 100644 robosystems_client/api/extensions_robo_ledger/op_transition_filing_status.py create mode 100644 robosystems_client/models/file_report_request.py create mode 100644 robosystems_client/models/transition_filing_status_request.py diff --git a/robosystems_client/api/extensions_robo_ledger/op_file_report.py b/robosystems_client/api/extensions_robo_ledger/op_file_report.py new file mode 100644 index 0000000..2eafb29 --- /dev/null +++ b/robosystems_client/api/extensions_robo_ledger/op_file_report.py @@ -0,0 +1,277 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error_response import ErrorResponse +from ...models.file_report_request import FileReportRequest +from ...models.http_validation_error import HTTPValidationError +from ...models.operation_envelope import OperationEnvelope +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: FileReportRequest, + idempotency_key: None | str | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(idempotency_key, Unset): + headers["Idempotency-Key"] = idempotency_key + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/extensions/roboledger/{graph_id}/operations/file-report".format( + graph_id=quote(str(graph_id), safe=""), + ), + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ErrorResponse | HTTPValidationError | OperationEnvelope | None: + if response.status_code == 200: + response_200 = OperationEnvelope.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = ErrorResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ErrorResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 403: + response_403 = ErrorResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = ErrorResponse.from_dict(response.json()) + + return response_404 + + if response.status_code == 409: + response_409 = ErrorResponse.from_dict(response.json()) + + return response_409 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = ErrorResponse.from_dict(response.json()) + + return response_429 + + if response.status_code == 500: + response_500 = ErrorResponse.from_dict(response.json()) + + return response_500 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[ErrorResponse | HTTPValidationError | OperationEnvelope]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: FileReportRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | HTTPValidationError | OperationEnvelope]: + """File Report + + Transitions the Report's filing_status to 'filed' — locks the package. Allowed from 'draft' or + 'under_review'. Stamps filed_at + filed_by. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (FileReportRequest): Transition a Report to ``filed`` — locks the package. + + Acceptable from ``draft`` or ``under_review``. ``filed_by`` and + ``filed_at`` are stamped from the auth context + server clock; the + request itself carries no fields today (kept as a model for OpenAPI + shape consistency and to avoid breaking changes if we add fields). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | HTTPValidationError | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + graph_id: str, + *, + client: AuthenticatedClient, + body: FileReportRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | HTTPValidationError | OperationEnvelope | None: + """File Report + + Transitions the Report's filing_status to 'filed' — locks the package. Allowed from 'draft' or + 'under_review'. Stamps filed_at + filed_by. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (FileReportRequest): Transition a Report to ``filed`` — locks the package. + + Acceptable from ``draft`` or ``under_review``. ``filed_by`` and + ``filed_at`` are stamped from the auth context + server clock; the + request itself carries no fields today (kept as a model for OpenAPI + shape consistency and to avoid breaking changes if we add fields). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | HTTPValidationError | OperationEnvelope + """ + + return sync_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ).parsed + + +async def asyncio_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: FileReportRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | HTTPValidationError | OperationEnvelope]: + """File Report + + Transitions the Report's filing_status to 'filed' — locks the package. Allowed from 'draft' or + 'under_review'. Stamps filed_at + filed_by. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (FileReportRequest): Transition a Report to ``filed`` — locks the package. + + Acceptable from ``draft`` or ``under_review``. ``filed_by`` and + ``filed_at`` are stamped from the auth context + server clock; the + request itself carries no fields today (kept as a model for OpenAPI + shape consistency and to avoid breaking changes if we add fields). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | HTTPValidationError | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + graph_id: str, + *, + client: AuthenticatedClient, + body: FileReportRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | HTTPValidationError | OperationEnvelope | None: + """File Report + + Transitions the Report's filing_status to 'filed' — locks the package. Allowed from 'draft' or + 'under_review'. Stamps filed_at + filed_by. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (FileReportRequest): Transition a Report to ``filed`` — locks the package. + + Acceptable from ``draft`` or ``under_review``. ``filed_by`` and + ``filed_at`` are stamped from the auth context + server clock; the + request itself carries no fields today (kept as a model for OpenAPI + shape consistency and to avoid breaking changes if we add fields). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | HTTPValidationError | OperationEnvelope + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ) + ).parsed diff --git a/robosystems_client/api/extensions_robo_ledger/op_transition_filing_status.py b/robosystems_client/api/extensions_robo_ledger/op_transition_filing_status.py new file mode 100644 index 0000000..bf20e88 --- /dev/null +++ b/robosystems_client/api/extensions_robo_ledger/op_transition_filing_status.py @@ -0,0 +1,281 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error_response import ErrorResponse +from ...models.http_validation_error import HTTPValidationError +from ...models.operation_envelope import OperationEnvelope +from ...models.transition_filing_status_request import TransitionFilingStatusRequest +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: TransitionFilingStatusRequest, + idempotency_key: None | str | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(idempotency_key, Unset): + headers["Idempotency-Key"] = idempotency_key + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/extensions/roboledger/{graph_id}/operations/transition-filing-status".format( + graph_id=quote(str(graph_id), safe=""), + ), + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ErrorResponse | HTTPValidationError | OperationEnvelope | None: + if response.status_code == 200: + response_200 = OperationEnvelope.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = ErrorResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ErrorResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 403: + response_403 = ErrorResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = ErrorResponse.from_dict(response.json()) + + return response_404 + + if response.status_code == 409: + response_409 = ErrorResponse.from_dict(response.json()) + + return response_409 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = ErrorResponse.from_dict(response.json()) + + return response_429 + + if response.status_code == 500: + response_500 = ErrorResponse.from_dict(response.json()) + + return response_500 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[ErrorResponse | HTTPValidationError | OperationEnvelope]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: TransitionFilingStatusRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | HTTPValidationError | OperationEnvelope]: + """Transition Filing Status + + Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed → + archived). Use 'file-report' to reach 'filed' so audit fields land cleanly. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (TransitionFilingStatusRequest): Generic filing-status transition — escape hatch for + non-file moves. + + Used for ``draft → under_review`` (submit for review) and + ``filed → archived`` (supersede / retire). Filing the package goes + through :class:`FileReportRequest` so ``filed_at`` / ``filed_by`` + audit fields land cleanly. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | HTTPValidationError | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + graph_id: str, + *, + client: AuthenticatedClient, + body: TransitionFilingStatusRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | HTTPValidationError | OperationEnvelope | None: + """Transition Filing Status + + Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed → + archived). Use 'file-report' to reach 'filed' so audit fields land cleanly. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (TransitionFilingStatusRequest): Generic filing-status transition — escape hatch for + non-file moves. + + Used for ``draft → under_review`` (submit for review) and + ``filed → archived`` (supersede / retire). Filing the package goes + through :class:`FileReportRequest` so ``filed_at`` / ``filed_by`` + audit fields land cleanly. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | HTTPValidationError | OperationEnvelope + """ + + return sync_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ).parsed + + +async def asyncio_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: TransitionFilingStatusRequest, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | HTTPValidationError | OperationEnvelope]: + """Transition Filing Status + + Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed → + archived). Use 'file-report' to reach 'filed' so audit fields land cleanly. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (TransitionFilingStatusRequest): Generic filing-status transition — escape hatch for + non-file moves. + + Used for ``draft → under_review`` (submit for review) and + ``filed → archived`` (supersede / retire). Filing the package goes + through :class:`FileReportRequest` so ``filed_at`` / ``filed_by`` + audit fields land cleanly. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | HTTPValidationError | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + graph_id: str, + *, + client: AuthenticatedClient, + body: TransitionFilingStatusRequest, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | HTTPValidationError | OperationEnvelope | None: + """Transition Filing Status + + Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed → + archived). Use 'file-report' to reach 'filed' so audit fields land cleanly. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (TransitionFilingStatusRequest): Generic filing-status transition — escape hatch for + non-file moves. + + Used for ``draft → under_review`` (submit for review) and + ``filed → archived`` (supersede / retire). Filing the package goes + through :class:`FileReportRequest` so ``filed_at`` / ``filed_by`` + audit fields land cleanly. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | HTTPValidationError | OperationEnvelope + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ) + ).parsed diff --git a/robosystems_client/clients/ledger_client.py b/robosystems_client/clients/ledger_client.py index b1cd9d8..2c96151 100644 --- a/robosystems_client/clients/ledger_client.py +++ b/robosystems_client/clients/ledger_client.py @@ -113,6 +113,9 @@ from ..api.extensions_robo_ledger.op_delete_report import ( sync_detailed as op_delete_report, ) +from ..api.extensions_robo_ledger.op_file_report import ( + sync_detailed as op_file_report, +) from ..api.extensions_robo_ledger.op_regenerate_report import ( sync_detailed as op_regenerate_report, ) @@ -122,6 +125,9 @@ from ..api.extensions_robo_ledger.op_share_report import ( sync_detailed as op_share_report, ) +from ..api.extensions_robo_ledger.op_transition_filing_status import ( + sync_detailed as op_transition_filing_status, +) from ..api.extensions_robo_ledger.op_update_publish_list import ( sync_detailed as op_update_publish_list, ) @@ -191,6 +197,7 @@ ) from ..graphql.queries.ledger import ( GET_PUBLISH_LIST_QUERY, + GET_REPORT_PACKAGE_QUERY, GET_REPORT_QUERY, GET_STATEMENT_QUERY, LIST_PUBLISH_LISTS_QUERY, @@ -198,6 +205,7 @@ parse_publish_list, parse_publish_lists, parse_report, + parse_report_package, parse_reports, parse_statement, ) @@ -250,8 +258,10 @@ from ..models.create_report_request import CreateReportRequest from ..models.delete_publish_list_operation import DeletePublishListOperation from ..models.delete_report_operation import DeleteReportOperation +from ..models.file_report_request import FileReportRequest from ..models.operation_envelope import OperationEnvelope from ..models.regenerate_report_operation import RegenerateReportOperation +from ..models.transition_filing_status_request import TransitionFilingStatusRequest from ..models.remove_publish_list_member_operation import ( RemovePublishListMemberOperation, ) @@ -1413,6 +1423,18 @@ def get_report(self, graph_id: str, report_id: str) -> dict[str, Any] | None: data = self._query(graph_id, GET_REPORT_QUERY, {"reportId": report_id}) return parse_report(data) + def get_report_package(self, graph_id: str, report_id: str) -> dict[str, Any] | None: + """Rehydrate a Report as a package — Report metadata + N rendered + `InformationBlock` envelopes (one per attached FactSet). + + Single round trip: returns everything needed to render BS + IS (and any + other statements the Report generated) without per-section fetches. + Each item's ``block`` is a fully-rehydrated ``InformationBlock`` envelope + pinned to its specific FactSet snapshot. + """ + data = self._query(graph_id, GET_REPORT_PACKAGE_QUERY, {"reportId": report_id}) + return parse_report_package(data) + def get_statement( self, graph_id: str, report_id: str, structure_type: str ) -> dict[str, Any] | None: @@ -1461,6 +1483,35 @@ def share_report( envelope = self._call_op("Share report", response) return {"operation_id": envelope.operation_id, "status": envelope.status} + def file_report(self, graph_id: str, report_id: str) -> dict[str, Any]: + """Transition a Report's filing_status to 'filed' — locks the package. + + Allowed from 'draft' or 'under_review'. Stamps filed_at + filed_by from + the auth context + server clock. + """ + body = FileReportRequest(report_id=report_id) + response = op_file_report(graph_id=graph_id, body=body, client=self._get_client()) + envelope = self._call_op("File report", response) + return {"operation_id": envelope.operation_id, "status": envelope.status} + + def transition_filing_status( + self, graph_id: str, report_id: str, target_status: str + ) -> dict[str, Any]: + """Move a Report along the non-file legs of the filing lifecycle. + + Use ``file_report()`` to reach 'filed' so audit fields land cleanly. + Other transitions (draft ↔ under_review, filed → archived) go through + here so the legal-transition graph stays in one place. + """ + body = TransitionFilingStatusRequest( + report_id=report_id, target_status=target_status + ) + response = op_transition_filing_status( + graph_id=graph_id, body=body, client=self._get_client() + ) + envelope = self._call_op("Transition filing status", response) + return {"operation_id": envelope.operation_id, "status": envelope.status} + def is_shared_report(self, report: dict[str, Any] | Any) -> bool: """Check if a report was received via sharing (vs locally created).""" if isinstance(report, dict): diff --git a/robosystems_client/graphql/queries/ledger/__init__.py b/robosystems_client/graphql/queries/ledger/__init__.py index a9d2444..92843df 100644 --- a/robosystems_client/graphql/queries/ledger/__init__.py +++ b/robosystems_client/graphql/queries/ledger/__init__.py @@ -678,6 +678,7 @@ def parse_reports(data: dict[str, Any]) -> list[dict[str, Any]]: id name taxonomyId generationStatus periodType periodStart periodEnd comparative mappingId aiGenerated createdAt lastGenerated entityName + filingStatus filedAt filedBy supersedesId supersededById sourceGraphId sourceReportId sharedAt periods { start end label } structures { id name structureType } @@ -691,6 +692,74 @@ def parse_report(data: dict[str, Any]) -> dict[str, Any] | None: return keys_to_snake(r) if r is not None else None +# Report rehydrated as a package — Report metadata + N rendered +# `InformationBlock` envelopes (one per attached FactSet). Drives the +# `/reports/[id]` package viewer and replaces the per-statement +# `getStatement(reportId, structureType)` round-trip flow. +GET_REPORT_PACKAGE_QUERY = """ +query GetLedgerReportPackage($reportId: String!) { + reportPackage(reportId: $reportId) { + id name description taxonomyId + periodType periodStart periodEnd + generationStatus lastGenerated + filingStatus filedAt filedBy + supersedesId supersededById + sourceGraphId sourceReportId sharedAt + entityName aiGenerated createdAt createdBy + items { + factSetId structureId displayOrder + block { + id blockType name displayName category + taxonomyId taxonomyName + informationModel { conceptArrangement memberArrangement } + artifact { topic parentheticalNote template mechanics } + elements { + id qname name code elementType + isAbstract isMonetary balanceType periodType + } + connections { + id fromElementId toElementId associationType + arcrole orderValue weight + } + facts { + id elementId value periodStart periodEnd + periodType unit factScope factSetId + } + rules { + id ruleCategory rulePattern ruleExpression + ruleMessage ruleSeverity ruleOrigin + } + factSet { + id structureId periodStart periodEnd + factsetType entityId reportId + } + verificationResults { + id ruleId structureId factSetId status message + periodStart periodEnd evaluatedAt + } + view { + rendering { + rows { + elementId elementQname elementName classification + balanceType values isSubtotal depth + } + periods { start end label } + validation { passed checks failures warnings } + unmappedCount + } + } + } + } + } +} +""".strip() + + +def parse_report_package(data: dict[str, Any]) -> dict[str, Any] | None: + pkg = data.get("reportPackage") + return keys_to_snake(pkg) if pkg is not None else None + + GET_STATEMENT_QUERY = """ query GetLedgerStatement($reportId: String!, $structureType: String!) { statement(reportId: $reportId, structureType: $structureType) { diff --git a/robosystems_client/models/__init__.py b/robosystems_client/models/__init__.py index 902f0ef..f47f2bb 100644 --- a/robosystems_client/models/__init__.py +++ b/robosystems_client/models/__init__.py @@ -159,6 +159,7 @@ from .evaluate_rules_request import EvaluateRulesRequest from .file_info import FileInfo from .file_layer_status import FileLayerStatus +from .file_report_request import FileReportRequest from .file_status_update import FileStatusUpdate from .file_upload_request import FileUploadRequest from .file_upload_response import FileUploadResponse @@ -364,6 +365,7 @@ from .transaction_template_entry import TransactionTemplateEntry from .transaction_template_item import TransactionTemplateItem from .transaction_template_leg import TransactionTemplateLeg +from .transition_filing_status_request import TransitionFilingStatusRequest from .upcoming_invoice import UpcomingInvoice from .update_agent_request import UpdateAgentRequest from .update_agent_request_address_type_0 import UpdateAgentRequestAddressType0 @@ -545,6 +547,7 @@ "EvaluateRulesRequest", "FileInfo", "FileLayerStatus", + "FileReportRequest", "FileStatusUpdate", "FileUploadRequest", "FileUploadResponse", @@ -714,6 +717,7 @@ "TransactionTemplateEntry", "TransactionTemplateItem", "TransactionTemplateLeg", + "TransitionFilingStatusRequest", "UpcomingInvoice", "UpdateAgentRequest", "UpdateAgentRequestAddressType0", diff --git a/robosystems_client/models/file_report_request.py b/robosystems_client/models/file_report_request.py new file mode 100644 index 0000000..22bcaff --- /dev/null +++ b/robosystems_client/models/file_report_request.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="FileReportRequest") + + +@_attrs_define +class FileReportRequest: + """Transition a Report to ``filed`` — locks the package. + + Acceptable from ``draft`` or ``under_review``. ``filed_by`` and + ``filed_at`` are stamped from the auth context + server clock; the + request itself carries no fields today (kept as a model for OpenAPI + shape consistency and to avoid breaking changes if we add fields). + + Attributes: + report_id (str): The Report to file. + """ + + report_id: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + report_id = self.report_id + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "report_id": report_id, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + report_id = d.pop("report_id") + + file_report_request = cls( + report_id=report_id, + ) + + file_report_request.additional_properties = d + return file_report_request + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/robosystems_client/models/transition_filing_status_request.py b/robosystems_client/models/transition_filing_status_request.py new file mode 100644 index 0000000..a0eaa95 --- /dev/null +++ b/robosystems_client/models/transition_filing_status_request.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="TransitionFilingStatusRequest") + + +@_attrs_define +class TransitionFilingStatusRequest: + """Generic filing-status transition — escape hatch for non-file moves. + + Used for ``draft → under_review`` (submit for review) and + ``filed → archived`` (supersede / retire). Filing the package goes + through :class:`FileReportRequest` so ``filed_at`` / ``filed_by`` + audit fields land cleanly. + + Attributes: + report_id (str): + target_status (str): under_review | archived + """ + + report_id: str + target_status: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + report_id = self.report_id + + target_status = self.target_status + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "report_id": report_id, + "target_status": target_status, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + report_id = d.pop("report_id") + + target_status = d.pop("target_status") + + transition_filing_status_request = cls( + report_id=report_id, + target_status=target_status, + ) + + transition_filing_status_request.additional_properties = d + return transition_filing_status_request + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties